pax_global_header00006660000000000000000000000064141562374350014524gustar00rootroot0000000000000052 comment=f0e82c3d90322aaf84b0eb34f56099e8ccca58f7 qTox/000077500000000000000000000000001415623743500120235ustar00rootroot00000000000000qTox/.circleci/000077500000000000000000000000001415623743500136565ustar00rootroot00000000000000qTox/.circleci/config.yml000066400000000000000000000147541415623743500156610ustar00rootroot00000000000000--- version: 2.1 jobs: deps64: machine: true steps: - checkout - run: name : Export environment variables command: | echo 'export MAKEFLAGS="j3"' >> $BASH_ENV echo 'export BUILD__="x86_64"' >> $BASH_ENV echo 'export BTYPE__="release"' >> $BASH_ENV docker info - run: name: Install zip command: | sudo apt-get update sudo apt-get install zip tree - restore_cache: key: deps64-{{ checksum "windows/cross-compile/build.sh" }}-{{ checksum ".travis/build-windows.sh" }} paths: - cache - run: name: Build stage 1 command: | ./.travis/build-windows.sh "$BUILD__" "$BTYPE__" "cache/${BUILD__}" stage1 - run: name: Build stage 2 command: | ./.travis/build-windows.sh "$BUILD__" "$BTYPE__" "cache/${BUILD__}" stage2 ls -al cache - save_cache: key: deps64-{{ checksum "windows/cross-compile/build.sh" }}-{{ checksum ".travis/build-windows.sh" }} paths: - cache release64: machine: true steps: - checkout - restore_cache: key: deps64-{{ checksum "windows/cross-compile/build.sh" }}-{{ checksum ".travis/build-windows.sh" }} paths: - cache - run: name : Export environment variables command: | echo 'export BUILD__="x86_64"' >> $BASH_ENV echo 'export BTYPE__="release"' >> $BASH_ENV - run: name: Install zip command: | sudo apt-get update sudo apt-get install zip tree - run: name: Build stage 3 command: | ./.travis/build-windows.sh "$BUILD__" "$BTYPE__" "cache/${BUILD__}" stage3 - run: name: Debug info command: | ls -al ~/ tree ~/project/workspace -L 4 - store_artifacts: path: "/home/circleci/project/workspace/x86_64/qtox/release/qtox-x86_64-release.zip" - store_artifacts: path: "/home/circleci/project/workspace/x86_64/qtox/release/setup-qtox-x86_64-release.exe" debug64: machine: true steps: - checkout - restore_cache: key: deps64-{{ checksum "windows/cross-compile/build.sh" }}-{{ checksum ".travis/build-windows.sh" }} paths: - cache - run: name : Export environment variables command: | echo 'export BUILD__="x86_64"' >> $BASH_ENV echo 'export BTYPE__="debug"' >> $BASH_ENV - run: name: Install zip command: | sudo apt-get update sudo apt-get install zip tree - run: name: Build stage 3 command: | ./.travis/build-windows.sh "$BUILD__" "$BTYPE__" "cache/${BUILD__}" stage3 - run: name: Debug info command: | ls -al ~/ tree ~/project/workspace -L 4 - store_artifacts: path: "/home/circleci/project/workspace/x86_64/qtox/debug/qtox-x86_64-debug.zip" deps32: machine: true steps: - checkout - run: name : Export environment variables command: | echo 'export MAKEFLAGS="j3"' >> $BASH_ENV echo 'export BUILD__="i686"' >> $BASH_ENV echo 'export BTYPE__="release"' >> $BASH_ENV docker info - run: name: Install zip command: | sudo apt-get update sudo apt-get install zip tree - restore_cache: key: deps32-{{ checksum "windows/cross-compile/build.sh" }}-{{ checksum ".travis/build-windows.sh" }} paths: - cache - run: name: Build stage 1 command: | ./.travis/build-windows.sh "$BUILD__" "$BTYPE__" "cache/${BUILD__}" stage1 - run: name: Build stage 2 command: | ./.travis/build-windows.sh "$BUILD__" "$BTYPE__" "cache/${BUILD__}" stage2 ls -al cache - save_cache: key: deps32-{{ checksum "windows/cross-compile/build.sh" }}-{{ checksum ".travis/build-windows.sh" }} paths: - cache release32: machine: true steps: - checkout - restore_cache: key: deps32-{{ checksum "windows/cross-compile/build.sh" }}-{{ checksum ".travis/build-windows.sh" }} paths: - cache - run: name : Export environment variables command: | echo 'export BUILD__="i686"' >> $BASH_ENV echo 'export BTYPE__="release"' >> $BASH_ENV - run: name: Install zip command: | sudo apt-get update sudo apt-get install zip tree - run: name: Build stage 3 command: | ./.travis/build-windows.sh "$BUILD__" "$BTYPE__" "cache/${BUILD__}" stage3 - run: name: Debug info command: | ls -al ~/ tree ~/project/workspace -L 4 - store_artifacts: path: "/home/circleci/project/workspace/i686/qtox/release/qtox-i686-release.zip" - store_artifacts: path: "/home/circleci/project/workspace/i686/qtox/release/setup-qtox-i686-release.exe" debug32: machine: true steps: - checkout - restore_cache: key: deps32-{{ checksum "windows/cross-compile/build.sh" }}-{{ checksum ".travis/build-windows.sh" }} paths: - cache - run: name : Export environment variables command: | echo 'export BUILD__="i686"' >> $BASH_ENV echo 'export BTYPE__="debug"' >> $BASH_ENV - run: name: Install zip command: | sudo apt-get update sudo apt-get install zip tree - run: name: Build stage 3 command: | ./.travis/build-windows.sh "$BUILD__" "$BTYPE__" "cache/${BUILD__}" stage3 - run: name: Debug info command: | ls -al ~/ tree ~/project/workspace -L 4 - store_artifacts: path: "/home/circleci/project/workspace/i686/qtox/debug/qtox-i686-debug.zip" workflows: version: 2 build: jobs: - deps64 - release64: requires: - deps64 - debug64: requires: - deps64 - deps32 - release32: requires: - deps32 - debug32: requires: - deps32 qTox/.clang-format000066400000000000000000000034461415623743500144050ustar00rootroot00000000000000--- # Language Language: Cpp Standard: Cpp11 # Indentation IndentWidth: 4 ContinuationIndentWidth: 4 AccessModifierOffset: -4 IndentCaseLabels: false NamespaceIndentation: None # Spacing UseTab: Never SpaceBeforeParens: ControlStatements SpacesBeforeTrailingComments: 1 SpaceInEmptyParentheses: false SpacesInAngles: false SpacesInParentheses: false SpacesInSquareBrackets: false SpacesInCStyleCastParentheses: false SpaceBeforeAssignmentOperators: true MaxEmptyLinesToKeep: 2 # Alignment AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true # Argument Packing BinPackArguments: true BinPackParameters: true # Break handling ColumnLimit: 100 BreakBeforeBraces: Mozilla BreakBeforeBinaryOperators: NonAssignment BreakConstructorInitializersBeforeComma: true AlwaysBreakTemplateDeclarations: true ConstructorInitializerAllOnOneLineOrOnePerLine: false Cpp11BracedListStyle: true # Break penalities PenaltyBreakBeforeFirstCallParameter: 200 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 5 PenaltyReturnTypeOnItsOwnLine: 60 # Includes SortIncludes: true IncludeCategories: # Match local headers - Regex: '^"[[:alnum:]_]+\.h"$' Priority: 1 # Match project headers - Regex: '^"[[:alnum:]_]+/.+\.h"$' Priority: 2 # Match Qt headers - Regex: '^$' Priority: 3 # Match other headers - Regex: '.*' Priority: 4 # Short blocks AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty # Set pointer format DerivePointerAlignment: false PointerAlignment: Left ... qTox/.clog.toml000066400000000000000000000001451415623743500137220ustar00rootroot00000000000000[clog] repository = "https://github.com/qTox/qTox" changelog = "CHANGELOG.md" from-latest-tag = true qTox/.gitattributes000066400000000000000000000003351415623743500147170ustar00rootroot00000000000000# Fix GitHub thinking our translations are TypeScript *.ts linguist-language=XML # fix bootstrap.sh on Windows MSYS *.sh eol=lf # Don't count the translation files in the language stats translations/* linguist-vendored qTox/.github/000077500000000000000000000000001415623743500133635ustar00rootroot00000000000000qTox/.github/ISSUE_TEMPLATE.md000066400000000000000000000014671415623743500161000ustar00rootroot00000000000000##### Brief Description OS: Windows / OS X / Linux (include version and/or distro) qTox version: (version numbers in menu `Settings → About`) Commit hash: toxcore: Qt: Hardware: … Reproducible: Always / Almost Always / Sometimes / Rarely / Couldn't Reproduce ##### Steps to reproduce 1. 2. 3. … ##### Observed Behavior ##### Expected Behavior ##### Additional Info (links, images, etc go here) ---- E.g., include log located in `~/.cache/Tox/qTox/` / `%APPDATA%\tox\`. To include it, rename it from `.log` to `.txt`, or zip it. Tip: use `v4l-info` and `v4l2-ctl --all` to get webcam info on Linux. More information on how to write good bug reports in the wiki: https://github.com/qTox/qTox/wiki/Writing-Useful-Bug-Reports. Please remove any unnecessary template section before submitting. qTox/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000002011415623743500171550ustar00rootroot00000000000000- [ ] Commits follow our [git commit guidelines](https://github.com/qTox/qTox/blob/master/CONTRIBUTING.md#git-commit-guidelines) qTox/.gitignore000066400000000000000000000010311415623743500140060ustar00rootroot00000000000000# Architecture specific extensions/prefixes *.[568vq] [568vq].out # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Doxygen doc/html/* # Executables qtox *.exe build-*-Release build-*-Profile build-*-Debug # Folders _[Bb]uild*/ _obj _test libs # Git *.orig # Go stuff (for Go OSX updater) *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.test # OSX .DS_Store /qtox.app # Qt *.user moc_* ui_* qrc_* Makefile *.qm /.qmake.stash # Rust target/ .cargo/ # Tarballs *.tar.* qTox/.travis.yml000066400000000000000000000232251415623743500141400ustar00rootroot00000000000000sudo: required dist: xenial language: cpp cache: ccache # regex is for master, release branches, and release tags branches: only: - master - /^v\d+\.\d+-dev$/ - /^v(\d+\.){2}\d+$/ stages: - Linux - name: "Windows Stage 1: Dependencies (OpenSSL, Qt)" if: type = push OR type = cron - name: "Windows Stage 2: Dependencies (other)" if: type = push OR type = cron - name: "Windows Stage 3: qTox" if: type = push OR type = cron env: global: - CIRP_GITHUB_REPO_SLUG="qTox/qTox-nightly-releases" jobs: include: - stage: Linux os: linux env: JOB=verify-commit-format script: "./.travis/$JOB.sh" - stage: Linux os: linux env: JOB=build-docs DOXYGEN_CONFIG_FILE=doxygen.conf script: "./.travis/$JOB.sh" - stage: Linux os: linux env: JOB=build-gitstats GITSTATS_DIR=gitstats addons: apt: packages: - gitstats script: "./.travis/$JOB.sh" # the actual compilin' - stage: Linux os: linux env: JOB=build-ubuntu-16-04 script: "./.travis/$JOB.sh" - stage: "Windows Stage 1: Dependencies (OpenSSL, Qt)" os: linux # Makes the cache this job creates avaiable only to jobs with WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=i686, # making sure that i686 and x86_64 caches don't overwrite each other env: WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=i686 script: ./.travis/build-windows.sh i686 release /opt/build-windows/i686 stage1 services: - docker cache: directories: - /opt/build-windows/i686 - stage: "Windows Stage 1: Dependencies (OpenSSL, Qt)" os: linux env: WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=x86_64 script: ./.travis/build-windows.sh x86_64 release /opt/build-windows/x86_64 stage1 services: - docker cache: directories: - /opt/build-windows/x86_64 - stage: "Windows Stage 2: Dependencies (other)" os: linux env: WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=i686 script: ./.travis/build-windows.sh i686 release /opt/build-windows/i686 stage2 services: - docker cache: directories: - /opt/build-windows/i686 - stage: "Windows Stage 2: Dependencies (other)" os: linux env: WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=x86_64 script: ./.travis/build-windows.sh x86_64 release /opt/build-windows/x86_64 stage2 services: - docker cache: directories: - /opt/build-windows/x86_64 - stage: "Windows Stage 3: qTox" os: linux env: WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=i686 script: - ./.travis/build-windows.sh i686 release /opt/build-windows/i686 stage3 - export ARTIFACTS_DIR="$(mktemp -d)" - cp -a ./workspace/i686/qtox/release/{setup-qtox-i686-release.exe,setup-qtox-i686-release.exe.sha256,qtox-i686-release.zip,qtox-i686-release.zip.sha256} "$ARTIFACTS_DIR" - .travis/cirp/cleanup1.sh - .travis/cirp/store.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup2.sh services: - docker cache: directories: - /opt/build-windows/i686 - stage: "Windows Stage 3: qTox" os: linux env: WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=x86_64 script: - ./.travis/build-windows.sh x86_64 release /opt/build-windows/x86_64 stage3 - export ARTIFACTS_DIR="$(mktemp -d)" - cp -a ./workspace/x86_64/qtox/release/{setup-qtox-x86_64-release.exe,setup-qtox-x86_64-release.exe.sha256,qtox-x86_64-release.zip,qtox-x86_64-release.zip.sha256} "$ARTIFACTS_DIR" - .travis/cirp/cleanup1.sh - .travis/cirp/store.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup2.sh services: - docker cache: directories: - /opt/build-windows/x86_64 - stage: "Windows Stage 3: qTox" os: linux env: WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=i686 script: - ./.travis/build-windows.sh i686 debug /opt/build-windows/i686 stage3 - export ARTIFACTS_DIR="$(mktemp -d)" - cp -a ./workspace/i686/qtox/debug/{qtox-i686-debug.zip,qtox-i686-debug.zip.sha256} "$ARTIFACTS_DIR" - .travis/cirp/cleanup1.sh - .travis/cirp/store.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup2.sh services: - docker cache: directories: - /opt/build-windows/i686 - stage: "Windows Stage 3: qTox" os: linux env: WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE=x86_64 script: - ./.travis/build-windows.sh x86_64 debug /opt/build-windows/x86_64 stage3 - export ARTIFACTS_DIR="$(mktemp -d)" - cp -a ./workspace/x86_64/qtox/debug/{qtox-x86_64-debug.zip,qtox-x86_64-debug.zip.sha256} "$ARTIFACTS_DIR" - .travis/cirp/cleanup1.sh - .travis/cirp/store.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup2.sh services: - docker cache: directories: - /opt/build-windows/x86_64 - stage: "macOS, AppImage and Flatpak" os: osx osx_image: xcode10.2 # macOS 10.14, oldest supported SDK according to https://developer.apple.com/support/xcode/ env: JOB=build-osx cache: timeout: 300 # seconds ccache: true directories: - $HOME/Library/Caches/Homebrew before_cache: - brew cleanup script: - "./.travis/$JOB.sh" - export ARTIFACTS_DIR="$(mktemp -d)" - cp -a ./{qTox.dmg,qTox.dmg.sha256} "$ARTIFACTS_DIR" - .travis/cirp/cleanup1.sh - .travis/cirp/store.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup2.sh - stage: "macOS, AppImage and Flatpak" os: linux env: JOB=APPIMAGE script: - ./appimage/build-appimage.sh - export ARTIFACTS_DIR="$(mktemp -d)" - cp -a ./output/* "$ARTIFACTS_DIR" - .travis/cirp/cleanup1.sh - .travis/cirp/store.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup2.sh services: - docker - stage: "macOS, AppImage and Flatpak" os: linux env: JOB=FLATPAK script: - ./flatpak/build-flatpak.sh - export ARTIFACTS_DIR="$(mktemp -d)" - cp -a ./output/* "$ARTIFACTS_DIR" - .travis/cirp/cleanup1.sh - .travis/cirp/store.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup2.sh services: - docker - stage: "Nightly publishing" if: type == cron script: - export ARTIFACTS_DIR="$(mktemp -d)" - .travis/cirp/collect.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup4.sh - .travis/cirp/publish.sh "$ARTIFACTS_DIR" - .travis/cirp/cleanup5.sh cache: directories: - /opt/cirp deploy: # Linux AppImage - provider: releases # This is the access token for pushing release builds on Tags # Owner: sudden6 # See https://docs.travis-ci.com/user/deployment/releases/#authenticating-with-an-oauth-token token: secure: "UAvVIUgfH8VGAzMsKk62yz26oqjNpUxEEyZ7OSjt09VoTMuepZd9wPmLCv0b+jFrk5BwQG9gpDzTD7vZ0Un1g1lGD23K801tvMRue/SO/KmOyeYrqd6o7MEFGb22qmmVoid/hqKLQFOxt/5HYj7fnCzG8EUuA9BvdWmsl4ir8Js=" file_glob: true file: ./output/* on: condition: $JOB == APPIMAGE repo: qTox/qTox tags: true skip_cleanup: true draft: true # Linux Flatpak - provider: releases token: secure: "UAvVIUgfH8VGAzMsKk62yz26oqjNpUxEEyZ7OSjt09VoTMuepZd9wPmLCv0b+jFrk5BwQG9gpDzTD7vZ0Un1g1lGD23K801tvMRue/SO/KmOyeYrqd6o7MEFGb22qmmVoid/hqKLQFOxt/5HYj7fnCzG8EUuA9BvdWmsl4ir8Js=" file_glob: true file: ./output/* on: condition: $JOB == FLATPAK repo: qTox/qTox tags: true skip_cleanup: true draft: true # osx binary - provider: releases token: secure: "UAvVIUgfH8VGAzMsKk62yz26oqjNpUxEEyZ7OSjt09VoTMuepZd9wPmLCv0b+jFrk5BwQG9gpDzTD7vZ0Un1g1lGD23K801tvMRue/SO/KmOyeYrqd6o7MEFGb22qmmVoid/hqKLQFOxt/5HYj7fnCzG8EUuA9BvdWmsl4ir8Js=" file: - "./qTox.dmg" - "./qTox.dmg.sha256" on: condition: $TRAVIS_OS_NAME == osx repo: qTox/qTox tags: true skip_cleanup: true draft: true # Windows Installer 64Bit - provider: releases token: secure: "UAvVIUgfH8VGAzMsKk62yz26oqjNpUxEEyZ7OSjt09VoTMuepZd9wPmLCv0b+jFrk5BwQG9gpDzTD7vZ0Un1g1lGD23K801tvMRue/SO/KmOyeYrqd6o7MEFGb22qmmVoid/hqKLQFOxt/5HYj7fnCzG8EUuA9BvdWmsl4ir8Js=" file: - "./workspace/x86_64/qtox/release/setup-qtox-x86_64-release.exe" - "./workspace/x86_64/qtox/release/setup-qtox-x86_64-release.exe.sha256" on: condition: $WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE == x86_64 && -f /home/travis/build/qTox/qTox/stage3 && -f /home/travis/build/qTox/qTox/release repo: qTox/qTox tags: true skip_cleanup: true draft: true # Windows Installer 32Bit - provider: releases token: secure: "UAvVIUgfH8VGAzMsKk62yz26oqjNpUxEEyZ7OSjt09VoTMuepZd9wPmLCv0b+jFrk5BwQG9gpDzTD7vZ0Un1g1lGD23K801tvMRue/SO/KmOyeYrqd6o7MEFGb22qmmVoid/hqKLQFOxt/5HYj7fnCzG8EUuA9BvdWmsl4ir8Js=" file: - "./workspace/i686/qtox/release/setup-qtox-i686-release.exe" - "./workspace/i686/qtox/release/setup-qtox-i686-release.exe.sha256" on: condition: $WINDOWS_BUILD_ARCH_CACHE_TRICK_VARIABLE == i686 && -f /home/travis/build/qTox/qTox/stage3 && -f /home/travis/build/qTox/qTox/release repo: qTox/qTox tags: true skip_cleanup: true draft: true after_success: - > test "$TRAVIS_PULL_REQUEST" == "false" && test "$TRAVIS_BRANCH" == "master" && test "$JOB" == "build-docs" && bash ./.travis/deploy-docs.sh - > test "$TRAVIS_PULL_REQUEST" == "false" && test "$TRAVIS_BRANCH" == "master" && test "$JOB" == "build-gitstats" && bash ./.travis/deploy-gitstats.sh qTox/.travis/000077500000000000000000000000001415623743500134115ustar00rootroot00000000000000qTox/.travis/build-docs.sh000077500000000000000000000023501415623743500157750ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2019 by The qTox Project Contributors # # This program is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Fail out on error set -eu -o pipefail # Obtain doxygen and its deps sudo apt-get update -qq sudo apt-get install doxygen graphviz # can fail due to travis cloning only `depth=50` GIT_DESC=$(git describe --tags 2>/dev/null || echo HEAD) GIT_CHASH=$(git rev-parse HEAD) # Append git version to doxygen version string echo "PROJECT_NUMBER = \"Version: $GIT_DESC | Commit: $GIT_CHASH\"" >> "$DOXYGEN_CONFIG_FILE" # Generate documentation echo "Generating documentation..." echo doxygen "$DOXYGEN_CONFIG_FILE" qTox/.travis/build-gitstats.sh000077500000000000000000000032331415623743500167100ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2019 The qTox Project Contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Scripts for generating gitstats on travis # # Downloads current git repo, and builds its stats. # usage: # ./$script # Fail as soon as an error appears set -eu -o pipefail # need to download whole history, since travis does only a shallow clone get_repo() { git clone https://github.com/qTox/qTox.git } make_stats() { # workaround gitstats not supporting non-blocking IO correctly see # https://github.com/travis-ci/travis-ci/issues/4704#issuecomment-348435959 python -c 'import os,sys,fcntl;\ flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL);\ fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);' gitstats \ -c authors_top=1000 \ -c max_authors=100000 \ qTox \ "$GITSTATS_DIR" } # check if at least something has been generated verify_exists() { [[ -e "$GITSTATS_DIR/index.html" ]] } main() { get_repo make_stats verify_exists } main qTox/.travis/build-osx.sh000077500000000000000000000032601415623743500156570ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2019 by The qTox Project Contributors # # This program is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Fail out on error set -eu -o pipefail readonly BIN_NAME="qTox.dmg" # accelerate builds with ccache install_ccache() { # manually update even though `install` will already update, due to bug: # Please don't worry, you likely hit a bug auto-updating from an old version. # Rerun your command, everything is up-to-date and fine now. echo "Updating brew..." brew update echo "Installing ccache..." brew install ccache brew --cache } # Build OSX build() { bash ./osx/qTox-Mac-Deployer-ULTIMATE.sh -i bash ./osx/qTox-Mac-Deployer-ULTIMATE.sh -b bash ./osx/qTox-Mac-Deployer-ULTIMATE.sh -d bash ./osx/qTox-Mac-Deployer-ULTIMATE.sh -dmg } # check if binary was built check() { if [[ ! -s "$BIN_NAME" ]] then echo "There's no $BIN_NAME !" exit 1 fi } make_hash() { shasum -a 256 "$BIN_NAME" > "$BIN_NAME".sha256 } main() { install_ccache build check make_hash } main qTox/.travis/build-ubuntu-16-04.sh000077500000000000000000000122211415623743500170320ustar00rootroot00000000000000#!/bin/bash # # Copyright © 2015-2019 by The qTox Project Contributors # # This program is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # stop as soon as one of steps will fail set -e -o pipefail sudo apt-get update -qq # install needed Qt, OpenAL, opus, qrencode, GTK tray deps, sqlcipher # `--force-yes` since we don't care about GPG failing to work with short IDs sudo apt-get install -y --force-yes \ automake \ autotools-dev \ build-essential \ check \ checkinstall \ libexif-dev \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ libkdeui5 \ libopenal-dev \ libopus-dev \ libqrencode-dev \ libsqlcipher-dev \ libtool \ libvpx-dev \ libxss-dev qrencode \ qt5-default \ qttools5-dev-tools \ qttools5-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ pkg-config || yes # ffmpeg if [ ! -e "libs" ]; then mkdir libs; fi if [ ! -e "ffmpeg" ]; then mkdir ffmpeg; fi # cd libs/ export PREFIX_DIR="$PWD" # cd ../ffmpeg wget http://ffmpeg.org/releases/ffmpeg-2.8.5.tar.bz2 tar xf ffmpeg* cd ffmpeg* # enabled: # v4l2 -> webcam # x11grab_xcb -> screen grabbing # demuxers, decoders and parsers needed for webcams: # mjpeg, h264 CC="ccache $CC" CXX="ccache $CXX" ./configure --prefix="$PREFIX_DIR" \ --disable-avfilter \ --disable-avresample \ --disable-bzlib \ --disable-bsfs \ --disable-dct \ --disable-decoders \ --disable-demuxers \ --disable-doc \ --disable-dwt \ --disable-encoders \ --disable-faan \ --disable-fft \ --disable-filters \ --disable-iconv \ --disable-indevs \ --disable-lsp \ --disable-lzma \ --disable-lzo \ --disable-mdct \ --disable-muxers \ --disable-network \ --disable-outdevs \ --disable-parsers \ --disable-postproc \ --disable-programs \ --disable-protocols \ --disable-rdft \ --disable-sdl \ --disable-static \ --disable-swresample \ --disable-swscale-alpha \ --disable-vaapi \ --disable-vdpau \ --disable-xlib \ --disable-yasm \ --disable-zlib \ --enable-shared \ --enable-memalign-hack \ --enable-indev=v4l2 \ --enable-indev=x11grab_xcb \ --enable-demuxer=h264 \ --enable-demuxer=mjpeg \ --enable-parser=h264 \ --enable-parser=mjpeg \ --enable-decoder=h264 \ --enable-decoder=mjpeg CC="ccache $CC" CXX="ccache $CXX" make -j$(nproc) make install cd ../../ # libsodium git clone git://github.com/jedisct1/libsodium.git cd libsodium git checkout tags/1.0.18 ./autogen.sh CC="ccache $CC" CXX="ccache $CXX" ./configure CC="ccache $CC" CXX="ccache $CXX" make -j$(nproc) sudo checkinstall --install --pkgname libsodium --pkgversion 1.0.8 --nodoc -y sudo ldconfig cd .. # toxcore git clone --branch v0.2.13 --depth=1 https://github.com/toktok/c-toxcore.git toxcore cd toxcore autoreconf -if CC="ccache $CC" CXX="ccache $CXX" ./configure CC="ccache $CC" CXX="ccache $CXX" make -j$(nproc) > /dev/null sudo make install echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf sudo ldconfig cd .. # filteraudio git clone --branch v0.0.1 --depth=1 https://github.com/irungentoo/filter_audio filteraudio cd filteraudio CC="ccache $CC" CXX="ccache $CXX" sudo make install -j$(nproc) sudo ldconfig cd .. $CC --version $CXX --version # needed, otherwise ffmpeg doesn't get detected export PKG_CONFIG_PATH="$PWD/libs/lib/pkgconfig" build_qtox() { bdir() { cd $BUILDDIR make -j$(nproc) # check if `qtox` file has been made, is non-empty and is an executable [[ -s qtox ]] && [[ -x qtox ]] cd - } local BUILDDIR=_build # first build qTox without support for optional dependencies echo '*** BUILDING "MINIMAL" VERSION ***' cmake -H. -B"$BUILDDIR" \ -DSMILEYS=DISABLED \ -DENABLE_STATUSNOTIFIER=False \ -DENABLE_GTK_SYSTRAY=False \ -DSTRICT_OPTIONS=ON \ -DSPELL_CHECK=OFF bdir # clean it up, and build normal version rm -rf "$BUILDDIR" echo '*** BUILDING "FULL" VERSION ***' cmake -H. -B"$BUILDDIR" \ -DUPDATE_CHECK=ON \ -DSTRICT_OPTIONS=ON bdir } test_qtox() { local BUILDDIR=_build cd $BUILDDIR make test cd - } # CMake is supposed to process files, e.g. ones with versions. # Check whether those changes have been committed. check_if_differs() { echo "Checking whether files processed by CMake have been committed..." echo "" # ↓ `0` exit status only if there are no changes git diff --exit-code } main() { build_qtox test_qtox check_if_differs } main qTox/.travis/build-windows.sh000077500000000000000000000122501415623743500165370ustar00rootroot00000000000000#!/usr/bin/env bash # MIT License # # Copyright (c) 2017 Maxim Biro # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # Fail out on error set -exuo pipefail readonly ARCH="$1" readonly BUILD_TYPE="$2" readonly CACHE_DIR="$3" readonly STAGE="$4" if [ -z "$ARCH" ] then echo "Error: No architecture was specified. Please specify either 'i686' or 'x86_64', case sensitive, as the first argument to the script." exit 1 fi if [[ "$ARCH" != "i686" ]] && [[ "$ARCH" != "x86_64" ]] then echo "Error: Incorrect architecture was specified. Please specify either 'i686' or 'x86_64', case sensitive, as the first argument to the script." exit 1 fi if [ -z "$BUILD_TYPE" ] then echo "Error: No build type was specified. Please specify either 'release' or 'debug', case sensitive, as the second argument to the script." exit 1 fi if [[ "$BUILD_TYPE" != "release" ]] && [[ "$BUILD_TYPE" != "debug" ]] then echo "Error: Incorrect build type was specified. Please specify either 'release' or 'debug', case sensitive, as the second argument to the script." exit 1 fi if [ -z "$CACHE_DIR" ] then echo "Error: No cache directory path was specified. Please specify absolute path to the cache directory as the third argument to the script." exit 1 fi if [ -z "$STAGE" ] then echo "Error: No stage was specified. Please specify either 'stage1', 'stage2' or 'stage3' as the fourth argument to the script." exit 1 fi if [[ "$STAGE" != "stage1" ]] && [[ "$STAGE" != "stage2" ]] && [[ "$STAGE" != "stage3" ]] then echo "Error: Incorrect stage was specified. Please specify either 'stage1', 'stage2' or 'stage3', case sensitive, as the fourth argument to the script." exit 1 fi # make the build stage visible to the deploy process touch "$STAGE" # make the build type visible to the deploy process touch "$BUILD_TYPE" # for debugging of the stage files echo $PWD # Just make sure those exist, makes logic easier mkdir -p "$CACHE_DIR" touch "$CACHE_DIR"/hash mkdir -p workspace/"$ARCH"/dep-cache # Purely for debugging ls -lbh "$CACHE_DIR" # If build.sh has changed, i.e. its hash doesn't match the previously stored one, and it's Stage 1 # Then we want to rebuild everything from scratch if [ "`cat $CACHE_DIR/hash`" != "`sha256sum windows/cross-compile/build.sh`" ] && [ "$STAGE" == "stage1" ] then # Clear the cache, removing all the pre-built dependencies rm -rf "$CACHE_DIR"/* touch "$CACHE_DIR"/hash else # Copy over all pre-built dependencies cp -dR "$CACHE_DIR"/* workspace/"$ARCH"/dep-cache fi # Purely for debugging ls -lbh "$CACHE_DIR" # Purely for debugging ls -lbh "$PWD" sudo apt-get update -qq # even though we're building in docker, libseccomp2 is used by docker, and needs to be up to date # to support functionality used by Qt's configure sudo apt-get install libseccomp2 -y --force-yes # Build sudo docker run --rm \ -v "$PWD/workspace":/workspace \ -v "$PWD":/qtox \ -e TRAVIS_CI_STAGE="$STAGE" \ debian:buster-slim \ /bin/bash /qtox/windows/cross-compile/build.sh "$ARCH" "$BUILD_TYPE" # Purely for debugging ls -lbh workspace/"$ARCH"/dep-cache/ # If we were building deps and it's any of the dependency building stages (Stage 1 or 2), copy over all the built dependencies to Travis cache if [ "`cat $CACHE_DIR/hash`" != "`sha256sum windows/cross-compile/build.sh`" ] && ( [ "$STAGE" == "stage1" ] || [ "$STAGE" == "stage2" ] ) then # Clear out the cache rm -rf "$CACHE_DIR"/* touch "$CACHE_DIR"/hash cp -dR workspace/"$ARCH"/dep-cache/* "$CACHE_DIR" fi # Update the hash if [ "`cat $CACHE_DIR/hash`" != "`sha256sum windows/cross-compile/build.sh`" ] && [ "$STAGE" == "stage2" ] then sha256sum windows/cross-compile/build.sh > "$CACHE_DIR"/hash fi # Generate checksum files for releases if [ "$STAGE" == "stage3" ] then readonly OUT_DIR=./workspace/"$ARCH"/qtox/"$BUILD_TYPE"/ if [ "$BUILD_TYPE" == "release" ] then NAME=setup-qtox-"$ARCH"-"$BUILD_TYPE".exe sha256sum "$OUT_DIR""$NAME" > "$OUT_DIR""$NAME".sha256 fi NAME=qtox-"$ARCH"-"$BUILD_TYPE".zip sha256sum "$OUT_DIR""$NAME" > "$OUT_DIR""$NAME".sha256 fi # Purely for debugging touch "$CACHE_DIR"/"$STAGE" ls -lbh "$CACHE_DIR" qTox/.travis/cirp/000077500000000000000000000000001415623743500143465ustar00rootroot00000000000000qTox/.travis/cirp/check_cache.sh000066400000000000000000000004221415623743500171000ustar00rootroot00000000000000#!/usr/bin/env bash if [ -f "/opt/cirp/previous_runs_commit" ] && [ "$(cat /opt/cirp/previous_runs_commit)" == "$(git rev-parse HEAD)" ] then # No new commits in the repo touch /opt/cirp/previous_runs_commit git log -1 echo "No new commits in the repo" exit 0 fi qTox/.travis/cirp/check_precondition.sh000066400000000000000000000004531415623743500205360ustar00rootroot00000000000000#!/usr/bin/env bash if [ ! -z "$TRAVIS_EVENT_TYPE" ] && [ "$TRAVIS_EVENT_TYPE" != "cron" ] then echo "Skipping publishing in a non-cron build" exit 0 fi if [ ! -z "$TRAVIS_PULL_REQUEST" ] && [ "$TRAVIS_PULL_REQUEST" != "false" ] then echo "Skipping publishing in a Pull Request" exit 0 fi qTox/.travis/cirp/cleanup1.sh000077500000000000000000000004401415623743500164130ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail . .travis/cirp/check_precondition.sh . .travis/cirp/install.sh ci-release-publisher cleanup_publish ci-release-publisher cleanup_store --scope current-job previous-finished-builds \ --release complete incomplete qTox/.travis/cirp/cleanup2.sh000077500000000000000000000004261415623743500164200ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail . .travis/cirp/check_precondition.sh . .travis/cirp/install.sh ci-release-publisher cleanup_store --scope current-build \ --release complete \ --on-nonallowed-failure qTox/.travis/cirp/cleanup3.sh000077500000000000000000000005371415623743500164240ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail . .travis/cirp/check_precondition.sh . .travis/cirp/install.sh ci-release-publisher cleanup_publish ci-release-publisher cleanup_store --scope current-build previous-finished-builds \ --release complete incomplete \ --on-nonallowed-failure qTox/.travis/cirp/cleanup4.sh000077500000000000000000000004421415623743500164200ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail . .travis/cirp/check_precondition.sh . .travis/cirp/install.sh ci-release-publisher cleanup_publish ci-release-publisher cleanup_store --scope current-build previous-finished-builds \ --release complete incomplete qTox/.travis/cirp/cleanup5.sh000077500000000000000000000006511415623743500164230ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail . .travis/cirp/check_precondition.sh if [ -z "$TRAVIS_TEST_RESULT" ] && [ "$TRAVIS_TEST_RESULT" == "0" ] then echo "Build has not failed, skipping cleanup" exit 0 fi . .travis/cirp/install.sh ci-release-publisher cleanup_publish ci-release-publisher cleanup_store --scope current-build previous-finished-builds \ --release complete incomplete qTox/.travis/cirp/collect.sh000077500000000000000000000007171415623743500163370ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail . .travis/cirp/check_precondition.sh if [ ! -z "$TRAVIS_TEST_RESULT" ] && [ "$TRAVIS_TEST_RESULT" != "0" ] then echo "Build has failed, skipping publishing" exit 0 fi if [ "$#" != "1" ] then echo "Error: No arguments provided. Please specify a directory where to download artifacts to as the first argument." exit 1 fi ARTIFACTS_DIR="$1" . .travis/cirp/install.sh ci-release-publisher collect "$ARTIFACTS_DIR" qTox/.travis/cirp/install.sh000066400000000000000000000026531415623743500163560ustar00rootroot00000000000000#!/usr/bin/env bash # Install verifying the hash # Get Python >=3.5 if [ "$TRAVIS_OS_NAME" == "osx" ] then brew update # make sha256sum available export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH" brew upgrade python || true pip3 install virtualenv || true python --version || true python3 --version || true pyenv versions || true cd . cd "$(mktemp -d)" virtualenv env -p python3 set +u source env/bin/activate set -u cd - else python --version || true python3 --version || true pyenv versions || true pyenv global $(pyenv versions | grep -o ' 3\.[5-99]\.[1-99]' | tail -n1) fi pip install --upgrade pip check_sha256() { if ! ( echo "$1 $2" | sha256sum -c --status - ) then echo "Error: sha256 of $2 doesn't match the known one." echo "Expected: $1 $2" echo -n "Got: " sha256sum "$2" exit 1 else echo "sha256 matches the expected one: $1" fi } # Don't install again if already installed. # OSX keeps re-installing it tough, as it uses a temp per-script virtualenv. if ! pip list --format=columns | grep '^ci-release-publisher ' then cd . cd "$(mktemp -d)" VERSION="0.2.0" FILENAME="ci_release_publisher-$VERSION-py3-none-any.whl" HASH="da7f139e90c57fb64ed2eb83c883ad6434d7c0598c843f7be7b572377bed4bc4" pip download ci_release_publisher==$VERSION check_sha256 "$HASH" "$FILENAME" pip install --no-index --find-links "$PWD" "$FILENAME" cd - fi qTox/.travis/cirp/publish.sh000077500000000000000000000015431415623743500163560ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail . .travis/cirp/check_precondition.sh if [ ! -z "$TRAVIS_TEST_RESULT" ] && [ "$TRAVIS_TEST_RESULT" != "0" ] then echo "Build has failed, skipping publishing" exit 0 fi if [ "$#" != "1" ] then echo "Error: No arguments provided. Please specify a directory containing artifacts as the first argument." exit 1 fi ARTIFACTS_DIR="$1" . .travis/cirp/check_cache.sh . .travis/cirp/install.sh ci-release-publisher publish --latest-release \ --latest-release-prerelease \ --latest-release-check-event-type cron \ --numbered-release \ --numbered-release-keep-count 3 \ --numbered-release-prerelease \ "$ARTIFACTS_DIR" . .travis/cirp/update_cache.sh qTox/.travis/cirp/store.sh000077500000000000000000000007031415623743500160410ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail . .travis/cirp/check_precondition.sh if [ ! -z "$TRAVIS_TEST_RESULT" ] && [ "$TRAVIS_TEST_RESULT" != "0" ] then echo "Build has failed, skipping publishing" exit 0 fi if [ "$#" != "1" ] then echo "Error: No arguments provided. Please specify a directory containing artifacts as the first argument." exit 1 fi ARTIFACTS_DIR="$1" . .travis/cirp/install.sh ci-release-publisher store "$ARTIFACTS_DIR" qTox/.travis/cirp/update_cache.sh000066400000000000000000000003531415623743500173100ustar00rootroot00000000000000#!/usr/bin/env bash if [ ! -z "$TRAVIS_TEST_RESULT" ] && [ "$TRAVIS_TEST_RESULT" != "0" ] then echo "Build has failed, skipping updating the cache" exit 0 fi mkdir -p /opt/cirp git rev-parse HEAD > /opt/cirp/previous_runs_commit qTox/.travis/deploy-docs.sh000077500000000000000000000031031415623743500161670ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2019 by The qTox Project Contributors # # This program is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Fail out on error set -eu -o pipefail # Extract html documentation directory from doxygen configuration OUTPUT_DIR_CFG=( $(grep 'OUTPUT_DIRECTORY' "$DOXYGEN_CONFIG_FILE") ) HTML_OUTPUT_CFG=( $(grep 'HTML_OUTPUT' "$DOXYGEN_CONFIG_FILE") ) DOCS_DIR="./${OUTPUT_DIR_CFG[2]}/${HTML_OUTPUT_CFG[2]}/" # Ensure docs exists if [ ! -d "$DOCS_DIR" ] then echo "Docs deploy failing, no $DOCS_DIR present." exit 1 fi # Obtain git commit hash from HEAD GIT_CHASH=$(git rev-parse HEAD) # Push generated doxygen to GitHub pages cd "$DOCS_DIR" git init --quiet git config user.name "Travis CI" git config user.email "qTox@users.noreply.github.com" git add . git commit --quiet -m "Deploy to GH pages from commit: $GIT_CHASH" echo "Pushing to GH pages..." git push --force --quiet "https://${GH_TOKEN}@github.com/qTox/doxygen.git" master:gh-pages &> /dev/null qTox/.travis/deploy-gitstats.sh000077500000000000000000000021341415623743500171040ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2019 The qTox Project Contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . cd "$GITSTATS_DIR" COMMIT=$(cd qTox && git describe) git init --quiet git config user.name "Travis CI" git config user.email "qTox@users.noreply.github.com" git add . git commit --quiet -m "Deploy to GH pages from commit: $COMMIT" echo "Pushing to GH pages..." git push --force --quiet "https://${GH_TOKEN_GITSTATS}@github.com/qTox/gitstats.git" master:gh-pages &> /dev/null qTox/.travis/verify-commit-format.sh000077500000000000000000000015431415623743500200330ustar00rootroot00000000000000#!/bin/bash # # Copyright © 2016-2019 by The qTox Project Contributors # # This program is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Fail out on error set -eu -o pipefail # Verify commit messages bash ./verify-commit-messages.sh "$TRAVIS_COMMIT_RANGE" qTox/.weblate000066400000000000000000000001071415623743500134450ustar00rootroot00000000000000[weblate] url = https://hosted.weblate.org/api/ translation = tox/qtox qTox/CHANGELOG.md000066400000000000000000005571771415623743500136620ustar00rootroot00000000000000 ## v1.17.14 (2021-12-14) This release only updates dependency versions including toktok/c-toxcore to v0.2.13 to address CVE-2021-44847. No code changes. ## v1.17.3 (2020-11-22) #### Features * **osx:** Add support for macOS 10.16, remove support for macOS 10.13 ([238b2478](https://github.com/qTox/qTox/commit/238b24787e4f53a79086344050cb55edf2287e08)) * **windows:** * Build our own gdb ([3092107a](https://github.com/qTox/qTox/commit/3092107a134a772b93b36ba57a9c58ad8d0ea18b)) * Make sure no dlls are missing ([3e6bc9b1](https://github.com/qTox/qTox/commit/3e6bc9b146fd5cecac062dbd3b6b18b4c1323156)) * Link all Windows deps dynamically ([5219ebc1](https://github.com/qTox/qTox/commit/5219ebc1fb30486196d91959fb9c5898742a874b)) #### Bug Fixes * **build:** cache debug deps during 2nd stage ([2712bc68](https://github.com/qTox/qTox/commit/2712bc68a54f3756d41c6164f72425f82fd1bc97)) * **osx:** * Allow rebuild using build script ([b5538c3f](https://github.com/qTox/qTox/commit/b5538c3f46a32e299c0bcc985ae9e703a1568344)) * Fix missing variables in macOS build script ([f741ac99](https://github.com/qTox/qTox/commit/f741ac99e65efb453a5c437502ae280bf637dd47)) * Update deprecated QDateTime and QProcess APIs ([08abedb6](https://github.com/qTox/qTox/commit/08abedb65513f88699244951889771573a8db588)) * don't tap kde-mac/kde since it us deprecated and unused ([b42ac760](https://github.com/qTox/qTox/commit/b42ac76011d9e65b10b6b9c27998f35df90b3dfa)) * **tools:** * Add Windows to toxcore version update script ([3ff53e8f](https://github.com/qTox/qTox/commit/3ff53e8ff685f67087f40cda4dfc26f49b91cc09)) * Use correct hash when automatically updating flatpak version ([9fb96b08](https://github.com/qTox/qTox/commit/9fb96b08c368e3b7bb6052d6d490a8c66e46877d)) * use a subdirectory for release source archives ([533f25e2](https://github.com/qTox/qTox/commit/533f25e25a37fc9c348c8dc03f1c6c9249a8d8b5)) * **travis:** Install virtualenv for nightly build upload script ([bd5bdf18](https://github.com/qTox/qTox/commit/bd5bdf183159f8bcbc02ae1e9023c3655c52efa1)) * **windows:** iconengines not being installed ([399c0231](https://github.com/qTox/qTox/commit/399c023131415b515e7390aa945c9c7ec0fbd9e5)) ## v1.17.2 (2020-04-26) This is a release to fix our automatic deployment scripts, no code changes. ## v1.17.1 (2020-04-22) #### Bug Fixes * **ci:** remove jenkins deploy step ([48c688bf](https://github.com/qTox/qTox/commit/48c688bf1b1939f1afb239e19764791e4fc98b7a)) ## v1.17.0 (2020-04-19) Since this is the first release in more than a year the changelog is quite huge. The most notable user facing changes are group chats that stay intact after disconnects or client shutdown as well as the very often requested dark theme. Advanced history search was added, as well as file transfers staying in chat history after client restart. There are of course also tons of fixes, namely much increased stability of audio and video calls as well as audio group calls. For v1.17.0 binaries, see v1.17.1. #### Performance * **history:** enable sql index on chat_id in history table ([edd72906](https://github.com/qTox/qTox/commit/edd72906fbd4ff7e8e48ebcd7fed764baf8cd85b)) * **smileys:** * create global regex object ([0f90abeb](https://github.com/qTox/qTox/commit/0f90abebddeb99b133e415e9a1e015eb08197690)) * use one big regex instead of constructing many small ones ([58f8a14a](https://github.com/qTox/qTox/commit/58f8a14a48b7a023def122aff2e72b08b38c10de)) #### Features * add Fcitx and Uim support to AppImage ([710c32de](https://github.com/qTox/qTox/commit/710c32ded0aad89d2dd14718d068f0146755296d)) * save selected search text after scrolling up ([dbf88007](https://github.com/qTox/qTox/commit/dbf880078e8b3207bf5c4f057bc6071b4c74b9ce)) * check chat status before start a search ([ce570927](https://github.com/qTox/qTox/commit/ce570927b145676ff3a63f36a3fe082fa52b228a)) * prohibition to remove messages in group chat ([5aeac56b](https://github.com/qTox/qTox/commit/5aeac56b761ad24b6a2829fca499b8eff480a306)) * remove part messages from chat ([4c7ecb60](https://github.com/qTox/qTox/commit/4c7ecb60247a0e0d84442e506ae5122204ffb328)) * edit position chat after load history ([c2d5b422](https://github.com/qTox/qTox/commit/c2d5b422b3ff09af329840dd829d9d2163b79e52)) * add action "Go to current date" ([2a9648d1](https://github.com/qTox/qTox/commit/2a9648d12c2f71efa8f9722f1c0fa6e39e701c47)) * edit load history in search ([8c4b1e00](https://github.com/qTox/qTox/commit/8c4b1e00a128b739904ed60543132b34817f0ba5)) * edit function "Load chat history" ([6de1173c](https://github.com/qTox/qTox/commit/6de1173c172a14aec3dba289dd63d5857fe69d19)) * load messages from the database after date ([b705ac80](https://github.com/qTox/qTox/commit/b705ac806059717d98cfd60b1b2f1abdaa84e6a9)) * load messages from the database before date ([fb2957c5](https://github.com/qTox/qTox/commit/fb2957c5ee9b0abffdc8c462e96be71c38d44949)) * add border for qrcode ([191f89ff](https://github.com/qTox/qTox/commit/191f89ffa4b01896a7ce94291f52fb482264eaa6)) * remove old boostrap nodes code ([acef759a](https://github.com/qTox/qTox/commit/acef759a586c38a314943555ee9e2949bbba90c5)) * load bootstrap nodes directly from JSON ([1f2bdf3a](https://github.com/qTox/qTox/commit/1f2bdf3a1b2e723989ae83b525b62b7a13dd610f)) * add color for links in palette ([d35dbcc8](https://github.com/qTox/qTox/commit/d35dbcc870d3f35c9ba075736f85ec27d7c84b42)) * edit reload themes ([e146c11f](https://github.com/qTox/qTox/commit/e146c11f0fbe52e91dda2267a54f83943768d410)) * show date in chat log ([d0e8ba8b](https://github.com/qTox/qTox/commit/d0e8ba8b9ca0de2ca9f5a847ea0c86cacd13833a)) * add class to retrieve bootstrap nodes from nodes.tox.chat ([c3363a1f](https://github.com/qTox/qTox/commit/c3363a1feab643eba488668e30d5afc8fedc5990)) * add message if text not found ([4253301c](https://github.com/qTox/qTox/commit/4253301c5606637c8b1beae082f922af10eb365a)) * Add spell checking ([671b9456](https://github.com/qTox/qTox/commit/671b9456a88c5ef12c6e29114eed4c708f845e69)) * add function for generating a filter for search word only ([17a97f1f](https://github.com/qTox/qTox/commit/17a97f1ff68af50588012bf3b9b31a61cdf2b194)) * add startButton in SearchForm ([8dd83477](https://github.com/qTox/qTox/commit/8dd83477591f9c357abba1892e1a7a6b9ca11af9)) * add functions for change title and info in LoadHistoryDialog ([3b7ba023](https://github.com/qTox/qTox/commit/3b7ba023242ba9e2a5340eb9c1b8ed69db710aef)) * use search settings ([610e04aa](https://github.com/qTox/qTox/commit/610e04aa2698deb24af2f04db5cf3e049101a54d)) * create widget for search settings ([87b340f4](https://github.com/qTox/qTox/commit/87b340f4a14b3544cdc2ea50c1256bca974ac967)) * **apparmor:** * Add AppArmor profile install scripts ([2e682c6e](https://github.com/qTox/qTox/commit/2e682c6e6a11d500854b09b61e271683c70c1b12)) * Add AppArmor v2.12.1 profile ([d6ef3d2e](https://github.com/qTox/qTox/commit/d6ef3d2eae072bea57d7e9fe913426b78f99b811)) * Add AppArmor profile ([89514eee](https://github.com/qTox/qTox/commit/89514eee6d9ba2495fe1d4fe845f0253601242c5)) * **build:** * use Debian Buster for Windows cross-compilation ([6bb2c7c6](https://github.com/qTox/qTox/commit/6bb2c7c62933b6f64351c932cfe869e30f858615)) * add the delta updater ([5eea8ba2](https://github.com/qTox/qTox/commit/5eea8ba27f6bbc89f534d1432e06c90a2b64e1b7)) * remove timestamps from build to allow reproducible builds ([013771c1](https://github.com/qTox/qTox/commit/013771c13a9e68a97d113cf268e52a4751cb4505)) * add option to enable AddressSanitizer ([fd99dfd0](https://github.com/qTox/qTox/commit/fd99dfd0a5989a5e5a92588c4b229951b9c6270d)) * update docs and tools to provide signed tar.gz ([7ff1d605](https://github.com/qTox/qTox/commit/7ff1d6053a57a29ee6fcc062feb32894c848f415)) * **chatfom:** make magnet links clickable ([5b1bc7e5](https://github.com/qTox/qTox/commit/5b1bc7e52323f6832e00a50c34f8143f4d7cbf33)) * **core:** * add send message error handling ([5289c999](https://github.com/qTox/qTox/commit/5289c99962b6be374e92163313bc620eef2cbd58)) * print a chat log entry when a user joins/leaves the group chat ([cabed6de](https://github.com/qTox/qTox/commit/cabed6def3d4b83b3c10de098bb0defa684583de)) * set group title for loaded groups ([8db744a5](https://github.com/qTox/qTox/commit/8db744a505fa3ebdb3afcc78661aa15b8586ad81)) * prepare qTox for groupchat saving ([a82eb6f3](https://github.com/qTox/qTox/commit/a82eb6f36e9e443a361de4e7efa04b92cc60be82)) * **coreav:** add assertions to check for threading errors ([8e54805e](https://github.com/qTox/qTox/commit/8e54805e7d13fe6b28c21b4ed48bc5addeae84d0)) * **db:** * File transfer history review comments ([25005c5c](https://github.com/qTox/qTox/commit/25005c5c19c80a3cbd2d966a2bb6bfeaa20eab36)) * add file hash to file history ([8427be66](https://github.com/qTox/qTox/commit/8427be6678eae2ef151b704feb6bf408038ccdd3)) * Hookup file history to the rest of the system ([d9b39b31](https://github.com/qTox/qTox/commit/d9b39b3102eff686a072630610c199639f0d8219)) * Database support for file history ([567ddfb2](https://github.com/qTox/qTox/commit/567ddfb2035dad1f3aad94789e76f8a4e4f77d07)) * Support schema version upgrades ([fb805b9c](https://github.com/qTox/qTox/commit/fb805b9cdb5f108b15da33aa0bd6c250aac7a8b1)) * **files:** Add maximum size to autoaccept downloads ([c8716e9c](https://github.com/qTox/qTox/commit/c8716e9c4541d696ae6aa04dd6eb48bfaf76f360)) * **groups:** * Allow being in group call if only member ([caf4f59f](https://github.com/qTox/qTox/commit/caf4f59fb2a771cce848b316673787940eadd76c)) * show who is in a group call before joining ([ec07fd72](https://github.com/qTox/qTox/commit/ec07fd7291f660f2dcc8cbb50972809eccb04b48)) * **login:** generate a GUI error when auto login fails ([74377430](https://github.com/qTox/qTox/commit/74377430ce83b4d1e10d52125fc5fb28d328c1c0)) * **notify:** * integrate desktop notifications into settings ([4cb00957](https://github.com/qTox/qTox/commit/4cb00957f39faa375e6ad6a454c6c3dd2a97b24b)) * add desktop notifications using snorenotify ([66e2c010](https://github.com/qTox/qTox/commit/66e2c01029efad7f6195cebcf96175152ce2ce22)) * **offlinemsg:** * Enable offline messages with no history ([2283d0c1](https://github.com/qTox/qTox/commit/2283d0c1b0221303197714cc2b6d90b3f0bb8144)) * Force offline messages to always be enabled ([d934cf37](https://github.com/qTox/qTox/commit/d934cf372b8c030de783cb57a1269224a5a88c4b)) * **paths:** create class to combine all qTox managed paths ([3ee8c665](https://github.com/qTox/qTox/commit/3ee8c665df68a22d3068804961ac46e2b793903b)) * **proxy:** provide commandline tools for proxy settings ([31fec748](https://github.com/qTox/qTox/commit/31fec7488f74dc2fe38f0a8515b415c21f3e2109)) * **settings:** save friend list sorting mode ([c8b156b3](https://github.com/qTox/qTox/commit/c8b156b3a1f756f34a0ef9229fd82033e2a5cee9)) * **themes:** make themes follow standard paths ([133ac8de](https://github.com/qTox/qTox/commit/133ac8def80a79b44899a0d781352ecfeef047c9)) * **travis:** publish nightly builds off Travis-CI ([516c52ad](https://github.com/qTox/qTox/commit/516c52ad207e84fa88606170fdf8d8e94872314c)) * **ui:** * add event icons for all statuses ([17048c9c](https://github.com/qTox/qTox/commit/17048c9cc4b3f9ee58924637e57573e285ffa8ee)) * add update notification enabled with -DUPDATE_CHECK ([6c9d7b59](https://github.com/qTox/qTox/commit/6c9d7b59c12a6fc79fc457f22e84060a9bcde940)) * Added feature to generate colors for user names in tox groups ([aaf5229e](https://github.com/qTox/qTox/commit/aaf5229ece69fb694381164f10bb85eb6fd53ee3)) * Add ui to setup spell checking ([8d10fe47](https://github.com/qTox/qTox/commit/8d10fe47ecf806ad2a8d238d97edd14142a10b42)) #### Breaking Changes * **groups:** Fix invalid group list on group member join ([836718aa](https://github.com/qTox/qTox/commit/836718aa263039c0e1daef0ba75593a3d35b5cdc), closes [#5838](https://github.com/qTox/qTox/issues/5838), breaks [#](https://github.com/qTox/qTox/issues/)) * **status:** use enum as UI property instead of untranslated string ([881aa308](https://github.com/qTox/qTox/commit/881aa3083aac75153f8fcc9548d48951a01f8fd2), breaks [#](https://github.com/qTox/qTox/issues/)) #### Bug Fixes * add missing type for Qt slot ([b5785a1b](https://github.com/qTox/qTox/commit/b5785a1b0c03688f534b810482d08debbc66002c)) * remove unused variables ([7e4f7f04](https://github.com/qTox/qTox/commit/7e4f7f0489084cfbdf9a959d0d78566949cbf269)) * hide current date at the top of the chat ([bbbbc6aa](https://github.com/qTox/qTox/commit/bbbbc6aade1b32485888b1842db15f65167fbf81)) * scroll bar stuck to bottom (fix #5755) ([38df897e](https://github.com/qTox/qTox/commit/38df897e024393ba15c80017930dac19a92e13b7)) * remove reconnect button ([909deb0f](https://github.com/qTox/qTox/commit/909deb0febba2d122c7c6f2455c7cb85e83ee389)) * update workerStb ([177bf12f](https://github.com/qTox/qTox/commit/177bf12f1150a842e5263eb573fe110fb717ea5a)) * data validation during the search (fix: #5791, #5723) ([acb91ed7](https://github.com/qTox/qTox/commit/acb91ed731bece1fbdac90064446c61ab68b721f)) * empty username causes mention on ever message ([db802822](https://github.com/qTox/qTox/commit/db802822f3b2625ffa19d491d786bdb72666da49)) * check time for get num messages for friend (fix #5714) ([b0f32379](https://github.com/qTox/qTox/commit/b0f32379d098cf525bcbe003d7add071c708bd69)) * Call 'tr' in place, where text is accessible ([15d72a96](https://github.com/qTox/qTox/commit/15d72a9610bc6e4e2e93a9c36ee6536b0aa13429)) * register RowId meta type for use in fileInserted signal ([18b52ce5](https://github.com/qTox/qTox/commit/18b52ce56841f682bf193ad5820c55a62990aa2b)) * initialization theme ([d517c3a6](https://github.com/qTox/qTox/commit/d517c3a638a9355ba8a799fd00119a56a3e9c88d)) * fix uninitialized variable ([ea18b613](https://github.com/qTox/qTox/commit/ea18b613ba4157529af2e1e888bd93d73d7ec7be)) * fix uninitialized variable ([52f0e2db](https://github.com/qTox/qTox/commit/52f0e2db9417af76eedf133f4435bc926bf659b1)) * Store groupId in D&D mime data ([8499a710](https://github.com/qTox/qTox/commit/8499a710929e045be8f031b25f44e19fed5d35f8)) * check valid date in getDateWhereFindPhrase ([c3095ab1](https://github.com/qTox/qTox/commit/c3095ab150a360cfc1787d84ab3c6a503cc31c9e)) * (chatform): Issue 5115, use QSharedPointer for groupChatForms ([37e5b6ce](https://github.com/qTox/qTox/commit/37e5b6ce8ba572b0f6e3dc067b7f7c99963515b7)) * fix memory leak in CoreAV and missuse of std::uniqe_ptr ([d776e6c3](https://github.com/qTox/qTox/commit/d776e6c34f495dfec445a55dd16ff71b2f0b7af5)) * add remove history prompt, clear log area after remove ([e6d40be7](https://github.com/qTox/qTox/commit/e6d40be72eff546f07b30a2ad33dc95fc8a29362)) * show the date every new day (fixes #5280) ([8808c630](https://github.com/qTox/qTox/commit/8808c630f25f72ebb9974fa2786d83a9e11e49b8)) * correct format log files ([ee0d4bb8](https://github.com/qTox/qTox/commit/ee0d4bb880a986416ed9d0ca4c147adb0bfbc37a)) * **AVForm:** don't load gain when device not ready ([cef773c4](https://github.com/qTox/qTox/commit/cef773c4f69d079340f1379d4a7d787cea03acce)) * **CircleCI:** make cache depend on script files ([7bbbb737](https://github.com/qTox/qTox/commit/7bbbb7377a180fc3e277ecb0a02606beb072b76b)) * **TravisCI:** build windows for release tags too ([c53a58e6](https://github.com/qTox/qTox/commit/c53a58e64e737a206378d3a92a5282df0758d4e7)) * **UI:** update peer label's style after setting audio playing property ([47795073](https://github.com/qTox/qTox/commit/477950737ffe2e6350862f00aa644da4235c99fc)) * **activity:** change last activity time from QDate to QDateTime ([d55332ee](https://github.com/qTox/qTox/commit/d55332ee0a0fdda8f63c6ac49f4feb8762bbcc9d)) * **alias:** allow clearing alias from chatformheader ([dfec934f](https://github.com/qTox/qTox/commit/dfec934ff043d631b3a445f557454f0afecb981c)) * **apparmor:** * Make network rules more strict ([11a32e33](https://github.com/qTox/qTox/commit/11a32e337182df5161299e648dc9fdc2b41e47cc)) * Fix typo in file path ([4d9cc721](https://github.com/qTox/qTox/commit/4d9cc7216af59ebf9300d433ba3bfc4e2709977a)) * Fix screenshot capture under AppArmor ([5304ba4c](https://github.com/qTox/qTox/commit/5304ba4cb0de52008cc9f9b6ae3c6838fa54b717)) * Backport fix from dri-common abstraction ([f6c11c9b](https://github.com/qTox/qTox/commit/f6c11c9b6d2f6412ca5204a491e22cc12decab5b)) * Fix openSUSE-related AppArmor denies ([7a1fb927](https://github.com/qTox/qTox/commit/7a1fb927ec4e4d74e5381aab40d57228c072fade)) * Fix font-related denies on openSUSE ([488b8a86](https://github.com/qTox/qTox/commit/488b8a869628d142e87663e36dc9e86981016d96)) * fix file dialog denies ([4565ac1b](https://github.com/qTox/qTox/commit/4565ac1b19903023d6d71043b37f120977e9b451)) * fix file dialog on KDE desktop ([dffe00b4](https://github.com/qTox/qTox/commit/dffe00b4e3ddcef5a56ec37fd3d4fcfd847c2704)) * backport kde abstraction ([e1ba972d](https://github.com/qTox/qTox/commit/e1ba972d8bfa2e1c1b98bd73417d783b4abd2077)) * Fix spam of DENIED messages on openSUSE ([c8eb34f0](https://github.com/qTox/qTox/commit/c8eb34f028e2051cc83b877c65799c289c6dc21e)) * Fix DBUS denies on Kubuntu 18.04 ([1d120b15](https://github.com/qTox/qTox/commit/1d120b15c27706bfe3b4b4d8ad911db37b33ef41)) * Backport dri-enumerate abstraction ([79f800b3](https://github.com/qTox/qTox/commit/79f800b39a3b0b12faf4959429104e14ecf881e5)) * Fix .local/share/qTox/ access ([e13b8a97](https://github.com/qTox/qTox/commit/e13b8a973eb8be8252a21feb066bd1853e9002a0)) * Fix access to openssl configuration ([514cd368](https://github.com/qTox/qTox/commit/514cd368264fdacdf36013ffb496910475506422)) * Fix dbus access ([a6c01eb0](https://github.com/qTox/qTox/commit/a6c01eb00713c21d8bc64cba47e511b0b5737908)) * Fix hunspell access ([577aeb8f](https://github.com/qTox/qTox/commit/577aeb8fa36b54d95f5b68b6c1533b170105e6ed)) * Fix accessibility DBus access ([a67faf29](https://github.com/qTox/qTox/commit/a67faf2976d2b8bcb67ae2feceb3772e3d3266fd)) * Fix qTox cache access ([aef47056](https://github.com/qTox/qTox/commit/aef47056367642d842e090d2362698dfcf9cc06a)) * Add ibus abstraction ([9fc89338](https://github.com/qTox/qTox/commit/9fc89338830a1c208b79991b9b2451f059827265)) * Backport qt5 abstraction for v2.12.1 profile ([6aa4435d](https://github.com/qTox/qTox/commit/6aa4435d174e71d4a788bbb27514387a34d1c6b9)) * Fix loading libraries from custom install prefix ([5fad77b9](https://github.com/qTox/qTox/commit/5fad77b9f8813d87b97d4b1e8a7d862e2deb13cf)) * Fix AppArmor profile for version 2.12.1 ([f8f7a2d1](https://github.com/qTox/qTox/commit/f8f7a2d14554919b424929403fb74f665c50a6fd)) * **audio:** * correctly loop incoming call sound ([85f90ab2](https://github.com/qTox/qTox/commit/85f90ab2219b3c6bd5e9eaf3b948ad56016e6aaa), closes [#5680](https://github.com/qTox/qTox/issues/5680)) * input device not closed under certain circumstances ([80f5cb5f](https://github.com/qTox/qTox/commit/80f5cb5f7a275f2dc498e8f3fe3a84d702133789)) * specify format for sounds and make sounds follow it ([5d65ab38](https://github.com/qTox/qTox/commit/5d65ab387631cf76adfc36f3f0bf356ec7fd6f7b)) * **autocomplete:** don't auto-complete own nick ([f188409b](https://github.com/qTox/qTox/commit/f188409b8ce85515fc3b45a2aedb3af79af2b009)) * **avatar:** * reject avatars that are larger than 64KB ([6e2ac12d](https://github.com/qTox/qTox/commit/6e2ac12d84df141b85b427765a74a704146d5b70)) * set friend to identicon on empty avatar receipt ([0c757356](https://github.com/qTox/qTox/commit/0c757356d3efe79438fdbe232e5c4b0354b7a740), closes [#4724](https://github.com/qTox/qTox/issues/4724)) * **blocked:** change blocked icon to match other used icons ([ff5c9052](https://github.com/qTox/qTox/commit/ff5c90525de6ee7e0b1c6ea6d27cc203f81c5aea)) * **build:** * disable Werror by default, keep enabled on CI ([9888bc2d](https://github.com/qTox/qTox/commit/9888bc2d8072f0360a10ba32b6a5834668f0349f)) * appease appstream-glib validate ([98a364ea](https://github.com/qTox/qTox/commit/98a364ea2e13041f9c94dc9b70ed626226383683)) * fix OpenAL Soft failing to build with newer CMake ([2419b627](https://github.com/qTox/qTox/commit/2419b6276282d4ccc8f9dd26592872318684cced)) * fix SQLCipher build issue with OpenSSL 1.1.1 on Windows ([8be99c08](https://github.com/qTox/qTox/commit/8be99c0861a1e9864a27d01b6d2b5ab8a38c578f)) * fix cmake command in appimage/build.sh ([e9665d51](https://github.com/qTox/qTox/commit/e9665d517aada920d81e57b9a5370bfb22773fbd)) * install and use AppImageUpdaterBridge ([837416b9](https://github.com/qTox/qTox/commit/837416b9039252720295a58568e36be50e36c3f5)) * bundle missing libjack.so* to work with Fedora Workstation ([e50b3bbd](https://github.com/qTox/qTox/commit/e50b3bbd5c2e25a7d0dad8e7a374f785b57a172f)) * copy OpenSSL libs to AppDir ([7153c1f2](https://github.com/qTox/qTox/commit/7153c1f2bf27c999be125fb136ab6383305eb875)) * add required private slots ([a9a953cf](https://github.com/qTox/qTox/commit/a9a953cf1cdd8a6142081b4ecf54060ce6ca993b)) * fix ifdefs ([d23e4fd7](https://github.com/qTox/qTox/commit/d23e4fd7095ed07d7d0173bab0edb90bdb799f7d)) * fixup CircleCI 2.0 config ([9caeb943](https://github.com/qTox/qTox/commit/9caeb943616c3092f26b9b1fc2f42a971f8a85c3)) * show brew compilation progress to avoid being killed by Travis ([fd709722](https://github.com/qTox/qTox/commit/fd7097222d48304e3e509d20488eb158eef9c493)) * manually update brew to workaround autoupdate bug ([c9cb5fef](https://github.com/qTox/qTox/commit/c9cb5fefdd34a431d8f65caf477117f043365d2a)) * add cmake_policy for CMP0071 to suppress warinings in cmake ([6a240141](https://github.com/qTox/qTox/commit/6a240141eafbc5304bc0fcc572d3b7f8e2eb6f5b)) * fix default cmake build not actually being debug ([edb306c7](https://github.com/qTox/qTox/commit/edb306c723cbbf4e7aa18fd4f08446ecf6053718)) * **calls:** Fix SIGSEGV on quit while in call ([87eed97a](https://github.com/qTox/qTox/commit/87eed97ab3daf432a7ed215f8dbeca95ce265dff)) * **chatform:** * Prevent date line oscillations maxing CPU ([64bae38b](https://github.com/qTox/qTox/commit/64bae38b999ee4ddd955695300beb1e5bb20446f), closes [#5620](https://github.com/qTox/qTox/issues/5620)) * don't try to send empty action messages ([89e94b6f](https://github.com/qTox/qTox/commit/89e94b6f895c8c57f4541147b6e56ec4d30adaac)) * fix stuck spinner on messages not at end of chatform ([9819aefc](https://github.com/qTox/qTox/commit/9819aefc44767220e696b9c0de0594a5992ee706)) * fix a crash when there are no messages to load ([040c6b95](https://github.com/qTox/qTox/commit/040c6b95aed26dfc7950770ed4dca379e8be2bc5)) * crash after opening chat in new window ([59a59fdc](https://github.com/qTox/qTox/commit/59a59fdc09ee5cbf32c915b94cb8ba8e8f0a29b1)) * add hack to avoid Qt bug on chat show ([e8d48e87](https://github.com/qTox/qTox/commit/e8d48e87888548c92daa88880ee7689d58b0c490)) * don't attempt to send messages to offline friends ([d9e587e4](https://github.com/qTox/qTox/commit/d9e587e4f5f85df979ed11fabfed443280716045)) * mark message with triple click ([2cdff7e9](https://github.com/qTox/qTox/commit/2cdff7e9ed5efd5402a7cb053bbf24cd29034927), closes [#5211](https://github.com/qTox/qTox/issues/5211)) * **chatlog:** * enable dynamic view range in chatlog with history disabled ([a7f34959](https://github.com/qTox/qTox/commit/a7f349595696b3292897ac72b5cffb05b7300eda)) * fix stick to bottom behavior ([f2fa6010](https://github.com/qTox/qTox/commit/f2fa601073373ae2ef9fba7952aed415af3ccee0)) * update precise selection when chatlog content changes ([b95bac23](https://github.com/qTox/qTox/commit/b95bac238d1caa40086f19b83dc20e0e9243719d)) * Prepare geometry changes on chatline proxy ([74d0b47c](https://github.com/qTox/qTox/commit/74d0b47ceb39c3c8962f77f8f6b3060476f64e67), closes [#5818](https://github.com/qTox/qTox/issues/5818)) * update multi line selection on chatlog change ([2bba1217](https://github.com/qTox/qTox/commit/2bba12175ee34b835c3888c391b3169f56d36bf7)) * update timestamp when db is slower than ack ([ca397aeb](https://github.com/qTox/qTox/commit/ca397aebbfb388df3754d36331d669c74d87a15c)) * **ci:** * quote shell comparison to avoid errors when unset ([475128d1](https://github.com/qTox/qTox/commit/475128d171984de5f9ff34f98020a119dce65b42)) * fix travis' window build in debian docker by upgrading libseccomp2 ([93c9eef5](https://github.com/qTox/qTox/commit/93c9eef51f8143c3e3e5c1e63e0cc64d1d533f6e)) * upgrade travis to xcode9.3 to get macos 10.13 with brew support ([6fb5b205](https://github.com/qTox/qTox/commit/6fb5b205f41d4e37d2d4964af2a99fc5abc937f6)) * increase bsu_test timeout ([81901696](https://github.com/qTox/qTox/commit/819016960d33664eb3b0af11c85dbe24c4277777)) * **core:** * message when peer changes group name ([0b5f7511](https://github.com/qTox/qTox/commit/0b5f751104664fb1d624910ed930ab1b05a7cc4f)) * avoid using stack allocated memory past end of scope ([9b23abc6](https://github.com/qTox/qTox/commit/9b23abc6dedf2ecba84920ef878335c9a76c592f)) * avoid logging which bootstrap node is being connected to ([731a4985](https://github.com/qTox/qTox/commit/731a49854a6b94b2b237048765e76f2695125181)) * don't log critical on every group join ([e4f73011](https://github.com/qTox/qTox/commit/e4f73011f9346332d27a8d59a1fbb07834c32edb)) * save tox profile when updating group or friend states ([3d7a872f](https://github.com/qTox/qTox/commit/3d7a872f1aeb2388e3a8f583fd7d494ee7f66185)) * don't set core status on connect, just update UI ([602671c9](https://github.com/qTox/qTox/commit/602671c9dcb1c9f42dd2d7fb9dfbadd729fb8efd)) * correctly relink ui/core when core is changed ([17d5d552](https://github.com/qTox/qTox/commit/17d5d55259ab9864c430df8bdc9b440a0315a59c), closes [#5710](https://github.com/qTox/qTox/issues/5710)) * if your username is empty, use toxPK instead in groups ([72bcc6ac](https://github.com/qTox/qTox/commit/72bcc6acaf22ef78c2db852a5cabf45f42e5af90)) * also print PKs in group userlist ([066bdc5c](https://github.com/qTox/qTox/commit/066bdc5cc1b0bbe05c35bb184f8b6981fd95e0ce)) * this should resolve message handling in persistent groups ([ee500703](https://github.com/qTox/qTox/commit/ee500703aab6418fde60ddd7237e6461fb5c7f55)) * fixed syntax ([7a437c74](https://github.com/qTox/qTox/commit/7a437c74493333bb77f18418059ff329dde55340)) * ignore mentioning users with empty nicknames ([96ad27f6](https://github.com/qTox/qTox/commit/96ad27f644fd151a608abdffe045ad215db28564)) * fix for users without nicknames ([2000bf55](https://github.com/qTox/qTox/commit/2000bf55f2f7e3c3573df8130e74850b1202b044)) * update group peerLists on local changes ([01f79b0d](https://github.com/qTox/qTox/commit/01f79b0d084176c4b6deaa289060f60652bf7428)) * support user aliases ([feee0e76](https://github.com/qTox/qTox/commit/feee0e767ffa15b2145f82d16da9c47c2d2f580e)) * this fixes displaying nickname refreshes in groups ([9b261fd8](https://github.com/qTox/qTox/commit/9b261fd870f8a240cdf93c5acc2235efaa7a8116)) * fix formatting ([c136a17f](https://github.com/qTox/qTox/commit/c136a17ff0f1eb29b7e9e3b56b0fb8f036ab0fbb)) * simplify the code ([8c239c8e](https://github.com/qTox/qTox/commit/8c239c8ef6d47177cabfcfca79783d188ac9c76b)) * fixed Timestamps ([6872ead8](https://github.com/qTox/qTox/commit/6872ead850eeab5cea2e036cb1701da8e116d51e)) * remove pgc code ([10a4b249](https://github.com/qTox/qTox/commit/10a4b249bbbc7e08923f543a96916911a3a799c4)) * set username and status on new profile ([109a4ffd](https://github.com/qTox/qTox/commit/109a4ffd43af5018d5848bfe8debfeddff3c4b9b)) * ensure QTimers are moved with the objects they belong to ([26206a35](https://github.com/qTox/qTox/commit/26206a35ebb7072b55877c91aa509eac4b96a17a)) * Fix misuse of toxcore tox_file_send API ([f988177a](https://github.com/qTox/qTox/commit/f988177a9b6af866ef2cf3aed9e5237ef76d5771)) * **coreav:** * prevent racy access to call variable ([dfeca3e9](https://github.com/qTox/qTox/commit/dfeca3e90637a55ce97013ffcf0cfece0e1699a4)) * change some lock to write locks ([26fcea06](https://github.com/qTox/qTox/commit/26fcea0639ba3af839b0e2017342f92cfdb83df7)) * fix assert because c-toxcore calls from unexpected thread ([e340688b](https://github.com/qTox/qTox/commit/e340688b94845cf9a37848b757c7b97aec549d94)) * avoid deadlock between CoreAV, main and Audio thread ([723a8e5d](https://github.com/qTox/qTox/commit/723a8e5dc75d5aaf0c0fb40ce041771c7bdc1abd)) * cleanup assertions ([a4ac6d67](https://github.com/qTox/qTox/commit/a4ac6d67c7a8459627ca4e5451af1064a670dd67)) * **corefile:** * cancel file transfer when peer goes offline ([6522988e](https://github.com/qTox/qTox/commit/6522988e8c2ef408278c0602ac9fd756b235b77b)) * handle empty avatar transfer in core ([1628d495](https://github.com/qTox/qTox/commit/1628d495d1c27a4864474d5e4a725e690b0dfba7)) * include file id in map when sending empty avatar ([b1eefc3f](https://github.com/qTox/qTox/commit/b1eefc3f230de36c76a3b158bc55f412f0a65a2a)) * **db:** * Support opening and upgrading to any of three SQLCipher params ([2c59c920](https://github.com/qTox/qTox/commit/2c59c9203035e35d81adc982ffe7e661d37e9b1f)) * Add half-upgraded cipher params ([deb7fbb6](https://github.com/qTox/qTox/commit/deb7fbb67cb5e7f799b01a37791e613471fff95a)) * fix schema log to display correct version ([92e51b05](https://github.com/qTox/qTox/commit/92e51b05acc550a71e58fe8ecc37181b72602a12)) * show full sqlite error message ([86b55a0f](https://github.com/qTox/qTox/commit/86b55a0fb0bf1bf5e2fc5ff64918e36c36f88f8f)) * prepare and execute queued statements one at a time ([d98fe85a](https://github.com/qTox/qTox/commit/d98fe85a582777b454724f0d3ea0642bd3a4b032)) * preserve user_version when adding or removing database password ([7e07025d](https://github.com/qTox/qTox/commit/7e07025d3c74c9bff32d840f5b1165fa2768a0ec)) * support databases with either SQLCipher 3.x or 4.x defaults ([dafb17b5](https://github.com/qTox/qTox/commit/dafb17b5fa81259a7a70804b56c233b05e76c3b9)) * use SQLCipher 3.x crypto even with SQLCipher 4.x ([e80dbe2d](https://github.com/qTox/qTox/commit/e80dbe2d83fdb133cedaddefad5fc2dff5a6ec91)) * **docs:** remove extra comma in README.md ([fee147d7](https://github.com/qTox/qTox/commit/fee147d788706d0b43c6d17a7d57514683c6f207)) * **flatpak:** * workaround unstable flathub downloads ([55b1f6af](https://github.com/qTox/qTox/commit/55b1f6af255c3322b9b4a2b89e7d8d7a6cf27fed)) * use SVG for icon ([489027ea](https://github.com/qTox/qTox/commit/489027ea1ef958543a5f90bca9288076497f4204)) * **friendlist:** * don't update friend's last activity on every start ([5f40ed8d](https://github.com/qTox/qTox/commit/5f40ed8d8c08cc7305506d796543c295d767515c)) * don't skip half of friends when sorting by activity ([3f922100](https://github.com/qTox/qTox/commit/3f922100af599e0c19925895f7d43b5a1b6932e5)) * **group:** * don't display netcam view for group voice calls ([5b31effd](https://github.com/qTox/qTox/commit/5b31effdb4f4b22c4d2ad64193ce799bef0db2b6)) * use valid pointer, allowing source invalidation ([765fce94](https://github.com/qTox/qTox/commit/765fce94b73b7e7f31680f352126575cbf129a19)) * Condense invalid title handling logic, clang-format ([aeddf482](https://github.com/qTox/qTox/commit/aeddf4822ad9930321a04a5a3cff4c7f3a5f1025)) * fix logic oversight and code style ([a1a37497](https://github.com/qTox/qTox/commit/a1a37497006a5440ea5ab529c2fe413e133d4c4e)) * set default group chat title when provided title is invalid ([f77a0621](https://github.com/qTox/qTox/commit/f77a062120f64bde9460e6caf4e71d0d802d4c21)) * always retain own name when updating group peer list ([c772db3b](https://github.com/qTox/qTox/commit/c772db3baa3f9e4694fa7f956ffdfb9ffab36cef), closes [#5686](https://github.com/qTox/qTox/issues/5686)) * treat empty peer names like empty friend names, by showing pk ([04f1ccda](https://github.com/qTox/qTox/commit/04f1ccda35595832dc2c7465bfada029b07bde80)) * **groups:** * Correct color of labels in group call ([3205c2c4](https://github.com/qTox/qTox/commit/3205c2c4079c3ad20766314e642e4ba583cf1e8f)) * Avoid segfault when resizing group audio window ([ce9e820b](https://github.com/qTox/qTox/commit/ce9e820b377f1f899846ca05a3d548a4100fd333)) * Correct color of labels in group call ([f27eb5b7](https://github.com/qTox/qTox/commit/f27eb5b76c62c724096658df134de3d4f28289e0)) * Avoid segfault when resizing group audio window ([d4d4308e](https://github.com/qTox/qTox/commit/d4d4308e286ffcf9fc23aafb95eb4c9786d85cca)) * Fix invalid group list on group member join ([836718aa](https://github.com/qTox/qTox/commit/836718aa263039c0e1daef0ba75593a3d35b5cdc), closes [#5838](https://github.com/qTox/qTox/issues/5838), breaks [#](https://github.com/qTox/qTox/issues/)) * reduce group message size limit by 50 ([6c77d57d](https://github.com/qTox/qTox/commit/6c77d57da8cd35e46251e9835d2edc9c9737197f), closes [#5760](https://github.com/qTox/qTox/issues/5760)) * remove logic that blocks parseConferenceSendMessageError ([9099eea0](https://github.com/qTox/qTox/commit/9099eea04ffdf9dc624a30ff8efa55b006a306d7)) * enable AV groups after load ([33d42c97](https://github.com/qTox/qTox/commit/33d42c9766e20fb62bb6a679b5a3f087904fa478)) * don't add friend alias to groups they aren't in ([6801298e](https://github.com/qTox/qTox/commit/6801298e964f73b8dc8d69a026ea3b1951e00322)) * fix assert on group invite accept ([0f5ad725](https://github.com/qTox/qTox/commit/0f5ad725d7173ec1a63892d0e6e1ffea768c42b8)) * add peers if already playing audio when netcam created ([e4891687](https://github.com/qTox/qTox/commit/e48916877536b34bf704414a77b65c2533078abc)) * don't add peers to netcam view until they play audio ([7c13b8b7](https://github.com/qTox/qTox/commit/7c13b8b7db0b948fc7cf76b4d640efb2aec9c980)) * check for label in timer audio playing timer callback ([d2508e7e](https://github.com/qTox/qTox/commit/d2508e7eac26d731c746333b5d74e9a051d3f5b7)) * avoid having to lookup peer pk ([13afbf7e](https://github.com/qTox/qTox/commit/13afbf7ec6e80a50fe9f6b04f31962de1d0bd0c7)) * don't invalidate all audio sources when peer list changes ([8422c09f](https://github.com/qTox/qTox/commit/8422c09f6a8a8043ce4c46ba4021680562aeb666)) * don't freeze when opening AV settings during group call ([093962e3](https://github.com/qTox/qTox/commit/093962e3ec05aa21a27083e9d7e096c00194568d)) * correctly show peers in the call after joining call ([d6abf76a](https://github.com/qTox/qTox/commit/d6abf76a279e45c21c4644b1bf571b1a95948b55)) * only leave audio call if in a call ([92d3c959](https://github.com/qTox/qTox/commit/92d3c959a1bc80688957c4775eb781e8804b76df)) * **history:** * check history settings when getting initial chatlog idx ([c906cdf5](https://github.com/qTox/qTox/commit/c906cdf57b3c4230cdb013d2f9cb4a5bdfc9aea4)) * Prevent invalid history access ([e3e6e1d9](https://github.com/qTox/qTox/commit/e3e6e1d9c4e22d6f090f153628677ad427cf4900)) * move stuck action messages to broken_messages table ([746314ba](https://github.com/qTox/qTox/commit/746314baf23118be28c5e38ee305448e5ad68ef3)) * display broken messages UI with error icon ([1a726b54](https://github.com/qTox/qTox/commit/1a726b54cd2e1d72c6cb6f0f9a733e904f5e5371)) * select broken messages from History, track in ChatLogMessage ([f6a15366](https://github.com/qTox/qTox/commit/f6a15366eff7254fc095f6a323272b602ebc9c30)) * move stuck pending message into broken_messages table ([b28dc300](https://github.com/qTox/qTox/commit/b28dc300610d096966bf18864bda0616efdc93ba)) * handle errors during db upgrade ([f72f3f71](https://github.com/qTox/qTox/commit/f72f3f714daabd955b256ec696c8917d0820c8b7)) * select only pending history for the requested friend ([64aa3eae](https://github.com/qTox/qTox/commit/64aa3eae4d687d899a56705c8105a10a8eac4db5)) * create file_transfers table in upgrade ([c12605db](https://github.com/qTox/qTox/commit/c12605db6d645ca313997543c8057a3dfd87bb48)) * save name to history for friend invite message ([2ccb1ec1](https://github.com/qTox/qTox/commit/2ccb1ec150ed721123b2d1d27b1f726390556522)) * **icon:** * use Qt tray icon in all cases, remove platform specific backends ([e85d3f52](https://github.com/qTox/qTox/commit/e85d3f522a4f38a5fb189dafa0b47b38f365dbf0)) * make busy notification icon translucent in middle ([da4928b7](https://github.com/qTox/qTox/commit/da4928b70481fda5f60707ae000e3442c77eaf5b)) * centre taskbars ([84836cf6](https://github.com/qTox/qTox/commit/84836cf6a0a549909b5be51c0016ee7c11a88a05)) * **interface:** use virtual destructors for all interfaces ([2b981d88](https://github.com/qTox/qTox/commit/2b981d88a2ddf9cca480ce1045ec558b63284a1f)) * **ipc:** handle ipc failure gracefully ([9dd08397](https://github.com/qTox/qTox/commit/9dd083978ece575820469c38a3b78418e8a5dfaf)) * **log:** don't attempt to close log file if it failed to open on exit ([fae9066b](https://github.com/qTox/qTox/commit/fae9066be6d982854d716ac5a077e4193047a85d)) * **logging:** only log toxcore messages above TRACE level ([7a98ea2d](https://github.com/qTox/qTox/commit/7a98ea2def1d97b759b6a017fe46f8c03aa4f0eb)) * **login:** show login screen when autologin fails ([e55e50d5](https://github.com/qTox/qTox/commit/e55e50d5105bc58cfb39b6ea64c8d2c6cc09ad76)) * **loginScreen:** make loginScreen return values comply with Qt standards ([b4bc0934](https://github.com/qTox/qTox/commit/b4bc09345c682ef0c84521ff60cc8f58fb80b87a), closes [#5781](https://github.com/qTox/qTox/issues/5781)) * **main:** register IPC handlers only after starting up ([bc3d3b3b](https://github.com/qTox/qTox/commit/bc3d3b3b13ad64ebae3a235557a761f3eca35bfd)) * **messages:** Fix broken sanitized name for notifications/alert messages ([fef89d70](https://github.com/qTox/qTox/commit/fef89d70f97edbf2218009e9842f32ad70aa84e5)) * **model:** * take QObject receiver argument to interface signal connection macro ([3a5e28ff](https://github.com/qTox/qTox/commit/3a5e28fffb56c1907950b12635c363c3d20216b4)) * stop interfaces from inheriting from QObject ([b7062b25](https://github.com/qTox/qTox/commit/b7062b251852347dff19595b5bf5eea43d53fcce)) * return connection from interface macro ([40e43586](https://github.com/qTox/qTox/commit/40e43586f10bbda9ce8e6647c41b9613c775439a)) * **net:** check if the node has all needed fields ([bdb3b61e](https://github.com/qTox/qTox/commit/bdb3b61ee0420fb43a5097dc950f302370716558)) * **notification:** implement review comments ([cd50376c](https://github.com/qTox/qTox/commit/cd50376c2f71561dc8abf0d9318dd954a82fed78)) * **offlinemsg:** * don't invalidate iterator before use ([1f80173b](https://github.com/qTox/qTox/commit/1f80173b2d8337fb40068f57e14e1a08a70165ec)) * fix offline message dispatching on history load ([dbef0b75](https://github.com/qTox/qTox/commit/dbef0b750865473c167683de2a630b4707aa755e)) * **offlingmsg:** only dipatch offline messages on friend online change ([479b39f5](https://github.com/qTox/qTox/commit/479b39f5364dc85817ea1cd31a8046c9bd48d989)) * **osx:** * Add camera and microphone usage description. ([28341d84](https://github.com/qTox/qTox/commit/28341d8457127f696b5c2c157d51ebd5d5700086)) * create /usr/local/sbin directory for brew package installation ([e97b2705](https://github.com/qTox/qTox/commit/e97b27058463df88a28a366f8573cd2238c8bb9e)) * **paths:** fix bugs uncovered while developing test cases ([0ea40905](https://github.com/qTox/qTox/commit/0ea409054ab1a0b950bdcd36a448dc795cd571d9)) * **profile:** * load settings before starting Core ([0a30c1b1](https://github.com/qTox/qTox/commit/0a30c1b1c0d6587581dea4d00f3ae1651640575b)) * avoid deadlock with between main and core thread ([aed820ba](https://github.com/qTox/qTox/commit/aed820ba60fad35b744cc3671ff7b90373c663bc)) * **search:** application crash when starting a search ([77d55d5d](https://github.com/qTox/qTox/commit/77d55d5d7d70a168b9b3a24d865eb1b03eee918a)) * **settings:** * apply toxcore settings for encrypted profiles ([74828c92](https://github.com/qTox/qTox/commit/74828c92e55f40ef22bf02e7440f31896fcea375)) * load personal settings before constructing core ([bef9d4b7](https://github.com/qTox/qTox/commit/bef9d4b77348d78f0c6536827a0fed511dcd9b95)) * repair saved invalid proxy type due to #5311 ([c8ffa1f9](https://github.com/qTox/qTox/commit/c8ffa1f921cbe36391f5d90ba888451a9310b5d8)) * Add mutex locks for consistency ([24f8bbf3](https://github.com/qTox/qTox/commit/24f8bbf35a06f2338f84eb8c1b3894cbf48d4d54)) * save settings early on Windows shutdown ([7839a260](https://github.com/qTox/qTox/commit/7839a2608396c77b60d67ab7aeeecc9c086bb71a)) * set default proxy type to None, enabling UDP by default ([a2c44cbe](https://github.com/qTox/qTox/commit/a2c44cbeda247bf06953a641d8d745fd0d3df62c)) * **status:** use enum as UI property instead of untranslated string ([881aa308](https://github.com/qTox/qTox/commit/881aa3083aac75153f8fcc9548d48951a01f8fd2), breaks [#](https://github.com/qTox/qTox/issues/)) * **test:** * create db tables in defined order, verify indexes ([05064771](https://github.com/qTox/qTox/commit/05064771abe045c59d747c8f778bb9eb829b37b9)) * include in groupmessagedispatcher_test.cpp ([34e1e25b](https://github.com/qTox/qTox/commit/34e1e25b7fb12dc662ed197e615c422ea56c867a)) * **toxcall:** * move ToxCall ownership to correct Thread ([df7fe223](https://github.com/qTox/qTox/commit/df7fe2239831484c1301350b5f940f59937744c5)) * handle Null sink correctly ([f02943c1](https://github.com/qTox/qTox/commit/f02943c191ea961d97c343c0360fe550ebe7c75d)) * **toxoptions:** handle nullptr in parameters ([6cce0dae](https://github.com/qTox/qTox/commit/6cce0dae838a08464661f56177c3474d4477e236)) * **transfer:** Accurately represent pause state in UI ([293a1d61](https://github.com/qTox/qTox/commit/293a1d615c0187e906453e7df904ffb320acc554)) * **travis:** * update pyenv to python 3.7 after updating ubuntu to 16.04 ([a44cce65](https://github.com/qTox/qTox/commit/a44cce65beb60c5f280b651e0c084fa9c2bdb0dc)) * fix latest nightly release not updating sometimes ([9b67d261](https://github.com/qTox/qTox/commit/9b67d2619c575cb24a30507d231ac7ca95181221)) * **ui:** * add vertical spacer to profileform ([c13ede85](https://github.com/qTox/qTox/commit/c13ede85df80faa67aee12b0c096dee52d94c464)) * fix auto-accept directory setup display ([8c5cab93](https://github.com/qTox/qTox/commit/8c5cab935ccf3d902d0a9c10509798bbe236a86c), closes [#5917](https://github.com/qTox/qTox/issues/5917)) * fix reversed mute/unmute hover text ([0055c776](https://github.com/qTox/qTox/commit/0055c7760cd10caa7814acc36d41a12e2bca8af3)) * fix away icon shape ([b4450319](https://github.com/qTox/qTox/commit/b445031912ae943d7580ea21f6813e9f71bc03ee)) * fix 2 ([1c02dc2d](https://github.com/qTox/qTox/commit/1c02dc2d6ba4f6d217c2fc55fa513d252aa4dbde)) * minor fix ([161a773d](https://github.com/qTox/qTox/commit/161a773dbbc4546442ad67dc96400fcd583fdccf)) * revert CMakeLists.txt ([821ed526](https://github.com/qTox/qTox/commit/821ed5261641043121d0b716d381346ac7502a15)) * apply requested changes ([f8b54b39](https://github.com/qTox/qTox/commit/f8b54b39f311b6d05b29e517c88c7fcf5fa4f5de)) * don't forget to update UI ([84e41303](https://github.com/qTox/qTox/commit/84e41303ce6297df68886d4a1615a07286aed794)) * minor fix ([4a6ebff6](https://github.com/qTox/qTox/commit/4a6ebff63a78650279856fcf3e1737d54e2523a0)) * Added option to hide message sender and contents ([3b305dd0](https://github.com/qTox/qTox/commit/3b305dd05a8210928a2bed7026b7f4fa7d9003bf)) * Improved notifications ([1bbe210c](https://github.com/qTox/qTox/commit/1bbe210c254a8ebbc6f529def5632ac47a941077)) * don't duplicate group peer name in tooltip ([87a53fad](https://github.com/qTox/qTox/commit/87a53fad96eb372630914748a864a45f55f13d29)) * show empty status as placeholder instead of as status ([e9f87951](https://github.com/qTox/qTox/commit/e9f8795197613326ba7e3d6d79a88c5ecd2cce30)) * fix typo, add plural to translation ([0dc46cdc](https://github.com/qTox/qTox/commit/0dc46cdc81bd49f892b75fc49f2944ed826d296b)) * update UI when leaving group call due to being last member ([7f802f59](https://github.com/qTox/qTox/commit/7f802f593eed3f49f9f209aa45f64851e3c32b23)) * add tooltip to about friend window ([4e76084c](https://github.com/qTox/qTox/commit/4e76084c6f634c2dde87c5797c1a18f6592fbda8)) * provide context when emitting friendWidgetRenamed() ([0b6d6d77](https://github.com/qTox/qTox/commit/0b6d6d771539ee7c67d4850dbd7586807cdaa92d), closes [#5495](https://github.com/qTox/qTox/issues/5495)) * tighten idealSize() for chatlog Timestamps ([c9f3830b](https://github.com/qTox/qTox/commit/c9f3830bc28dc0b0c7e6597f86a27fff0657cbd1), closes [#3957](https://github.com/qTox/qTox/issues/3957)) * remove useless variable ([2557f5e0](https://github.com/qTox/qTox/commit/2557f5e04521a7e19708e6254936e9d0eb625beb)) * oops ([d54e2106](https://github.com/qTox/qTox/commit/d54e21064c596dceef87b43b41d5b2723f244131)) * fix anything ([89cb2425](https://github.com/qTox/qTox/commit/89cb24254dc21077e2d0b8d014932e87940586ba)) * groupcolors fix2 ([a4a8e361](https://github.com/qTox/qTox/commit/a4a8e361d0e5aed1b3461dacd63b6a3600339a6f)) * groupcolors fix1 ([ab89ca2d](https://github.com/qTox/qTox/commit/ab89ca2d8ebb641d7af2d5b1100eafe8bde759d4)) * allow adding friends with Tox URI ([dec90ad8](https://github.com/qTox/qTox/commit/dec90ad84da5edd2c530196af8024a1259c58068)) * require . in www.url regex matching ([13b15a87](https://github.com/qTox/qTox/commit/13b15a87d71a91cbe49d7f868e914caf8d0aadc5)) * re-sort friends list on friend rename ([aa7542f7](https://github.com/qTox/qTox/commit/aa7542f729346fc87b113259c361d781bbac5da8)) * only add chat message for call once ([5f48fd4e](https://github.com/qTox/qTox/commit/5f48fd4e100fc41874aabc57a84884260a248894)) * amend QCheckBox background chromatic aberration ([b7019a4f](https://github.com/qTox/qTox/commit/b7019a4f667f4247f8eac7e52f36ebf6e6349b93)) * avatar highlight on hover in profile ([064dccf8](https://github.com/qTox/qTox/commit/064dccf8b49212b65c2c114fc87a9226b5ecf518)) * remove focus window option, fixing alert() on Windows ([91bdd40f](https://github.com/qTox/qTox/commit/91bdd40f10aa8731e5a3a1ee7d14682e142cc8f5)) * by default, don't grab focus on new message ([dac1582b](https://github.com/qTox/qTox/commit/dac1582b72290df846fd29da5a9922a83d709056)) * don't scale avatar to point of cropping in chat form header ([dc7ddbeb](https://github.com/qTox/qTox/commit/dc7ddbebad370bb7c7bc851a738037fc3c18d3fa)) * Display most recent group member's name instead of ([11131fa0](https://github.com/qTox/qTox/commit/11131fa083d2c6373358ade5e1d3bb0f08c43a72)) * fix the context menu enabled/disabled behaviors ([bc69c8c9](https://github.com/qTox/qTox/commit/bc69c8c9196f3ec3682c415145e5b7003c3a3006)) * don't emit filename change windows for every chat ([291e86d9](https://github.com/qTox/qTox/commit/291e86d9f9ea877fd92daa3b83ac283d56b305d5)) * **video:** * support UYVY camera pixel format ([4c42e0ea](https://github.com/qTox/qTox/commit/4c42e0ea5b5dbb904bf28aaa5790d3f5acbdb75c), closes [#5479](https://github.com/qTox/qTox/issues/5479)) * correctly align data passed to toxcore ([5c1fe520](https://github.com/qTox/qTox/commit/5c1fe520102cdfb5e47f6be87d314585e9e2a3c6)) * don't pass invalid pixel format strings to ffmpeg ([9de40e5a](https://github.com/qTox/qTox/commit/9de40e5a97930de5d002ae3460db25bb2cb6c54c)) * workaround for webcams that provide no fps value ([3746bd13](https://github.com/qTox/qTox/commit/3746bd13bc345c7785ed3e06ac8f02e364a57ce3)) * **widget:** * only change group title once ([f7a2a7d6](https://github.com/qTox/qTox/commit/f7a2a7d64840d9aaa276cc6a51a3d9c8611f76a7)) * don't try to play audio if it's disable ([2ea50309](https://github.com/qTox/qTox/commit/2ea5030958d2bf47fbedc2294c46677881b62a6a)) * fix freeze on showMainGui ([df62463e](https://github.com/qTox/qTox/commit/df62463e27f3ccf6cf9b1bf75008af27e41833ef)) * always force show window on activate event ([08f368da](https://github.com/qTox/qTox/commit/08f368da43a85e79377540a228b607ace844e7ae)) ## v1.16.3 (2018-07-18) This point release fixes flatpak build. No feature changes. ## v1.16.2 (2018-07-15) This point release fixes dialog spam from receiving invalid filenames and logs spam. No feature changes. #### Bug Fixes * **logging:** only log toxcore messages above TRACE level ([4dc74201](https://github.com/qTox/qTox/commit/4dc7420162e69095942b392048c309e6246d6b21)) * **ui:** don't emit filename change windows for every chat ([c1701345](https://github.com/qTox/qTox/commit/c1701345455ad5b253beeaa3d487daa01b8b1b21)) ## v1.16.1 (2018-07-04) This point release fixes our deployment of Flapak and AppImage on Github. No feature changes. #### Features * **deploy:** upload Flatpak bundle to Github releases ([59b5578c](https://github.com/qTox/qTox/commit/59b5578c7bffc56f6227c60bfcb38f97d39ec8d9)) #### Bug Fixes * **deploy:** fix file path in AppImage deployment ([64602f38](https://github.com/qTox/qTox/commit/64602f38f154a3f3d2429146ae5d370b2202d1b8)) ## v1.16.0 (2018-07-02) The most notable additions in this release are a new fullscreen mode for video calls, a new call end sound and support for more camera resolutions. To distribute qTox in a more user friendly manner we now publish Flatpak and AppImage packages. #### Bug Fixes * remove full screen btn from audio group chat ([0d3f061b](https://github.com/qTox/qTox/commit/0d3f061ba80d9f3f8a971d2b8e11a7d9b59d180a)) * local toxcore install with bootstrap.sh ([9ca38750](https://github.com/qTox/qTox/commit/9ca3875079adf175f31f568e45aabc37e3409000), closes [#5199](https://github.com/qTox/qTox/issues/5199)) * simple_make.sh script ([ead2152d](https://github.com/qTox/qTox/commit/ead2152d6f0d15f7e662975fb3ed8525109794c3)) * Fix PR #5182. Eliminating the 'new' operator at ToxOptionsWrapper ([9b6cd1c0](https://github.com/qTox/qTox/commit/9b6cd1c0227006308d4fe556f2b721865c2d9b21)) * Fix usage of unitialized functions ([06ae7ead](https://github.com/qTox/qTox/commit/06ae7ead0c7c23935c1c05c75d9cb11ed516224b)) * two crashes, uncovered by the persistent groupchat patch ([48179b6a](https://github.com/qTox/qTox/commit/48179b6a19807383e298661a21f97db3b140eb44)) * delete double initialization callDuration ([dc1f5ea0](https://github.com/qTox/qTox/commit/dc1f5ea0a319bf4cbf05989c414ccaea898b4826)) * **Core:** fix use after free of proxyAddrData ([26b59d31](https://github.com/qTox/qTox/commit/26b59d312375ad6391228308aabe45f0a85a1194)) * **appimage:** build sqlcipher form source ([64a7c24b](https://github.com/qTox/qTox/commit/64a7c24b2b5ad11a6df5dbb11da6e3aa7c0fd6f3)) * **audio:** * fix error introduced in 67f2605971cf43093c72f811e4df90ab70544dd6 ([40d30153](https://github.com/qTox/qTox/commit/40d30153aed223b65b596dc7d3bf17573b04f3e9)) * connect the correct audio callbacks ([a00af087](https://github.com/qTox/qTox/commit/a00af087778c6315ef55ed77c4209cbb63a6323d)) * close the audio device after playing a sound ([a3370173](https://github.com/qTox/qTox/commit/a3370173df24cd6880e3e3845ddbbc7c090b7aed)) * **build:** * Elimination the build warnings (Wunused-variable, Wreorder) ([2cd65610](https://github.com/qTox/qTox/commit/2cd65610fcce0c3dcf8a5e9cb9f313a76167c09a)) * correct install script nsis for win64 ([25e69572](https://github.com/qTox/qTox/commit/25e69572f89d816cfab5a8c0d1c261bae34d3cdd)) * make qTox compile with ffmpeg 4.0 and newer ([44193176](https://github.com/qTox/qTox/commit/441931765ffe3de349b28a28bf10a006edcc9949)) * **chatform:** * name in window title and close detached chats ([39968a31](https://github.com/qTox/qTox/commit/39968a313d78c727046837901e6cc3d6c31d18e0)) * check for empty path when exporting profile ([757791ee](https://github.com/qTox/qTox/commit/757791eea4be390bb6d1cdc908d1cd3c4b18728d), closes [#5146](https://github.com/qTox/qTox/issues/5146)) * **core:** Clean illegal chars from filenames ([ab85716f](https://github.com/qTox/qTox/commit/ab85716f00acfe00ff8035670919dd548d7f7f83)) * **docs:** update toxcore build instructions ([b00cbc1d](https://github.com/qTox/qTox/commit/b00cbc1d6f3a7f8406e4a96e732c534068fde22c)) * **file:** don't clean the filenames of avatar transfer ([2a8ab03e](https://github.com/qTox/qTox/commit/2a8ab03e46dd08efc4051a01bea56fe6a4c38a11)) * **history:** don't save both action prefix and displayed name ([dfd2de83](https://github.com/qTox/qTox/commit/dfd2de836eae605e02a1afb270620dd9274f6385)) * **leak:** Fix few memory leaks ([daaa5518](https://github.com/qTox/qTox/commit/daaa5518dd7c02c2de45690daa3f592206fc4023)) * **login:** start login screen on profile select by -p option ([1af3ad69](https://github.com/qTox/qTox/commit/1af3ad69e884bc4e74a4fcdd452a6aff10bffd62)) * **settings:** * automatically disable UDP when a proxy is set ([977b7fc9](https://github.com/qTox/qTox/commit/977b7fc9a02b2b44164ffb77ab35f4cdfae90542)) * prevent segfault on wrong proxy settings ([dfd5232e](https://github.com/qTox/qTox/commit/dfd5232e2fb727685a20804d7ca3b932ea239332)) * **simple_make:** correct variable initialization ([1537f83e](https://github.com/qTox/qTox/commit/1537f83e85ff28dd73fb66161ae2cd5eeef692d1)) * **theme:** clear stylesheet cache on theme colour change ([8ba8ce91](https://github.com/qTox/qTox/commit/8ba8ce91f3317794b72fb4937c459dac2856d367)) * **ui:** increase number of low res camera options ([72931514](https://github.com/qTox/qTox/commit/72931514695a8691593d6a5abd2df1e340f95002)) * **video:** unsubscribe the video device correctly ([e55f86c6](https://github.com/qTox/qTox/commit/e55f86c6a5b0344642fcb3d7a2550df6e899a6e5)) * **wayland:** Fix desktop file name in Qt properties ([c1caeb58](https://github.com/qTox/qTox/commit/c1caeb585a8845eaa72c7db79fb334262eafdb8f)) #### Features * Add ability to remove dialog from content dialog with middle click ([aae567ed](https://github.com/qTox/qTox/commit/aae567ed8e299fc0cdd700e2e0020042ee1cba11)) * Add ability to quit group with middle click ([228c431c](https://github.com/qTox/qTox/commit/228c431c890a7e68d078b441311892c691643926)) * Add middle mouse clicked signal for GenericChatroom ([65fc1dc2](https://github.com/qTox/qTox/commit/65fc1dc266da29e0679f2b645c31bc428f0cf575)) * **appimage:** build appimage on TravisCI ([f7345e4d](https://github.com/qTox/qTox/commit/f7345e4db264a5681490b9094981a65cac68d317)) * **call:** add call end sound ([65896e45](https://github.com/qTox/qTox/commit/65896e45017f8f748bc5b9db10a4400d7fd418dc)) * **chat:** * add UI option to mute group peers ([2fae2a30](https://github.com/qTox/qTox/commit/2fae2a30f76978ce722c5b24236384c8052ebfc4)) * full screen video chat ([d6df8883](https://github.com/qTox/qTox/commit/d6df8883e399b95a55c5a5870497c1dcd45a3917)) * **core:** put c-toxcore log messages in the qTox log ([4faab075](https://github.com/qTox/qTox/commit/4faab0750d3841beeb08c7d17e85044b5013aea8)) * **history:** load set number of messages from history ([ca32e77d](https://github.com/qTox/qTox/commit/ca32e77d7400e23a6a839f6a8d1f322bfe48bbf0)) ## v1.15.0 (2018-04-18) #### Performance * **ui:** cache stylesheets to reduce memory usage ([6d9d26db](https://github.com/qTox/qTox/commit/6d9d26db654981dbd22bdb0a70dfbc48f89b2e60)) #### Bug Fixes * remove build date and time from main.cpp ([23f6ad70](https://github.com/qTox/qTox/commit/23f6ad7047a2391defd95b144cfcceac37994a51)) * don't use invalid reference after erasing element ([1afed5f7](https://github.com/qTox/qTox/commit/1afed5f72e78b96222af4ab1d747cc73d6e8df35)) * **IPC:** Update profileName to update IPC ID after login screen ([d2ad2107](https://github.com/qTox/qTox/commit/d2ad2107f2ea9238a0e56ff8dd883d308615ab57)) * **audio:** * apply gain to both audio channels ([f6622e40](https://github.com/qTox/qTox/commit/f6622e40928196ae85724f340a760b4ea5f2fb2f)) * move audio timers to and start from audioThread ([5d0f9509](https://github.com/qTox/qTox/commit/5d0f9509a8d5fac940f402237efe4a8a6ab7e27b)) * **avform:** Audio/Video settings tab #5011 issue bugfix ([ad6ddfc4](https://github.com/qTox/qTox/commit/ad6ddfc4f245f9d0e36a6ce0ef9646a50fde30f8)) * **build:** Remove unused vars, add default case for switch ([bc18990f](https://github.com/qTox/qTox/commit/bc18990fd248c117916c88d640456e6b967a4802)) * **chatform:** include pressed key(s) when changing focus ([a8fc6e5c](https://github.com/qTox/qTox/commit/a8fc6e5c6bb0284950eab8495c8bc8ad45fd6355)) * **chattextedit:** ChatTextEdit bug fix for issue #5020 Corrected ([ebdc675c](https://github.com/qTox/qTox/commit/ebdc675cbc2dd0054306d461c382568c6148bf60)) * **core:** split messages on utf8 multibyte character boundary properly ([869036f3](https://github.com/qTox/qTox/commit/869036f3c6fc567b56fca1e36b7b4bdcda03a089)) * **docs:** * include master key fingerprint instead of subkey fingerprint ([3748ca6b](https://github.com/qTox/qTox/commit/3748ca6bc849046a44b83f16104490a685e65055)) * update dependencies for Debian ([7627d60a](https://github.com/qTox/qTox/commit/7627d60a89c89adea514f97b2f803617a22e6171)) * **history:** don't save own messages when history is disabled ([b852809d](https://github.com/qTox/qTox/commit/b852809d0c554754496a54cadc7d14d3802bc183)) * **import:** don't use java-style iterator before first element ([acea7c31](https://github.com/qTox/qTox/commit/acea7c315fd2b8cb89bac21956a16bca721f0712)) * **login:** Don't dereference null pointer ([703876c3](https://github.com/qTox/qTox/commit/703876c3724386c540d7e62e4f5d19dad31477bb)) * **name:** Clear alias on name change so that name changes are visible ([c2410e0a](https://github.com/qTox/qTox/commit/c2410e0a656f1708ed07bf8b2644300ae54af53c)) * **offlinemsg:** make faux offline messages purely event based ([4951f909](https://github.com/qTox/qTox/commit/4951f90964317a8a409f5ab75ecde3073c72e491)) * **profile:** don't error if no new avatar is selected ([cfdc1cdb](https://github.com/qTox/qTox/commit/cfdc1cdb2e74aeaece70c2798ec9b7fddbbbed39)) * **settings:** * connect enable LAN discovery checkbox ([55d8922d](https://github.com/qTox/qTox/commit/55d8922d69f035580dbea92285648d75645bfcf0)) * add missing enableTestSound to settings ([65d59ba6](https://github.com/qTox/qTox/commit/65d59ba6b7d2f89bf1d4078df3a3647abc84858c)) * **ui:** remove placeholder update UI ([cb0f2635](https://github.com/qTox/qTox/commit/cb0f26356d419b9be945f4f910c7415f14bf024d)) * **widget:** Fix double free crash on group leave ([81989406](https://github.com/qTox/qTox/commit/81989406073a70f7e9b17f03dee78ed7be5e978a)) #### Features * **Weblate:** create script to automatically merge Weblate changes ([96ae4284](https://github.com/qTox/qTox/commit/96ae4284a09df48fda2cdd0868779ec32a9d18de)) * **camera:** add higher resolution camera options ([04ecfe3f](https://github.com/qTox/qTox/commit/04ecfe3f344c29d9c598d38aaad46f2da8a17728)) * **ui:** * add setting for disabling new message notification popup ([fcd88d65](https://github.com/qTox/qTox/commit/fcd88d65b2875c06312374186fd9a08a9637dac7)) * Add ability to disable LAN discovery ([9f8b0fed](https://github.com/qTox/qTox/commit/9f8b0fed07d5a1debb27252df6dc88e8941ae487)) ## v1.14.0 (2018-03-12) #### Bug Fixes * Not quit on close if this setting is enabled ([e73dc10c](https://github.com/qTox/qTox/commit/e73dc10c7fd23b887cc5e2d5d4021bc02c8555ec)) * add search symbol ' in history ([3e05279c](https://github.com/qTox/qTox/commit/3e05279c097b33b09cedcebae4150c839a23af35)) * Use real channels number ([e74cc37a](https://github.com/qTox/qTox/commit/e74cc37a2d02e9d4cbd016bac9dbb7697e8445e7)) * Allocate memory to input buffer ([900f2a1a](https://github.com/qTox/qTox/commit/900f2a1ad3b328359a0ae089e778b15280512a9d)) * Call doAudio on timer timeout ([2353a66f](https://github.com/qTox/qTox/commit/2353a66fded32174421c9663ced5cfe4ceabe00b)) * [un]subscribe output in avform ([8c05399e](https://github.com/qTox/qTox/commit/8c05399e418f2c0147ce2d9c7dd220a0cdc97765)) * Correct display the call confirm window (CallConfirmWidget) ([f4fe343e](https://github.com/qTox/qTox/commit/f4fe343eca3eaf84f9ce300b59be9e83a70c204e)) * elimination of warning '-Wreorder' ([0869d3d8](https://github.com/qTox/qTox/commit/0869d3d8fdc9e9de2f1df51c377ddba71a1ce523)) * Use epsilon to compare float ([91dabf11](https://github.com/qTox/qTox/commit/91dabf11d31807f499d6e949373bf22762e80f5b)) * **UI:** prevent deadlocks on logout and profile delete ([a49e3458](https://github.com/qTox/qTox/commit/a49e34589f40edfb3fc46d5700573f87d5dfe3d0)) * **build:** * move Appdata file installation to /usr/share/metainfo ([5db0bdd3](https://github.com/qTox/qTox/commit/5db0bdd381f0f08c5685501702f2a2eb9d2f5674)) * add needed ffmpeg decoder to configuration ([8973a521](https://github.com/qTox/qTox/commit/8973a5216f49e65adc48d5fada8a574db598cced)) * Add missing dependency for openSUSE ([f7e089f7](https://github.com/qTox/qTox/commit/f7e089f7a71c41ff31d311fe7148e57b5c6fb60a)) * **chatform:** Broaden URL matching to include unicode ([e564b85e](https://github.com/qTox/qTox/commit/e564b85e3c485b283855bfdf00dfc0ec5427fad4)) * **chatlog:** * Match multi-character emoticons again ([9643e48e](https://github.com/qTox/qTox/commit/9643e48ef1d68948d52feec4e1be28c3ad61c0da)) * parse multi-length emoji properly ([5df63f9c](https://github.com/qTox/qTox/commit/5df63f9c2e6d78f4799447b0a22cdb9fb70c3fea)) * **chatwidget:** fix send file button not working ([af1aebfd](https://github.com/qTox/qTox/commit/af1aebfd1a7409ea821be2a616067561b62751c0)) * **cmake:** * fix platform extensions for windows ([7ad68e2f](https://github.com/qTox/qTox/commit/7ad68e2f43b458cd00ca27b9cfb20abf0b9ae46c)) * add missing dependency ([423f0956](https://github.com/qTox/qTox/commit/423f095622824a34d081fb69bddd83cddf83ca03)) * **core:** * Adapt qtox to new conferences state change callback. ([1111949f](https://github.com/qTox/qTox/commit/1111949f450fb4fe63321386f7f452ee1663f07a)) * Use new callback API for bitrate set ([d2deec7c](https://github.com/qTox/qTox/commit/d2deec7c554b3df651fe789dfb7964748329eff4)) * Use new API for bitrate set ([2c8f03da](https://github.com/qTox/qTox/commit/2c8f03dada443e30d6189050c7cf6d42e01827c5)) * **cpu:** Reduce CPU usage by avatar render ([8db61f96](https://github.com/qTox/qTox/commit/8db61f96ec78ac53479dd8db36eb192f6a1ddbcd)) * **friendwidget:** Use queued connection to avoid removing 'this' ([9b4972e0](https://github.com/qTox/qTox/commit/9b4972e0459de2921370cda9de645eb64e37ecfc)) * **group:** Show correct count of user on first creation ([0a590336](https://github.com/qTox/qTox/commit/0a590336b1467405a903464085dcdfc4474f93e6)) * **install:** Fix gzip invalid usage ([266f63f6](https://github.com/qTox/qTox/commit/266f63f6dfb1869aa2339d48cdc9b52ece3597ce)) * **l10n:** * Correction of the translation into Russian ([3fb42b75](https://github.com/qTox/qTox/commit/3fb42b75d75bf6c0240748ffff368b912b14a838)) * Correction of the translation into Russian ([9229fdd1](https://github.com/qTox/qTox/commit/9229fdd17e013a8bd60102648a200734890c2140)) * **smiley:** change license of classic smileys to CC BY-SA 4.0 ([da7c12e2](https://github.com/qTox/qTox/commit/da7c12e20cac1ac7340b4bb4ec89f782e2e4a159)) * **travis:** * try working around Travis + gitstats issue ([4c980945](https://github.com/qTox/qTox/commit/4c98094551ff4a1e7377a206b72fedd470b8be96)) * switch back to older Ubuntu Image ([378daeaa](https://github.com/qTox/qTox/commit/378daeaad4c5992a7acd2b650ff081d213556e10)) * **video:** * improve debug message ([ff2fc18b](https://github.com/qTox/qTox/commit/ff2fc18be164fcbc89bfd46d64f4b0096a97aee5)) * choose first available resolution in preview automatically ([81522dea](https://github.com/qTox/qTox/commit/81522deabdc3fb11fd8d3e1feb59274a96583121)) * use float framerates also for V4L2 ([a2927de2](https://github.com/qTox/qTox/commit/a2927de27d4776b52303e07c07ce89e8dadf86c5)) * allow not integer framerates ([db7ee65d](https://github.com/qTox/qTox/commit/db7ee65d0efbe23a45e385a148b20701e521a5c5)) * Fix square form of a video ([8de8c14a](https://github.com/qTox/qTox/commit/8de8c14a76908cf84a322a0bfd9e2c7ad2b4fa16)) * **widget:** Fix status pic alignment ([d9118cfc](https://github.com/qTox/qTox/commit/d9118cfc71e2b030914187df7fd9fb3d98378cf1)) * **windows:** %APPDATA -> %APPDATA% in template ([f53b8282](https://github.com/qTox/qTox/commit/f53b82825bf76be5a6793d18f2d102ed7b222313)) #### Features * Add the cmake option USE_CCACHE ([aa9cff31](https://github.com/qTox/qTox/commit/aa9cff315d659a7ca2010fb4791893abc8c5abdb)) * update to the new c-toxcore 0.2.0 conferences api ([d3d81bbd](https://github.com/qTox/qTox/commit/d3d81bbdf3c198a7c1258c6ad6405c6ab61cedd4)) * add hot keys for search ([ffb51e8a](https://github.com/qTox/qTox/commit/ffb51e8a0ea7dc3fb01f1f7650edc80b779a9be2)) * optimise search in history ([18fa8a74](https://github.com/qTox/qTox/commit/18fa8a745bdafddc00ba2f577c36451f40edfd61)) * add search in text in group chats ([7718734c](https://github.com/qTox/qTox/commit/7718734c9ab9705c1a1274b2a447611c1a2e22b4)) * remove search button and add line in context menu ([8bb80c77](https://github.com/qTox/qTox/commit/8bb80c770c1d21d1bdfc03c3d0569fabe6535e8f)) * edit load history for search ([de9c9061](https://github.com/qTox/qTox/commit/de9c9061175c97a9ee203d18a39e73f77544d5e6)) * add text search ([b881d32d](https://github.com/qTox/qTox/commit/b881d32d1bddb7352b8d24e2442ef6277ff0d583)) * add form for search ([863c46c7](https://github.com/qTox/qTox/commit/863c46c73d1a2fc677f9142ba8d7a2e8dc659c2a)) * add a button to search ([47d9da98](https://github.com/qTox/qTox/commit/47d9da98cf6811a30d35a1204e5342a4f7f4bf94)) * Prefere new line as message break ([3b52402f](https://github.com/qTox/qTox/commit/3b52402fa20d2d5418e129e5f001b626401a9ae5)) * **UI:** new status icons for message notifications ([4288785d](https://github.com/qTox/qTox/commit/4288785d31e215bc379223577f7d4dd65664ed86)) * **avatar:** Add outline hightlight on mouse hover ([bb26485d](https://github.com/qTox/qTox/commit/bb26485db6fed706f4ebccaffe35740394210032)) * **groupchat:** mark blocked users with different color ([a729f2f8](https://github.com/qTox/qTox/commit/a729f2f8c00d29d2837b6e380f5af1b95c344bad)) * **l10n:** * add Macedonian translation ([1a06f85d](https://github.com/qTox/qTox/commit/1a06f85d3ccc91ff6f759a38534483fa40aaaa29)) * add Macedonian translation using Weblate ([41420331](https://github.com/qTox/qTox/commit/414203310a30720e02e06719bfcafbb8bcff9018)) * update French translation from Weblate ([a7e90969](https://github.com/qTox/qTox/commit/a7e9096919d4c0b89f061e8b77741d517f574838)) * update Portuguese translation from Weblate ([3bad087b](https://github.com/qTox/qTox/commit/3bad087bbff2fbff4c4d543df1f96931784c93df)) * update Portuguese translation from Weblate ([8c3be522](https://github.com/qTox/qTox/commit/8c3be5225f484469aed43dde04f03bc588ca2c15)) #### Performance * **widget:** don't save on setExpanded if categorywidget is unchanged Fix #4932 ([b9845e1d](https://github.com/qTox/qTox/commit/b9845e1d23eb23380f447692e3a813413e897c2d)) ## v1.13.0 (2017-11-25) In this release we added microphone voice activation and the long-awaited Github-like Identicon instead of default avatars. Of course also some bugs were fixed and new ones added :P #### Bug Fixes * use only well known categories in the desktop file ([4d36c23c](https://github.com/qTox/qTox/commit/4d36c23c9ba88a350a9b7ae6a7657fda3b8a2af2)) * add default return in Widget::getStatusIconPath to fix warning ([8a9c34d8](https://github.com/qTox/qTox/commit/8a9c34d8886ac2db3def1d08e8c203839d68c37d)) * URL patterns based on RFC 3986 ([6ffe4cd8](https://github.com/qTox/qTox/commit/6ffe4cd8d892183ecdfcba571ff8dd4d62d595fd)) * using current setting in autoaccept dialog ([70b235f2](https://github.com/qTox/qTox/commit/70b235f271b2f3d1dfaf036d60bb7e8e63db2908)) * remove unnecessary semicolon to compile with `-pedantic` flag ([37419825](https://github.com/qTox/qTox/commit/37419825cafcc0d4a239c2922abc074b753a4e29)) * add missed header ([5e455699](https://github.com/qTox/qTox/commit/5e455699730284e71709326b30f897d103db17ea)) * Use correct initialization order ([b41767d0](https://github.com/qTox/qTox/commit/b41767d0874eaf8887f908d2a9c5571f4342d5d5)) * remove unused variables ([c089c80e](https://github.com/qTox/qTox/commit/c089c80e53be33a8d42ea28176763659c1eb2005)) * **build:** * install libexif-dev on apt-based systems ([2066c2e0](https://github.com/qTox/qTox/commit/2066c2e002009a75708c61f2ec3c39ce588d0e56)) * do not build unix-specific test on Windows ([d69023c9](https://github.com/qTox/qTox/commit/d69023c9d78714f5e730e98bb251c8d0f723688a)) * **call:** fix bugs introduced from ToxCall refactor ([1394dd1b](https://github.com/qTox/qTox/commit/1394dd1b7fc59da57f0bd7470f4caed22231670a)) * **cameradevice:** Save string in local variable to avoid errors ([92def839](https://github.com/qTox/qTox/commit/92def839d084a97bed80e01e5afda49c593c34bd)) * **chatForm:** Fix issue with mixing friend and group id ([5bc8ef4e](https://github.com/qTox/qTox/commit/5bc8ef4e74dee407447de52328c3ccf98307c46a)) * **chatform:** * Subscribe on accept and reject buttons once ([d77fbb4b](https://github.com/qTox/qTox/commit/d77fbb4b1999da2e20d5ed0071b254eea99dce4b)) * Hide call confirm on call end ([f4a3bb28](https://github.com/qTox/qTox/commit/f4a3bb2812fbe3cab424a190a7f8114d26351cd7)) * **core:** Check that result of get peer name is successful ([78262b41](https://github.com/qTox/qTox/commit/78262b41bd8d2bec9d50ac736302eaa389369404)) * **ffmpeg:** don't use deprecated features ([2f13796a](https://github.com/qTox/qTox/commit/2f13796acc5e63a57a5af1e5a4ce102da506d5d5)) * **friend:** Add save friend alias on change ([c0a7488c](https://github.com/qTox/qTox/commit/c0a7488c12923354d0ed25f405994327ce2eb31a)) * **group:** * Send all parts of long message ([7c76bebe](https://github.com/qTox/qTox/commit/7c76bebebef9d20da38bd4b45c3fb4aed2cf5915)) * Add second signal for titile changed ([2f9ba3c4](https://github.com/qTox/qTox/commit/2f9ba3c4d793a9b50f932c82792a5f8afce5f2b8)) * **identicon:** don't set identicon as an avatar ([676be5f6](https://github.com/qTox/qTox/commit/676be5f625dc485433ba9137a027cad2bef1b5f5)) * **init:** register AV connects and call after AV is ready ([7170b485](https://github.com/qTox/qTox/commit/7170b48589c88eaa33263ffb0155dbf384b82d56), closes [#4651](https://github.com/qTox/qTox/issues/4651)) * **ipc:** Add check if IPC init failed ([c274cec8](https://github.com/qTox/qTox/commit/c274cec87e957894c021bd7f27994a2f6ce5473c)) * **logout:** Synchronously call showLogin to avoid multiple deletion ([5046fc90](https://github.com/qTox/qTox/commit/5046fc90103f8c33d8e5221a1e655414aad53f44), closes [#4201](https://github.com/qTox/qTox/issues/4201)) * **platform:** use result to remove -Wunused-result warning ([729dc774](https://github.com/qTox/qTox/commit/729dc7740b04becae7c509c3aafbf553fd955b31)) * **preview:** only downscale preview images, never upscale ([45b3575d](https://github.com/qTox/qTox/commit/45b3575d7902d99a3c8a31987b4ef92d4ba5d78f), closes [#4733](https://github.com/qTox/qTox/issues/4733)) * **profile:** Fix status message and username signal connection typo ([d41031bd](https://github.com/qTox/qTox/commit/d41031bd15bfa6cce1a0f55b20952a98680c3f2f), closes [#4760](https://github.com/qTox/qTox/issues/4760)) * **settings:** * prevent signed overflow and associated warning ([6d1b1f62](https://github.com/qTox/qTox/commit/6d1b1f62aba08b84e4dddf3436a936cddea3518f)) * Delete dynamically allocated members on destruction ([94cb6ce1](https://github.com/qTox/qTox/commit/94cb6ce1c562d7be0bc71e165f73c8ec5cd66353), closes [#4670](https://github.com/qTox/qTox/issues/4670)) * **test:** If json string not quoted is server error ([7c744912](https://github.com/qTox/qTox/commit/7c744912268c53db3509dd7b5575c72df1e9b542)) * **toxme:** Add #include to toxmedata ([2f1bf107](https://github.com/qTox/qTox/commit/2f1bf107808953f5f080de3458f88abb33ad247a)) * **travis:** make Windows cross-compilation caching work ([0966e91e](https://github.com/qTox/qTox/commit/0966e91e31684cbfdc8b5b339929db1f17907707)) * **ui:** Set tooltip colour and background for all tooltips ([b1f40dec](https://github.com/qTox/qTox/commit/b1f40decd868f4df81ac8784bdcbb6a791d945ea), closes [#4667](https://github.com/qTox/qTox/issues/4667)) * **video:** * don't use pointers to temporary objects ([5d6ae9ae](https://github.com/qTox/qTox/commit/5d6ae9ae372a95f8e6cd1950d87ba379eb1b8dfe)) * prevent segfault when ending videocall ([fed70602](https://github.com/qTox/qTox/commit/fed7060270932f3731d78c2f2ba30f5a10300f95)) * reduce default video bitrate to make it more usable ([ecea4104](https://github.com/qTox/qTox/commit/ecea41045c50611fb8f8b506c25c6f613073a4ce)) * Set toxav video bitrate to 0 if answering audio-only call ([1613044c](https://github.com/qTox/qTox/commit/1613044c68cee2ceee82b61e789e5225dda45f7d)) #### Performance * **smileys:** * Cleanup smileys icons by timer ([fa215949](https://github.com/qTox/qTox/commit/fa21594902beef73e70f69e80ae7acb016aa7ec6)) * Use shared_ptr to automaticaly count references ([c6400077](https://github.com/qTox/qTox/commit/c64000777510e8b6c899475bf49fba31af9fda8b)) * Use lazy smileys loading ([d83400bc](https://github.com/qTox/qTox/commit/d83400bcdc1bf177aabb6940ee3686f8e4f0e942)) #### Features * **audio:** microphone voice activation ([d24d4fb8](https://github.com/qTox/qTox/commit/d24d4fb8ea44999d037e7ca839c338140c5a59c7)) * **build:** hash everything in Windows cross-compilation ([319d871b](https://github.com/qTox/qTox/commit/319d871be38d6f92386f626c7fcbd25252e680bd)) * **identicon:** * use Identicons instead of empty avatars ([003fc6b0](https://github.com/qTox/qTox/commit/003fc6b0b0cbef99888a746d60566a1bcc3805f9)) * add algorithm to create identicons ([61b36d1b](https://github.com/qTox/qTox/commit/61b36d1bce87173fd290398db1045bd90a998dc9)) * **l10n:** * update German translation from Weblate ([e0235dff](https://github.com/qTox/qTox/commit/e0235dffc5e3240696d5350bdbc1d2c5386e8cec)) * update German translation from Weblate ([af2addec](https://github.com/qTox/qTox/commit/af2addec860de6fb986250b2f7d22c8ea039ffd7)) * update German translation from Weblate ([13df29e5](https://github.com/qTox/qTox/commit/13df29e5d1f5e3c5365695b287778e024a9fc220)) * update Italian translation from Weblate ([45a053cc](https://github.com/qTox/qTox/commit/45a053ccb7dc414b4f464d32039b99707632e665)) * update Lithuanian translation from Weblate ([79f96c90](https://github.com/qTox/qTox/commit/79f96c902665e4c0d9f314a232201d2f16c137a9)) * update Slovak translation from Weblate ([5bd1b971](https://github.com/qTox/qTox/commit/5bd1b97170bc42561519448692d16c9f7fe3eee9)) * update Hungarian translation from Weblate ([d344e060](https://github.com/qTox/qTox/commit/d344e06016076265940dd1c293f946cd99df05c8)) * update Norwegian (old code) translation from Weblate ([64a83067](https://github.com/qTox/qTox/commit/64a8306731b1992cb49c79c3de8b921cb15ee92a)) * update Russian translation from Weblate ([a62535a5](https://github.com/qTox/qTox/commit/a62535a5e4acac41e95e2a186806d6f387fde9fd)) * update Russian translation from Weblate ([e9c9eeea](https://github.com/qTox/qTox/commit/e9c9eeea9f3bd60105b8eeeb15b3275102d3be7f)) * update Belarusian translation from Weblate ([652cdab8](https://github.com/qTox/qTox/commit/652cdab8d42101f6348dc183f5ad72a58082f8a2)) * update Romanian translation from Weblate ([fd7fe766](https://github.com/qTox/qTox/commit/fd7fe76697512ffc1188e0588b17c81f934207ba)) * update Estonian translation from Weblate ([d0394e88](https://github.com/qTox/qTox/commit/d0394e88e76752996bc24a8c179e17c0043d4160)) * update Spanish translation from Weblate ([00e33153](https://github.com/qTox/qTox/commit/00e3315360036b5db031139b2c4b05e6dca10eb5)) * update Serbian (latin) translation from Weblate ([6f4c675a](https://github.com/qTox/qTox/commit/6f4c675a46fe471fa00d9e63ae14306d0b88fe0d)) * update Serbian translation from Weblate ([1aec68fd](https://github.com/qTox/qTox/commit/1aec68fdf0c32a4a0a782c9ef256563818ebbf41)) * update Chinese (Simplified) translation from Weblate ([e828583b](https://github.com/qTox/qTox/commit/e828583b9af80c8aacbe208f2a6a831d8427e863)) * update Persian translation from Weblate ([b3ceda6e](https://github.com/qTox/qTox/commit/b3ceda6e47d485037ab3c1df9c2f433801d1b688)) * update Belarusian translation from Weblate ([07cf8838](https://github.com/qTox/qTox/commit/07cf8838b59059450b90a0c8cab7fb5285d02d59)) * update Estonian translation from Weblate ([235e6bfd](https://github.com/qTox/qTox/commit/235e6bfd34959d0f0d11da167e7b63339780963a)) * update Estonian translation from Weblate ([33d4cc4a](https://github.com/qTox/qTox/commit/33d4cc4a1dfc4c874eab8db916232e84000c7698)) * update Estonian translation from Weblate ([f82d3449](https://github.com/qTox/qTox/commit/f82d34492872148757dfddcc5663d9f7a21ffb71)) * update Estonian translation from Weblate ([53475ed2](https://github.com/qTox/qTox/commit/53475ed2dc334f4babebb14780dadc08b9b3e9a5)) * update Serbian translation from Weblate ([907c3e5c](https://github.com/qTox/qTox/commit/907c3e5c8b557f3051943d17ef6aa785f01b890f)) * update Serbian (latin) translation from Weblate ([f171c232](https://github.com/qTox/qTox/commit/f171c2327f19041b908ec4883d8d3426aa5f1e20)) * update Croatian translation from Weblate ([6f0b6925](https://github.com/qTox/qTox/commit/6f0b692562cbc303ac8f420f7a9aee82821ab8e8)) * add Serbian (latin) translation to UI ([641a8084](https://github.com/qTox/qTox/commit/641a80842e0d3fabb6b4872d2a342d167ff80958)) * add Serbian (latin) translation using Weblate ([66a680df](https://github.com/qTox/qTox/commit/66a680df8720880fda7254cedccb8ddd45f6ceed)) * add Serbian translation to UI ([3c4b1f11](https://github.com/qTox/qTox/commit/3c4b1f117d29293d6ebbb68457eb96452a9b557b)) * add Serbian translation using Weblate ([17c00487](https://github.com/qTox/qTox/commit/17c00487921d12c2f54ed25f08d943e6914231e1)) * add Persian translation to UI ([76df9a45](https://github.com/qTox/qTox/commit/76df9a453511e98693c7302b3f48598caff45d9e)) * add Persian translation using Weblate ([f2d3beb6](https://github.com/qTox/qTox/commit/f2d3beb67e734fc190fd331a1539a043590ffeee)) * update Lithuanian translation from Weblate ([7e80cbca](https://github.com/qTox/qTox/commit/7e80cbcaeee1c2187d3f31d83e0e31b615d5dac9)) * update Swedish translation from Weblate ([27a58b93](https://github.com/qTox/qTox/commit/27a58b938d381f6d9a39a78280f4bb5958b34193)) * update Portuguese translation from Weblate ([9b7e23c6](https://github.com/qTox/qTox/commit/9b7e23c68be190487cf131018e4a5a071aa23152)) * update Portuguese translation from Weblate ([227bba8f](https://github.com/qTox/qTox/commit/227bba8fa2624714d8a73e6cd4dc50aab5eaafea)) * update Portuguese translation from Weblate ([1425dff6](https://github.com/qTox/qTox/commit/1425dff6e7a711b5c053842620b8fdeaaed653ce)) * update Portuguese translation from Weblate ([8c07a2b2](https://github.com/qTox/qTox/commit/8c07a2b22503be0ededabb261a553db132a38109)) * update Portuguese translation from Weblate ([ccc5296f](https://github.com/qTox/qTox/commit/ccc5296fd30fe531de552abf76da3062c9e31c9f)) * update Estonian translation from Weblate ([8710563b](https://github.com/qTox/qTox/commit/8710563bd638d672271c62fab9a06ef4cb200313)) * update Estonian translation from Weblate ([d5e484b8](https://github.com/qTox/qTox/commit/d5e484b8d94469e866597d1794ec2d6eb0f6396f)) * update Norwegian (old code) translation from Weblate ([bcdbd2ca](https://github.com/qTox/qTox/commit/bcdbd2caef16a8f4bc11da287dba01db2ed1c640)) * update Romanian translation from Weblate ([2d2cdd01](https://github.com/qTox/qTox/commit/2d2cdd010a6cdd890cfea6bec2b7bd0a296a1ed6)) * update Belarusian translation from Weblate ([01b9bcd9](https://github.com/qTox/qTox/commit/01b9bcd9d214389cfe7f84fb1ac300bbb5bfb4bc)) * update Chinese (Simplified) translation from Weblate ([1514b800](https://github.com/qTox/qTox/commit/1514b80093c701318e187c8eb9ab93a4bd71822c)) * update Spanish translation from Weblate ([154002c0](https://github.com/qTox/qTox/commit/154002c0027eb7dea7f5b74e302ef29eb5a096fe)) * update Estonian translation from Weblate ([681661f6](https://github.com/qTox/qTox/commit/681661f61c55ba7b63e54b38594e9a85abb92454)) * update Romanian translation from Weblate ([47a83e3b](https://github.com/qTox/qTox/commit/47a83e3bf9d099dc084dd8098d43f73258c47b56)) * update Chinese (Simplified) translation from Weblate ([0145bb11](https://github.com/qTox/qTox/commit/0145bb11775d11dd2dc9b7025c2b1a0f8355fcc5)) * update Belarusian translation from Weblate ([753ea45b](https://github.com/qTox/qTox/commit/753ea45b6d1b0eb5744370f96eae0492af9fda79)) * **settings:** * add an option to toggle identicons ([905ca770](https://github.com/qTox/qTox/commit/905ca7708662c6fe5520ad2707715075ad1aa41d)) * make audio quality setting persistent ([7ed2d97a](https://github.com/qTox/qTox/commit/7ed2d97aadab8aa0acb39e54aafaea06c0ebe57c)) * Add audio quality setting ([61eddc1f](https://github.com/qTox/qTox/commit/61eddc1f6b76f2da1a6d180426a2b7f53c757e2a), closes [#4693](https://github.com/qTox/qTox/issues/4693)) * **travis:** * run tests on Windows ([21af6875](https://github.com/qTox/qTox/commit/21af6875cc7a1f5a311b8149ced9970570a4b2e5)) * Windows cross-compilation ([9358297a](https://github.com/qTox/qTox/commit/9358297af87ebb13dc4f74d2ced6033e38b25a5b)) ## v1.12.0 (2017-10-01) This release provides many handy new features, most notable are mass import of contacts from a file, improvements around images in the chatlog and an experimental audio backend with echo cancelling. #### Features * UI to import a list of contacts from a file ([0974da54](https://github.com/qTox/qTox/commit/0974da543a6b76851ffa86d24883c324651a8ff9), closes [#4181](https://github.com/qTox/qTox/issues/4181)) * **audio:** * make the libfilteraudio dependency optional ([8652fe99](https://github.com/qTox/qTox/commit/8652fe99e3cb80fac22a601c89ba405962c5537d)) * add setting to switch between new and old audio backend ([9d0498e0](https://github.com/qTox/qTox/commit/9d0498e0cfdf654f4319675f019669dacde4dbcc)) * make echo cancellation work and improve some minor stuff ([5d60f09d](https://github.com/qTox/qTox/commit/5d60f09df4b59c3a42c7544fd92c78710dd9d5b0)) * add libfilteraudio ([9c603e86](https://github.com/qTox/qTox/commit/9c603e86546da199cd94f22f4e4f9349d526fad1)) * add alternative OpenAL backend ([c7157291](https://github.com/qTox/qTox/commit/c7157291679ccb0414789dd04acd43455ce6aafc)) * **build:** Check sha256 of tarballs for Windows cross-compilation ([7f6f8a45](https://github.com/qTox/qTox/commit/7f6f8a4513bfa07356f7ac6a88da346d8c325e7a)) * **exif:** Honour exif orientation tag ([414fa178](https://github.com/qTox/qTox/commit/414fa178b4908d8451ca5304ee9ca1ab7421b26a), closes [#1848](https://github.com/qTox/qTox/issues/1848)) * **l10n:** * update Belarusian translation from Weblate ([d5c6d102](https://github.com/qTox/qTox/commit/d5c6d1027c4e9e892049cbd4fb4dff6829a99318)) * update Bulgarian translation from Weblate ([0e688da1](https://github.com/qTox/qTox/commit/0e688da1b0f6228478a5e18c5d2d63a369afcb43)) * update Chinese (Simplified) translation from Weblate ([78a0d339](https://github.com/qTox/qTox/commit/78a0d33942dcda153fac2fa30321227127cf60e1)) * update Estonian translation from Weblate ([035c158a](https://github.com/qTox/qTox/commit/035c158a9913389bd56faea4dcd5bd6b8f4a9de0)) * update Finnish translation from Weblate ([814a6ea0](https://github.com/qTox/qTox/commit/814a6ea0cf83c68b959cdf53b73e7e18fcf18231)) * update French translation from Weblate ([069feae3](https://github.com/qTox/qTox/commit/069feae311fef515eee948e90f4ed36a27160c87), [4abaf031](https://github.com/qTox/qTox/commit/4abaf03106d8d51b307d56d62f0ad7d1bfde8a4a), [53264f4c](https://github.com/qTox/qTox/commit/53264f4cf90357893f297d8a79580c9f7f697465), [5aa47be7](https://github.com/qTox/qTox/commit/5aa47be72a68d62b6bd8c3a6442bc43ad82b8800), [6917def2](https://github.com/qTox/qTox/commit/6917def238287a51458012983d27d70f0003d697), [8a94ad75](https://github.com/qTox/qTox/commit/8a94ad75efffe07da97725d9a1cbba9f87887b18), [8d3a6310](https://github.com/qTox/qTox/commit/8d3a631084f4281b07b910da63ebbf1a27275748), [a225eec5](https://github.com/qTox/qTox/commit/a225eec5fd36c9f154b90a9aaa148139d1961521), [d662866c](https://github.com/qTox/qTox/commit/d662866c413fccd0dc18fb1bdfba01e13023d1b5), [db4602ea](https://github.com/qTox/qTox/commit/db4602ead1cd7cbd031423429aca7747b96d5d03), [dbfc4794](https://github.com/qTox/qTox/commit/dbfc4794e53ed8523ba139031d03a512cd9a2dbb), [f2b648e4](https://github.com/qTox/qTox/commit/f2b648e4d3be5eba65c6444a544956133e9242db)) * update German translation from Weblate ([55425705](https://github.com/qTox/qTox/commit/5542570505bc4d7b3ca5e4ff33bbccf3b5e79f9a), [f0ca3bed](https://github.com/qTox/qTox/commit/f0ca3bed382cf4cc51118989cc39a87a12d37be2)) * update Hungarian translation from Weblate ([3b1a8ac7](https://github.com/qTox/qTox/commit/3b1a8ac704dabf14a60201a3fd114bf58db13cb7)) * update Lithuanian translation from Weblate ([c06abad8](https://github.com/qTox/qTox/commit/c06abad83ba2fc127502d272a4ee0c4e9c16a566)) * update Norwegian (old code) translation from Weblate ([868fd55d](https://github.com/qTox/qTox/commit/868fd55d8e30c9b17e1ae60a42c0640b4f3d6cad)) * update Polish translation from Weblate ([916f7aa2](https://github.com/qTox/qTox/commit/916f7aa250281c6beae8135df05aa627d18e6527)) * update Romanian translation from Weblate ([227516d2](https://github.com/qTox/qTox/commit/227516d206a8fea9f408861f5c63aa8c2a530f54), [e441db87](https://github.com/qTox/qTox/commit/e441db87d3b0f8c54f59848796bb76f56d47702b)) * update Russian translation from Weblate ([a6692e28](https://github.com/qTox/qTox/commit/a6692e28ee89fe56094d719d7235d281dfe07c6c)) * update Slovak translation from Weblate ([155812e4](https://github.com/qTox/qTox/commit/155812e444a4614c35e9c8ddeab619200c1d4643)) * update Swedish translation from Weblate ([57519ca7](https://github.com/qTox/qTox/commit/57519ca75de1f1426a3fe20ee78330733ce328aa)) * update Tamil translation from Weblate ([092cae62](https://github.com/qTox/qTox/commit/092cae62d05eada032f207ac3d13d55201905eee), [13c572c3](https://github.com/qTox/qTox/commit/13c572c393619f0da9777935b1c7bd8a5bcd437d)) * **login:** Add command line argument to open login screen ([0906b8eb](https://github.com/qTox/qTox/commit/0906b8eb1f9a413e80320d002d15540736a6fabb), closes [#4673](https://github.com/qTox/qTox/issues/4673)) * **paste:** Implement pasting images from clipboard ([5fed3736](https://github.com/qTox/qTox/commit/5fed37365f1d32f900814527bc8eb514986f2abd), closes [#1290](https://github.com/qTox/qTox/issues/1290)) * **settings:** add group chat local member black list ([27ecace7](https://github.com/qTox/qTox/commit/27ecace752424b67924011e5838fd1f11857b3cf)) * **ui:** output instructions when user tries to open a second instance ([48d65c26](https://github.com/qTox/qTox/commit/48d65c269a3dd3badd498b816f9d1cbfaa33bef4), closes [#3483](https://github.com/qTox/qTox/issues/3483)) * **video:** Error message on call fail ([ac75f7b5](https://github.com/qTox/qTox/commit/ac75f7b5944dc6dfc0b7334b22f53a159d8e7bc7)) #### Bug Fixes * ATOMIC_FLAG_INIT can't be used inside braces initializer ([f790747f](https://github.com/qTox/qTox/commit/f790747f2adda60f8809bb0e1671f18c3f7c6168)) * Return value ([f97c776a](https://github.com/qTox/qTox/commit/f97c776a8722c5f230cb709270559fdfcd77ba47)) * Fix crash on auto accept click in AboutFriendForm ([bd6516ea](https://github.com/qTox/qTox/commit/bd6516eab707c3e4b208b937e91a7c0eef39fafa)) * Fix crash on accept or reject friend request ([359a42f7](https://github.com/qTox/qTox/commit/359a42f78e8edf073d6132bca8954a55094385c5)) * Keep open and share X11 connection ([ae5cb4bc](https://github.com/qTox/qTox/commit/ae5cb4bcc27754f46d30209bf1658e49ff559dc7)) * **IPC:** don't double lock shared memory ([0bf27a00](https://github.com/qTox/qTox/commit/0bf27a000aa42559a5b04b7e03781a29885f6adf), closes [#4678](https://github.com/qTox/qTox/issues/4678)) * **audio:** * Fix signed and unsigned comparation ([aa356bb7](https://github.com/qTox/qTox/commit/aa356bb73a7443720a0d73be1f5401940f014498)) * Echo cancelling supports only mono audio ([809c5e6b](https://github.com/qTox/qTox/commit/809c5e6b04c991960afcc66a308bad63ca473610)) * **autoaway:** Improve autoaway algorithm. ([9fe503c7](https://github.com/qTox/qTox/commit/9fe503c7083b1e9f1600af0597455eb8237f8c6e)) * **avatar:** Don't invert default avatar colour on connect ([be324b93](https://github.com/qTox/qTox/commit/be324b932b3e572b59f408faacd690110360dcbb), closes [#4629](https://github.com/qTox/qTox/issues/4629)) * **build:** * Fix regression in how cmake finds libraries ([b7ef73d4](https://github.com/qTox/qTox/commit/b7ef73d401aabbd0f5ee6ccfcd1a9f0a962bdaab)) * Update toxcore and Qt versions for Windows cross-compilation ([80f1286e](https://github.com/qTox/qTox/commit/80f1286e7ca5025d3e9f93b4209f791e043ad367)) * Add libexif to the Windows cross-compilation ([95548c6a](https://github.com/qTox/qTox/commit/95548c6a5fcce7a31ffcaafcaec8650fd6f4c968)) * Fixed header file of libfilteraudio not being found ([8108bfdc](https://github.com/qTox/qTox/commit/8108bfdc358b5070d1dfbf57a02e25439ce09541)) * Windows cross-compilation didn't see libfilterudio ([b0b157e7](https://github.com/qTox/qTox/commit/b0b157e77bc87eae1f8235e7d15d76e82c992ce6)) * add install steps for libfilteraudio on OSX ([8f2401fe](https://github.com/qTox/qTox/commit/8f2401fee64d0b5535bd4d737a1caf2bffa30682)) * make the OpenAL backend build with OpenALSoft < 1.15 ([526701fa](https://github.com/qTox/qTox/commit/526701fa9f7a2a5c950e7b16b6578bf32bff4604)) * remove newline from timestamp ([0843b770](https://github.com/qTox/qTox/commit/0843b770c64204a078e0d73fe5f3b71b8ef17e52)) * fix sqlcipher header inclusion ([e7b5c93b](https://github.com/qTox/qTox/commit/e7b5c93bfe3d3fbcfe8659ab93c4d03492ddefb9)) * **chatform:** Hide author on history like on new messages ([28979f57](https://github.com/qTox/qTox/commit/28979f577165b0ce2a8e67b8eea870218bdd0b21)) * **dependency:** remove unused qt sql dependency ([5d159e02](https://github.com/qTox/qTox/commit/5d159e0203bdd0e72153cce9ca5c0fe8483ba0db)) * **desktop:** Improved the comment in qtox.desktop ([38ef3ad6](https://github.com/qTox/qTox/commit/38ef3ad69de0dfa3b3126f6c8fbfcb6392aa46f2)) * **exit:** delete heap-based QApplication before exit ([a601df31](https://github.com/qTox/qTox/commit/a601df31da0859255e1220d24f3c97eae9eb0cca), closes [#4648](https://github.com/qTox/qTox/issues/4648)) * **friends:** Friend request button target location ([7f36ec92](https://github.com/qTox/qTox/commit/7f36ec9219e93fbbe2b790efc68215f5ec8bfb4b), closes [#4631](https://github.com/qTox/qTox/issues/4631)) * **friendwidget:** Add correct index calculation ([ad58c176](https://github.com/qTox/qTox/commit/ad58c1767b2ab4ff8340076d5ede53aa6e889972)) * **group:** Fix crash on group quit ([e606d3cb](https://github.com/qTox/qTox/commit/e606d3cb5573d956aa4df58fbe39a21b8c423860)) * **i18n:** string `Call with %1 ended. %2` is no longer translatable ([56ac95da](https://github.com/qTox/qTox/commit/56ac95dad9a807b4799840d7b2c3b1c1c44869f2), closes [#4552](https://github.com/qTox/qTox/issues/4552)) * **ipc:** * Reorder initialize list ([43c2308b](https://github.com/qTox/qTox/commit/43c2308b7bd845aa1db572642858f9dde762a28a)) * Call processEvent on timer timeout ([c2140e21](https://github.com/qTox/qTox/commit/c2140e21ad9a7edc893191f5e49307fc05933959)) * **l10n:** use native language names ([817a9897](https://github.com/qTox/qTox/commit/817a9897c2e1289c7ceda19f0335582ec46ad343)) * **platform:** handle terminating POSIX signals ([32b97cb9](https://github.com/qTox/qTox/commit/32b97cb9278e40c92337468fb61751cdf87a0542)) * **popup:** Don't create two error popups for friend requests ([1f787cc4](https://github.com/qTox/qTox/commit/1f787cc475c804ef890dec7e24c51ba6c2a3f9c4), closes [#4633](https://github.com/qTox/qTox/issues/4633)) * **receipts:** Prevent double message send for received receipt ([e9d63397](https://github.com/qTox/qTox/commit/e9d63397e1008e57a23c763aaa418fc65f57577b), closes [#2726](https://github.com/qTox/qTox/issues/2726)) * **threads:** Always stop the camera device thread ([8522141b](https://github.com/qTox/qTox/commit/8522141b1e7db63dade973f1ba9048aecc7db41e)) * **tooltip:** Set font colour to black for beige tooltip background ([fc1ed101](https://github.com/qTox/qTox/commit/fc1ed10199a4f34faf38081bf9b1a58ee5bdf033), closes [#4641](https://github.com/qTox/qTox/issues/4641)) * **toxme:** Remove HTML tags from ID to un-break toxme integration ([5af1b4af](https://github.com/qTox/qTox/commit/5af1b4af73d05e8da4bbc174410f0854a810ecd6)) * **widget:** Add removing friends and group on main window close ([638cdba5](https://github.com/qTox/qTox/commit/638cdba58d4ed506edb6eb5fadbeae8fa0c22995)) * **windows:** * remove unneeded qt sql and fix problem when build dir exists ([68c5cdf0](https://github.com/qTox/qTox/commit/68c5cdf096fbdf5d18bad06b58241098f6873505)) * workaround docker problem when extracting with tar ([af15cd06](https://github.com/qTox/qTox/commit/af15cd06acf549df753abf9f8521e24de1586a07)) ## v1.11.0 (2017-07-16) The most interesting new features that were added in this release are export of history to plaintext file and outgoing call sound. #### Performance * Optimize open/close device ([d704f5d2](https://github.com/qTox/qTox/commit/d704f5d21d7b33fbb52f89d9d2a5386015e6a3ce)) * Pass std::function by reference ([365d703e](https://github.com/qTox/qTox/commit/365d703e8ab57fff4930062e2ecf65420dccebec)) #### Bug Fixes * Invoke device methods in deviceThread ([d86912ea](https://github.com/qTox/qTox/commit/d86912eacd10f9c6472e1ef920a258f0e1ef3b83)) * Fix warning about stack protection ([efcad35f](https://github.com/qTox/qTox/commit/efcad35fd53279b32b7b169af256c0ac14d6e9d1)) * Using foreach to iterate through CFLAGS_OTHER ([9a0632b0](https://github.com/qTox/qTox/commit/9a0632b0cf8bc78d1aae0f2e75b26b73cdb98e52)) * Replace hardcored roaming path on QStandartPath ([f616ff36](https://github.com/qTox/qTox/commit/f616ff36b6db9142f6e49b2b8bc436b6652aa329)) * Fix freez on circle removing ([4d15aed5](https://github.com/qTox/qTox/commit/4d15aed53fa8986747b450b42d23befc92600986)) * gcc-7 build ([246e23ab](https://github.com/qTox/qTox/commit/246e23abf3aa64c494c6abf42c83944eb78f1b16)) * **IPC:** Add update profileId in to IPC ([c2f82f78](https://github.com/qTox/qTox/commit/c2f82f7808ed075c9c31813151d2cf001c4c7d10)) * **bootstrap:** qrencode url ([f90da3d0](https://github.com/qTox/qTox/commit/f90da3d07acdf25e7e871ee8447412bd315c5f02)) * **build:** * disable linux specific stuff on windows ([a9d2b03c](https://github.com/qTox/qTox/commit/a9d2b03c7c8ea33e34c238e1079c66687a6e1ab0)) * when building without XSS extension ([ab622213](https://github.com/qTox/qTox/commit/ab62221375281ca1320bc7b437cbf33766827f94)) * add missing rcc options ([6b7c0c73](https://github.com/qTox/qTox/commit/6b7c0c738b8439df6d2966de4a1296d32805366f)) * **chatform:** add space for current copy link ([41a781d3](https://github.com/qTox/qTox/commit/41a781d3f875a0af51aaa4c727ce94c6115c48ad)) * **cmake:** fix icon for .exe file ([cf06cc7a](https://github.com/qTox/qTox/commit/cf06cc7a1d8c94965f6ed76ce3d499ce531d946e)) * **core:** * use correct byte representation when bootstrapping ([4e5b1915](https://github.com/qTox/qTox/commit/4e5b191553d2dcd14e744aa4c9e81d8f4e5f66a7), closes [#4385](https://github.com/qTox/qTox/issues/4385)) * Add action message with friend request text ([b25f5b5e](https://github.com/qTox/qTox/commit/b25f5b5ed69681a0549a7ff3377704536b34c19d)) * **emojis:** Add text variants for the blush emoji ([9a367ffb](https://github.com/qTox/qTox/commit/9a367ffbf010ad2698e32d2f1c24add9d6dbefb9)) * **macro:** Fixed clang warning about USING_V4L ([dd59f5ee](https://github.com/qTox/qTox/commit/dd59f5ee90b6b05bd810596cfa3ad55c9b449776)) * **main:** Use correct way to handle application quit ([0b5b3fcf](https://github.com/qTox/qTox/commit/0b5b3fcf1a91a8f163574a2650102fedead6431b)) * **message size:** Replaced TOX_MAX_*_LENGTH with API calls. ([3963d3c1](https://github.com/qTox/qTox/commit/3963d3c150fa4afe2fe69f493da6452c783a5655)) * **profile:** set maxLength on userName QLineEdit ([71a838b5](https://github.com/qTox/qTox/commit/71a838b53e75a239174a0de09988415f64325e36), closes [#4335](https://github.com/qTox/qTox/issues/4335)) * **qtox.pro:** add openal.h and openal.cpp ([d78a9790](https://github.com/qTox/qTox/commit/d78a979021578d6837f4ccb17a352b1ebfaeb8ff)) * **settings:** * set default values for some important settings ([da4f6222](https://github.com/qTox/qTox/commit/da4f6222d806ff74f799a7e380eca20ab9ff3342)) * compute toxcore version in runtime ([2d0a4e79](https://github.com/qTox/qTox/commit/2d0a4e794dd10818dc830c8384fc0f76f0184b7f)) * **ui:** * Use native file picker dialog ([42a9534b](https://github.com/qTox/qTox/commit/42a9534b24fe789b23e2ab07e3c6963b503ea91a), closes [#3494](https://github.com/qTox/qTox/issues/3494)) * wrong size of svg images on buttons ([5b0bf9fc](https://github.com/qTox/qTox/commit/5b0bf9fc9b31cedac5c8dccc51c96852a9a1e1ce)) * add date message before info messages ([ed453598](https://github.com/qTox/qTox/commit/ed45359863cf617d94e8f39506971a515c495304), closes [#4388](https://github.com/qTox/qTox/issues/4388)) * do not save splitter state in multiple windows mode ([7e5387ca](https://github.com/qTox/qTox/commit/7e5387cad283877c422b2e6d4a8bd9404fff67cd)) * Set `Qt::Window` flag in multi windows mode ([6b767e47](https://github.com/qTox/qTox/commit/6b767e476551f9f7bb771803a3f356f3e45a8d9e)) * switch to settings after multiple windows mode ([0c98f6a5](https://github.com/qTox/qTox/commit/0c98f6a5488cb8d8b27471f3cfecd6dd7f582300)) * restore splitter state ([9d94cd0c](https://github.com/qTox/qTox/commit/9d94cd0c507afdbf4b69c8adcdd82546a6bc83b8), closes [#4387](https://github.com/qTox/qTox/issues/4387)) * **v4l:** Fixed CMakeLists.txt for v4l on FreeBSD ([dfe696e2](https://github.com/qTox/qTox/commit/dfe696e25a26a163430aefff3ba7a75428050598)) #### Features * Add message count in tooltip to LoadHistoryDialog ([bb65a18d](https://github.com/qTox/qTox/commit/bb65a18de59677cbbe3f1568380c9149b4939466)) * **aboutform:** Add OS name in issue template ([7b3bd45d](https://github.com/qTox/qTox/commit/7b3bd45d5089aacf3b72b4206dbff922cdef6dbb)) * **audio:** * OutgoingCallSound ([a06ad704](https://github.com/qTox/qTox/commit/a06ad7048e7f2a450da455e16b5d907566c3eedb)) * split the audio interface from the backend library ([28c2298a](https://github.com/qTox/qTox/commit/28c2298ad97e8aec6097d63ead55214bac8152ba)) * **chatform:** * Highlight chat history ([3257770b](https://github.com/qTox/qTox/commit/3257770bfa61838ab398a31de28f623f7b28d77a), closes [#2296](https://github.com/qTox/qTox/issues/2296)) * Export chat history to file ([5e4ab769](https://github.com/qTox/qTox/commit/5e4ab76944ea5952b7123ca64102de4f50b4f4d3), closes [#4143](https://github.com/qTox/qTox/issues/4143)) * **l10n:** * add Tamil translation ([37a93042](https://github.com/qTox/qTox/commit/37a93042601965e59c47adaad60598e3046808c3), [bf405e17](https://github.com/qTox/qTox/commit/bf405e17fb63036c23621b8abc96692fe813eb6b)) * update Arabic translation from Weblate ([3ab423da](https://github.com/qTox/qTox/commit/3ab423dad25b185d8d8fefd5dae14c4acee05c62)) * update Belarusian translation from Weblate ([0c95284e](https://github.com/qTox/qTox/commit/0c95284ecc5b05586915d1573184baa5a0b9872c)) * update Bulgarian translation from Weblate ([79e00832](https://github.com/qTox/qTox/commit/79e00832f6c8ee1e700449a02ab8781ba27e4c06)) * update Chinese (Simplified) translation from Weblate ([06519872](https://github.com/qTox/qTox/commit/065198724cc9cb6d9718b4f3ec2dfbd6f2a1eca0)) * update Esperanto translation from Weblate ([dfdf11d1](https://github.com/qTox/qTox/commit/dfdf11d17d4a7b3957097fb14dd2a7469c28b5fb)) * update Estonian translation from Weblate ([1d9e646b](https://github.com/qTox/qTox/commit/1d9e646b199fa615ebafdebdd2ba5b6014495bd5)) * update Finnish translation from Weblate ([e10c5247](https://github.com/qTox/qTox/commit/e10c5247deadb85aed06c673e9d671918192e7d4)) * update German translation from Weblate ([1196731f](https://github.com/qTox/qTox/commit/1196731fa1ef3ff79ad43faa5fe33ecbf5b6548f), [1ce73175](https://github.com/qTox/qTox/commit/1ce73175b3afa55cdd2d773bf18c29515bc0a6d0), [20d31c81](https://github.com/qTox/qTox/commit/20d31c81c9b9bf2f91f0e6a4cd0ec46fff6fea85), [32cd745a](https://github.com/qTox/qTox/commit/32cd745af62be64ab385a9aeb69d873431a5e14a), [d8cd871e](https://github.com/qTox/qTox/commit/d8cd871e7e8e57b8aab1d32d5502f7cdb5cc97ee)) * update Greek translation from Weblate ([b2501ed1](https://github.com/qTox/qTox/commit/b2501ed114fdad88dbe64d2f2c346eab053584ea)) * update Lithuanian translation from Weblate ([57f94139](https://github.com/qTox/qTox/commit/57f941398aac060c0fbb90ede1e80847905ff1c4)) * update Polish translation from Weblate ([5e432c5e](https://github.com/qTox/qTox/commit/5e432c5ecfb23d9fb78a76c1291f2d7f08c727e1)) * update Romanian translation from Weblate ([38374604](https://github.com/qTox/qTox/commit/383746043fa4c54a80769b2d57980e8785c5dda8)) * update Russian translation from Weblate ([317f250e](https://github.com/qTox/qTox/commit/317f250e68524f68c5f15a68bb722e0fe88fc76d), [3a252c9e](https://github.com/qTox/qTox/commit/3a252c9e96568a537baa24997a017efa8d4424d6), [d5831060](https://github.com/qTox/qTox/commit/d583106008bceb1e42624bd0dba007de7966d6f9)) * update Slovak translation from Weblate ([e8e1d41f](https://github.com/qTox/qTox/commit/e8e1d41ff56d45ae2e87961b19fe637aa561534f)) * update Spanish translation from Weblate ([0cd34485](https://github.com/qTox/qTox/commit/0cd3448508d0839919c07a677f5b643d111607c3)) * update Swedish translation from Weblate ([040f14b3](https://github.com/qTox/qTox/commit/040f14b38ee3b531b9e34ae0f50be1b93d3a247e)) * update Turkish translation from Weblate ([fd8decd5](https://github.com/qTox/qTox/commit/fd8decd52e10c8b128a757e2034ebd1aa1a37f78)) * update Ukrainian translation from Weblate ([bcc3c0ef](https://github.com/qTox/qTox/commit/bcc3c0ef92ef27c8dcd7284a6cfbf98cf6801c8f)) * **toxid:** Show NoSpam and checksum parts of ToxID in colors. ([c0951a56](https://github.com/qTox/qTox/commit/c0951a5675bf6c8f6d2c4b05d18f48b88adae6a5)) ## v1.10.0 (2017-05-07) The more prominent new features are an option to autojoin groupchats and ability to select messages with triple click. More about new stuff below. #### Features * stop using plaintext passwords in the code ([084f3b06](https://github.com/qTox/qTox/commit/084f3b06262b2ca8272238b2ec450d11840d7551)) * **audio:** don't use dynamic memory allocation and remove resource leak ([b68ad4ce](https://github.com/qTox/qTox/commit/b68ad4ced2ce985c4e53b1469681ed3ec807d2b6)) * **chatform:** mark message with triple click ([3acbc148](https://github.com/qTox/qTox/commit/3acbc148f51afc96bf5bd96e88da053bdd64f702)) * **groups:** add option to automatically accept groupchat invites ([6a16a2bd](https://github.com/qTox/qTox/commit/6a16a2bdbc94f9eb20386794652f8a1766498a00)) * **l10n:** * add Romanian translation to UI ([0192e927](https://github.com/qTox/qTox/commit/0192e9275f1ceb1cc4caa0b810f87da92d56b75a)) * add Romanian translation using Weblate ([8b22e962](https://github.com/qTox/qTox/commit/8b22e962edc1f3779525f678a2b1f1f99415c7f5)) * update Belarusian translation from Weblate ([57615230](https://github.com/qTox/qTox/commit/576152305aabb64b9b05e705f0eb0dfcb68c9329)) * update Chinese (Simplified) translation from Weblate ([9d6be415](https://github.com/qTox/qTox/commit/9d6be4151073762f323b497a9379cb67e5747898)) * update Esperanto translation from Weblate ([2c158eb5](https://github.com/qTox/qTox/commit/2c158eb57c0ed5dd50420589b8f706f99ea9b5af)) * update Estonian translation from Weblate ([e580edd1](https://github.com/qTox/qTox/commit/e580edd184c3426669014a036e7dd23db3c3dcae)) * update Finnish translation from Weblate ([2f8e8b84](https://github.com/qTox/qTox/commit/2f8e8b840b426c27539e1b2e40caa890343ad481)) * update French translation from Weblate ([0748b6c6](https://github.com/qTox/qTox/commit/0748b6c6c6abf16a1069c880554a1438ca268ea7)) * update French translation from Weblate ([38bb82e5](https://github.com/qTox/qTox/commit/38bb82e59bdde27c62b8d836dfae47283c3cfd22)) * update German translation from Weblate ([1dfb4a63](https://github.com/qTox/qTox/commit/1dfb4a630b865f7f0e17a1d948eb7e71eee98ddc), [87d66c01](https://github.com/qTox/qTox/commit/87d66c019c5607fdcc8d68167202967aa8f6f753)) * update Greek translation from Weblate ([6e6d32a7](https://github.com/qTox/qTox/commit/6e6d32a7405a28fb76f28a542d90e7318ad02a40)) * update Lithuanian translation from Weblate ([c970e065](https://github.com/qTox/qTox/commit/c970e065f681d8447c80c442df0f405f42ef1b97)) * update Russian translation from Weblate ([e6b76561](https://github.com/qTox/qTox/commit/e6b76561062e85633305d0653cdb01c3c50dba88)) * update Slovak translation from Weblate ([63d5caf3](https://github.com/qTox/qTox/commit/63d5caf37c03401398256a2443f10adc4b488850)) * update Spanish translation from Weblate ([615c3614](https://github.com/qTox/qTox/commit/615c3614974678b2e4a9fca7af015ec65bcde1b0)) * update Swedish translation from Weblate ([3145949c](https://github.com/qTox/qTox/commit/3145949cc27ed4445c6435726eb4e0f9b1eb655f)) * update Ukrainian translation from Weblate ([9dd40bec](https://github.com/qTox/qTox/commit/9dd40bec63b7470bdb455129a1d1e5a2ec60751c)) * **ui:** grey out proxy settings if no proxy selected ([77aa2e92](https://github.com/qTox/qTox/commit/77aa2e92520de1323a3ac8ef9a154d0fdf041c0b)) #### Bug Fixes * fix segfault while trying to send messages ([b5f4628c](https://github.com/qTox/qTox/commit/b5f4628c8984d063cac385e5f2ae57e7371e58bd)) * fix OpenAL name ([8a77723a](https://github.com/qTox/qTox/commit/8a77723a893e0211fd1f02e099408e8a92aa8f1c)) * fixed segfault after accepting group invite ([532e05cb](https://github.com/qTox/qTox/commit/532e05cb0f135d763890f4146e14da036cc3b248)) * set CSS font-weight of editbox always to normal ([96b2977a](https://github.com/qTox/qTox/commit/96b2977a5f4ddad2be1d7d22b4628ff960f174aa)) * remove useless line that caused a warning ([e9515f48](https://github.com/qTox/qTox/commit/e9515f4898aedfd9c70094d61a6f3b85debb9e3e)) * fixed wrong formatting for multiple URL's in one message ([08208e9a](https://github.com/qTox/qTox/commit/08208e9aa515270a190ff85a1ba704c8a130c5cd)) * silence various warnings ([f0cec44a](https://github.com/qTox/qTox/commit/f0cec44a8cb91a9867b5761fb3703a90691b892b)) * Stop using deprecated avcodec_decode_video2 ([f07daaf1](https://github.com/qTox/qTox/commit/f07daaf1595e320a19408321fcb62fe76d48d118)) * Remove unnecessary calls to av_frame_unref ([91414b4f](https://github.com/qTox/qTox/commit/91414b4fcef944765507112583787b887dd62966)) * Stop using deprecated AVStream::codec ([9674bff0](https://github.com/qTox/qTox/commit/9674bff0cfe05fcd5b667afca6df5b214644939d)) * silence warning by not using dynamic arrays on stack ([300ac12e](https://github.com/qTox/qTox/commit/300ac12e3f220265fdd4685efb8e75e9da0ef3cd)) * **UI:** * rename `qTox.desktop` → `qtox.desktop` to fix displaying icon ([6de129fe](https://github.com/qTox/qTox/commit/6de129fef1122c20d356cfb5fe504cb2f0999128), closes [#4323](https://github.com/qTox/qTox/issues/4323)) * improve unclear message about text being resized in chatform ([c8ed535b](https://github.com/qTox/qTox/commit/c8ed535bbad3300c02871cbc718bee03accc6c26)) * **chatform:** disable Tab in add friend message text area ([195e891d](https://github.com/qTox/qTox/commit/195e891da156b87c4973b13e5decfb95445d885f)) * **core:** * add missing nullptr check ([407413c6](https://github.com/qTox/qTox/commit/407413c65b1d7a43a1788e00935c02fa2d9ffe33)) * don't continue when the tox file is corrupted ([20db8b74](https://github.com/qTox/qTox/commit/20db8b740bcccd0889b4d19210f98c1f902b87bd)) * **groupinvite:** prevent multiple groupinvites to the same group from showing up ([13029e30](https://github.com/qTox/qTox/commit/13029e3047cf021ff98ea816008e0f514b79fc0c)) * **includes:** Added missing #include ([e597d391](https://github.com/qTox/qTox/commit/e597d391fcd9b1630ae2b049b361549d4a34f9fc)) * **profile:** toxsave wasn't correctly encrypted ([5b31cf6d](https://github.com/qTox/qTox/commit/5b31cf6d9d188d064b7151cdb21959b064c0cef6)) * **settings:** * remove legacy code ([6a6e30d6](https://github.com/qTox/qTox/commit/6a6e30d6393f29b0e15b61d31267bcd60a9c8426)) * changing language sets title to "Add Friend" ([4886868e](https://github.com/qTox/qTox/commit/4886868eceec38bdb13031f36fb916828a399f0a)) * **ui:** fixed typos of pseudo-states in stylesheets ([f35103e5](https://github.com/qTox/qTox/commit/f35103e547b517106cb5048b43dfe6dece5fe8d8)) * **widget:** Add friendWidget deletation ([c7202c8b](https://github.com/qTox/qTox/commit/c7202c8b243453d9630a51d7de1f745b4b865abb)) ## v1.9.0 (2017-03-19) The most noticeable change to UI are moved buttons in file transfer widget, lessening chance of an accidental cancellation of a file transfer. #### Bug Fixes * Fix incorrect headers order ([0fc39113](https://github.com/qTox/qTox/commit/0fc39113183afcf1bc0750b8727a48634d8a623a), closes [#4220](https://github.com/qTox/qTox/issues/4220)) * emoji packs path in user manual ([a3e64618](https://github.com/qTox/qTox/commit/a3e646182a719edd135bcf7308e7b37a27ab09f9)) * URL's are not affected by markdown ([18c3f3a2](https://github.com/qTox/qTox/commit/18c3f3a2170f32f1045a92290456a86737eb5254)) * iterate all blocks ([7a5c5a86](https://github.com/qTox/qTox/commit/7a5c5a86fd82ab9812db019db2e98d700e08a900)) * fixed documentation mistake ([e8ed7e1f](https://github.com/qTox/qTox/commit/e8ed7e1f530b380d0b676933e6298327aeeb4398)) * Add splitter restorer ([a231532b](https://github.com/qTox/qTox/commit/a231532bd06a4a47432b5bc20d6d34028ca66941)) * changed incorrect videocall button offline tooltip ([0effac2b](https://github.com/qTox/qTox/commit/0effac2bdd8834bd3b1edcc91f3c8ffed03abfe6)) * Autoupdater trying to open files twice could fail ([20ff68c3](https://github.com/qTox/qTox/commit/20ff68c3a69ec8c92d06b8e36b8e8443d76e094f)) * msleep in toxuri processEvents loops, to avoid 100% CPU ([2d0698af](https://github.com/qTox/qTox/commit/2d0698af95959a895e1cb86721d288d8ef5f3bae), closes [#1926](https://github.com/qTox/qTox/issues/1926)) * Don't even try to add ourselves as a friend in the Tox URI handler ([bfda028a](https://github.com/qTox/qTox/commit/bfda028a2a3ef63181dbc8ffa42817e08e616f20)) * Various IPC event handling and related bugs on startup ([c75ee8a6](https://github.com/qTox/qTox/commit/c75ee8a6619e6c546121462d28e221bb8df11f19), closes [#1926](https://github.com/qTox/qTox/issues/1926)) * Friend list avatars not updating ([055c41bc](https://github.com/qTox/qTox/commit/055c41bc30de6901b63d14dbc50e384de5779d16)) * Accept IDs as tox URIs, not just ToxDNS addresses ([1d307bcc](https://github.com/qTox/qTox/commit/1d307bcc0e3bde3282615f5f2db08578e7cf4984), closes [#1925](https://github.com/qTox/qTox/issues/1925)) * **UI:** actually load bundled Unicode font as soon as possible ([bfd47733](https://github.com/qTox/qTox/commit/bfd47733c241677163e38b0bc28a9de87f4dc535)) * **audio:** alternate audio fix implementation from #4139 ([62ac4801](https://github.com/qTox/qTox/commit/62ac480171e6b073689344ee8eb65430485166de)) * **cmake:** Add ability to real disable optional dependency ([fb43e306](https://github.com/qTox/qTox/commit/fb43e306774db7b040fde550c9d4c479793d1bad)) * **core:** Ignore online connection status ([ea50eaae](https://github.com/qTox/qTox/commit/ea50eaaef742141ec2023050d5d1ee8719583333)) * **db:** Made RawDatabase::execLater executes statements asynchronously. ([54fb9f73](https://github.com/qTox/qTox/commit/54fb9f73ccb70735f0b551faaa2e713004a3864d)) * **font:** * Made the font combobox not editable. ([ba28f163](https://github.com/qTox/qTox/commit/ba28f163fdd243998f0962cf4ad2ca5e5134d9f5)) * Made font changes in settings apply on screen instantly. ([742583bb](https://github.com/qTox/qTox/commit/742583bb9ed11eebea2686f985b7dd254f5a6d89)) * **icons:** Removed unnecessary icon preparation. ([fac0021a](https://github.com/qTox/qTox/commit/fac0021a4752bca7d5a72fc2c1c464d59ba81754)) * **qmake:** add forgotten files to qTox.pro ([db2e2d7a](https://github.com/qTox/qTox/commit/db2e2d7ab77836d65c27a57ece69220eeb17e0e9)) * **toxencrypt:** return the plaintext after decryption ([14ee8af3](https://github.com/qTox/qTox/commit/14ee8af3cd156437b37ada4d06b07ccfbd2d3d4a)) #### Features * Switch windows portable links to stable versions ([b771f87e](https://github.com/qTox/qTox/commit/b771f87e0becbc649a5a3f0823efe153b6ffe2cb)) * Add version numbers in windows installer download links ([b73770d7](https://github.com/qTox/qTox/commit/b73770d703e98d47e9635918ad635eadcbbe2852), closes [#4112](https://github.com/qTox/qTox/issues/4112)) * **UI:** Move filetransferwidget buttons side-by-side. ([9322f29e](https://github.com/qTox/qTox/commit/9322f29ef4d6b45fb66623ef0655a496a3064b78), closes [#2597](https://github.com/qTox/qTox/issues/2597)) * **l10n:** * add Chinese (Taiwan) translation to UI ([43d16d91](https://github.com/qTox/qTox/commit/43d16d91817f1224702b55aa2d6d53fb2aabd242)) * add Chinese (Taiwan) translation using Weblate ([b80e964d](https://github.com/qTox/qTox/commit/b80e964d18763fcb5b3f2699789d17e95607bd9c)) * update Belarusian translation from Weblate ([6fe20861](https://github.com/qTox/qTox/commit/6fe20861485e1261a4da67b15212825cd5fd5126), [a335e32a](https://github.com/qTox/qTox/commit/a335e32af50788e2e94cd4a1db65344f62e7323f), [f92bbbb9](https://github.com/qTox/qTox/commit/f92bbbb9dd4b37535e80e7687935958d43592299)) * update Bulgarian translation from Weblate ([6df92b73](https://github.com/qTox/qTox/commit/6df92b73629933f1d0a82da1b6ff4e7f8193670b)) * update Chinese (China) translation from Weblate ([95881c97](https://github.com/qTox/qTox/commit/95881c975dee764f1c545da99ddfcd9013ed54ac), [bb3ee7d0](https://github.com/qTox/qTox/commit/bb3ee7d040edfe0389461f677cff0d691f5e7071), [e0cb8e5f](https://github.com/qTox/qTox/commit/e0cb8e5f530cfaeb640ab3754b36f8e8a45f789a), [fbe0de9b](https://github.com/qTox/qTox/commit/fbe0de9bbbce117f81c6ce2f52ac29fcd9854543)) * update Chinese (Simplified) translation from Weblate ([ae676246](https://github.com/qTox/qTox/commit/ae676246106f4890f34099840a92536d4831b92d)) * update Chinese (Taiwan) translation from Weblate ([423fb83c](https://github.com/qTox/qTox/commit/423fb83c9f80a6b01f00ea980fc67e7fd1c0fbdb)) * update Chinese (Traditional) translation from Weblate ([9fadde67](https://github.com/qTox/qTox/commit/9fadde67a14fc4d125bab9e5f285ee42206f2bcc)) * update Czech translation from Weblate ([99d34b04](https://github.com/qTox/qTox/commit/99d34b049db7709c42cb0bf074aff9db1ab7d11e)) * update German translation from Weblate ([015c8b00](https://github.com/qTox/qTox/commit/015c8b009264cd978b2d50ddab2460ec5697d6e6), [036ea14b](https://github.com/qTox/qTox/commit/036ea14b2689d39d89e9cd35782536048227b806)) * update Greek translation from Weblate ([2551baab](https://github.com/qTox/qTox/commit/2551baab46415cea6f0f9bca48f65189cb201cc3), [cebfe4a6](https://github.com/qTox/qTox/commit/cebfe4a63bfcda211b366a9fe61f500fee898760)) * update Italian translation from Weblate ([c6ca1518](https://github.com/qTox/qTox/commit/c6ca15188384993863dce217e3de815407be7084)) * update Korean translation from Weblate ([9fca7365](https://github.com/qTox/qTox/commit/9fca736506454779892b447eeaee8fdaf2e42f5e)) * update Lithuanian translation from Weblate ([77ee1787](https://github.com/qTox/qTox/commit/77ee1787b11687bfc467880c720768c472c3a9f1)) * update Pirate translation from Weblate ([6d2cf522](https://github.com/qTox/qTox/commit/6d2cf5222b83ed58562a766fafd0dac546c2ead8)) * update Russian translation from Weblate ([17413c08](https://github.com/qTox/qTox/commit/17413c08641fddbc872176bc03f16807d6307531), [4d22b7cc](https://github.com/qTox/qTox/commit/4d22b7cc97401e447947e1150de8414e89e951eb), [c2b7d7a5](https://github.com/qTox/qTox/commit/c2b7d7a533838ae8382bc42f953e15470149f3e2)) * update Slovak translation from Weblate ([a6665fe0](https://github.com/qTox/qTox/commit/a6665fe028cabb1269f0143bc2ebfda189b63427), [cf5e7f3a](https://github.com/qTox/qTox/commit/cf5e7f3ad194f171ffbd835a01ac20861fffba65)) * update Swedish translation from Weblate ([8eeee452](https://github.com/qTox/qTox/commit/8eeee4526c83b1b043bd517c0e5152e6fcd3b480), [c5067f65](https://github.com/qTox/qTox/commit/c5067f65d1845cbc89b4b4910283183807c45c5b)) * **settings:** create ui when needed instead of show/hide ([12bcc261](https://github.com/qTox/qTox/commit/12bcc26154722a1f5028ae55709830c89a3d3b69)) * **shortcut:** Implemented F11 shortcut for toggling fullscreen. ([3a20a4ba](https://github.com/qTox/qTox/commit/3a20a4ba43e609c96385f25c1a9a20e55cbfa4fa)) * **toxencrypt:** add function to get min length for isEncrypted() to succeed ([a5955c67](https://github.com/qTox/qTox/commit/a5955c67ede3de72d4b9b76730216bdcc8f62334)) * **toxpk:** Add 'ToxPk::getPkSize()' method ([f5c0d61a](https://github.com/qTox/qTox/commit/f5c0d61ae6cda2106c9d88ef7050b4003e725489)) ## v1.8.0 (2017-02-05) With this version build system has been migrated to `cmake` and `qmake` support is now deprecated and will be removed in the `v1.9.0` release. #### Bug Fixes * Now cannot send party invite to a friend which has "Offline" status ([034c507c](https://github.com/qTox/qTox/commit/034c507cc4fa83210a40b92c5aa458e9daf5ba5a)) * Add workaround for FreeBSD and ICU ([22d3aa11](https://github.com/qTox/qTox/commit/22d3aa11debfc54ad32f3a4642ea81da058c0ee2)) * Remove old ChatForm code from Widget ([52642578](https://github.com/qTox/qTox/commit/52642578a0d66c79c03b6dbfe0ab2af86a30302b)) * Further Windows Jenkins link errors ([b6934927](https://github.com/qTox/qTox/commit/b693492702a5f65d278baf8d38b4f1a9ea537089)) * Jenkins Windows builds ([debb6e9c](https://github.com/qTox/qTox/commit/debb6e9cc897de62687ad39880059472d0eb8e48)) * fix warning about missing (removed) `cdata.h` ([4972ac1d](https://github.com/qTox/qTox/commit/4972ac1d537bab37aa73635c49067d75913f5662)) * Fix problem with unicode symbols on FreeBSD ([56d65650](https://github.com/qTox/qTox/commit/56d656502c82d81e5339fad7eb7bc77545adea8b)) * **UI:** fix own status message not being properly displayed in tooltip ([dccef4d4](https://github.com/qTox/qTox/commit/dccef4d49f62d10427f4a625a9595da881a1370d)) * **addfriend:** * create ToxId from QString before emitting the signal ([a5d2c573](https://github.com/qTox/qTox/commit/a5d2c573ddca775567024ee9e64fe735146b1807)) * do not load own Tox ID from clipboard ([efcff576](https://github.com/qTox/qTox/commit/efcff57601a3dc04fc7376b6edbeff81487dc5b8)) * **avatar:** wrong conversion of own ToxId ([0e4cff88](https://github.com/qTox/qTox/commit/0e4cff884db58e31e7a269721bc9e6138174b9b0)) * **build:** * avoid version checking where unnecessary ([bf8b23e3](https://github.com/qTox/qTox/commit/bf8b23e373275f0d34d99ff79d0637f2fb558d18)) * do not invoke version update on travis ([07639011](https://github.com/qTox/qTox/commit/07639011daa5ff84b61e76b0ad0dc8e659c5a68b)) * attempt to make travis happy ([919a94b9](https://github.com/qTox/qTox/commit/919a94b978aa957ed3b7702600b90f72a952eb7e)) * set generic macOS Info.plist version by default ([3a88abdd](https://github.com/qTox/qTox/commit/3a88abddd7cb96bbff48d5c6601798173328590f)) * correct macOS deployment ([8912e9e0](https://github.com/qTox/qTox/commit/8912e9e016d7591eb1a1d1f782c90bd8da7b16c6)) * add an option to compile with autotools toxcore on Windows ([482757e6](https://github.com/qTox/qTox/commit/482757e6a2ab74410f33fca364af1b69f2695ae4)) * fix ASLR and DEP protection for windows builds ([ce0590d2](https://github.com/qTox/qTox/commit/ce0590d2d0788a3d0cb9318211e3c79761b49492)) * **chatform:** * Add ability to cancel call ([320099fa](https://github.com/qTox/qTox/commit/320099faf80ca51e60c68953463a60b50b23e4c2)) * Fixed buttons ([aff0c6f3](https://github.com/qTox/qTox/commit/aff0c6f3a5a6f883329b4e20598ff72852fe2cca)) * **clipboard:** Made clipboard operations work the same on all OSes. ([a18e0b6a](https://github.com/qTox/qTox/commit/a18e0b6a2cbea94229598c9e777ceea1aa46fff7)) * **cmake:** * Add search for toxcore or libtoxcore ([a4537549](https://github.com/qTox/qTox/commit/a4537549c0c53a88a47ca7a06cf276f0430eb43e)) * Fix smileys resource adding ([fddda5de](https://github.com/qTox/qTox/commit/fddda5de61d302abe3f075dc35ef296c8a29a82f)) * **font:** Fixed the editbox font reset problem ([c84837d6](https://github.com/qTox/qTox/commit/c84837d662b6838a55caae7b3d4a121abd2d0192)) * **icon:** * add more size versions to qtox.ico ([ec95d7f5](https://github.com/qTox/qTox/commit/ec95d7f571431a8d46158266123cd0806fc4fdc3)) * correct the shape of the logo ([f258cf20](https://github.com/qTox/qTox/commit/f258cf20e243c7b426cb4096108924871b4958d0), closes [#4114](https://github.com/qTox/qTox/issues/4114)) * **icons:** install icons for linux ([6b513819](https://github.com/qTox/qTox/commit/6b51381980e4d742efff0905a697040efbcce049)) * **jenkins:** preserve the correct linking order ([5d4609bd](https://github.com/qTox/qTox/commit/5d4609bd0ee7854b27791c856ca7de93491232d1)) * **l10n:** rename `zh` translation to `zh_CN` ([1df9f572](https://github.com/qTox/qTox/commit/1df9f5726dcd2605245f4164c7462922ef9464fb), closes [#4080](https://github.com/qTox/qTox/issues/4080)) * **nexus:** Add qRegisterMetaType call for ToxPk ([328d0b6d](https://github.com/qTox/qTox/commit/328d0b6ddfe4f74ab701d926503860c6b1c77c1a)) * **osx:** Remove useless '#ifdef Q_OS_MAC' ([f0467abc](https://github.com/qTox/qTox/commit/f0467abc34f3dc2308b9cd4473550e8f1d4ce787)) * **toxid:** Tox ID construction from ToxMe ([a223510c](https://github.com/qTox/qTox/commit/a223510cf7b8ca72dce7b2f3d6ed9c6ca2284fab)) * **warnings:** fix some warnings about the stack protector not working ([e7276e7b](https://github.com/qTox/qTox/commit/e7276e7b43dd7b8631074866c27dfd70e40ae9f5)) #### Features * **UI:** remove unnecessary spacings on the Profile page ([1fc412df](https://github.com/qTox/qTox/commit/1fc412df72156b264f77e62ba395fe25eeb39514)) * **chatform:** Changed mic and vol buttons ([5381d55e](https://github.com/qTox/qTox/commit/5381d55e942bb08ce6ff870626a0e10bfaa2358c)) * **l10n:** * update Belarusian translation from Weblate ([1494fbe4](https://github.com/qTox/qTox/commit/1494fbe4e1c3399b1b7cacde7dc4189c87c89763), ([f938fef2](https://github.com/qTox/qTox/commit/f938fef23366681114099a6518b0612262681bac)) * update Chinese (China) translation from Weblate ([65f29811](https://github.com/qTox/qTox/commit/65f29811e39de1cdf9402c0e68cda905f2e87821), [cf6e83ba](https://github.com/qTox/qTox/commit/cf6e83baf1bdd3a689ac9fd5c0bf652ce6cc736d)) * update Czech translation from Weblate ([9676e8c9](https://github.com/qTox/qTox/commit/9676e8c97a3ba7c2a12c9c4511d1db75c3006c04), [b7ec9016](https://github.com/qTox/qTox/commit/b7ec90167e89679552b4953ac3bcbb602c6dae4e)) * update Estonian translation from Weblate ([3d639e00](https://github.com/qTox/qTox/commit/3d639e007c235ca556aaed69bc88e6cbc5434dc3)) * update Finnish translation from Weblate ([48ebc821](https://github.com/qTox/qTox/commit/48ebc821d8568831a55322ee6b2988bcb12f8fff)) * update French translation from Weblate ([275fdc6a](https://github.com/qTox/qTox/commit/275fdc6aa08222e0441f042ccfa50cc1c1922c68), [c990dfd7](https://github.com/qTox/qTox/commit/c990dfd7cf6090d2e68ee29990853061cca12d83), [cd2b1b1a](https://github.com/qTox/qTox/commit/cd2b1b1ae62d6fc0ac27b860a78cced7acbacafb), [d82f0670](https://github.com/qTox/qTox/commit/d82f0670e301edee962d1ccb5f177da80a23d9a3), [edab0fe3](https://github.com/qTox/qTox/commit/edab0fe3fd87df673b8563ac115d34d42c92ed9c)) * update German translation from Weblate ([2c8c5c52](https://github.com/qTox/qTox/commit/2c8c5c5259387a57c4de5e80e08227523a419478), [6a492ea4](https://github.com/qTox/qTox/commit/6a492ea4277e07c468fbfce74252b0c384962502)) * update German translation from Weblate ([ac7352db](https://github.com/qTox/qTox/commit/ac7352db0574935b43116924c0bbda8e9757da01), [e5f1cdcb](https://github.com/qTox/qTox/commit/e5f1cdcbc42183d088fe8dfc3433a11b9497b825)) * update Hungarian translation from Weblate ([29715335](https://github.com/qTox/qTox/commit/29715335782137e985e408f412ccf3e5dfeba043)) * update Korean translation from Weblate ([c8660c8b](https://github.com/qTox/qTox/commit/c8660c8b80238a8698a9f6ccc74aeca7bfac90f4)) * update Lithuanian translation from Weblate ([7fab1632](https://github.com/qTox/qTox/commit/7fab1632d501693611c6db762f4454773f9ad336), [c6fa538e](https://github.com/qTox/qTox/commit/c6fa538ee547c27dd03dfcd37ae7a7d5f09131ed)) * update Pirate translation from Weblate ([d570c2b3](https://github.com/qTox/qTox/commit/d570c2b3cd213a6c4a8b0618e222e4e1d5683048)) * update Polish translation from Weblate ([a6f52704](https://github.com/qTox/qTox/commit/a6f5270461db2dbd25f8589fe4dcbdfb5478aef3)) * update Russian translation from Weblate ([5e04b72a](https://github.com/qTox/qTox/commit/5e04b72afd1261f787551930e0f3164b44e81891), [8bcaa221](https://github.com/qTox/qTox/commit/8bcaa221c8545301b462daa49b39af4b1d7e44df), [8c868e2c](https://github.com/qTox/qTox/commit/8c868e2cf587f7e465e550c29235ebb20ae2abf7), [ce69f2b9](https://github.com/qTox/qTox/commit/ce69f2b997ea6d8c19d8479964c8a33da949b8ce)) * update Slovak translation from Weblate ([3f0ee63e](https://github.com/qTox/qTox/commit/3f0ee63e02c842e0e10570f25f2146355ee841e8), [4278d165](https://github.com/qTox/qTox/commit/4278d16554f5e0efb1ae427582e39bbfffeec75b)) * update Spanish translation from Weblate ([09727af0](https://github.com/qTox/qTox/commit/09727af00b17ad3427ddbba1dc916765191a1725)) * update Swedish translation from Weblate ([bd965086](https://github.com/qTox/qTox/commit/bd9650862f9230c8eccef60d40a69fa0a15dcf71)) * **toxid:** * reduce passing Tox IDs around ([e07d8d35](https://github.com/qTox/qTox/commit/e07d8d358f6fc890a77e029aa230b69bdecd325e)) * Improve validation of Tox IDs ([d196611f](https://github.com/qTox/qTox/commit/d196611ffeeccc200d42047c85af3f7fe93f0730)) * Improve the ToxId class ([94ec5614](https://github.com/qTox/qTox/commit/94ec5614f2bb50d7d487d5b933393ed0e3c05e03)) * Added correct checksum validation. ([b4c45692](https://github.com/qTox/qTox/commit/b4c4569299d734d8056e48a6d6dd62f4df2ae86f)) ## v1.7.0 (2016-12-25) #### Breaking Changes * **database:** use own public key as salt to encrypt the database ([c4b9d302](https://github.com/qTox/qTox/commit/c4b9d302d0c3007102436f7f76498a7a0b8edcaf)) #### Features * Added Kiss smiley shortcut for Universe and emojione ([059bb9a3](https://github.com/qTox/qTox/commit/059bb9a3a2ebf4c4310723ab386babc96c77cc15)) * **UI:** * make font in message input field use font settings ([c14cbdf4](https://github.com/qTox/qTox/commit/c14cbdf4fd99352d1a70ad0c9dfeb78e309b1228)) * add `My` to text labels on Profile page ([785e546e](https://github.com/qTox/qTox/commit/785e546e9e76144f2f39135cd425e0d924c960dd)) * make information text in settings selectable ([e9778e81](https://github.com/qTox/qTox/commit/e9778e81a238d2688ba5106b602d08208a9311a9)) * make it possible to select text of the received friend request ([06606958](https://github.com/qTox/qTox/commit/0660695873ef8ab1f5e31761c113065560d3a75b)) * Add 'Copy link' context menu item ([a7de2680](https://github.com/qTox/qTox/commit/a7de2680d971337c697357c052dc90ddb468883f), closes [#927](https://github.com/qTox/qTox/issues/927)) * make link to filled issue template on About page visible ([feaedeed](https://github.com/qTox/qTox/commit/feaedeed841f419a8eb5b7f40a14e27ef1e104cb)) * add accessibility labels to all remaining .ui files ([25e9d3c4](https://github.com/qTox/qTox/commit/25e9d3c46cf717bd623be1bda8dbc7a8541d84e1)) * add accessibility labels to the add friend page ([8854589b](https://github.com/qTox/qTox/commit/8854589b0d3ddc50bf899413c1ef970c43d127c4)) * add accessibility labels to the Profile page ([949e3cb8](https://github.com/qTox/qTox/commit/949e3cb830134a1412b93d751a1d16dd80877aba)) * add some accessibility labels to login pages ([953fe241](https://github.com/qTox/qTox/commit/953fe2416a97ebdeb6de45a801e66fc3b434d551)) * rename `Toxme register` → `Register on ToxMe` ([2433fa9d](https://github.com/qTox/qTox/commit/2433fa9dcee8c10a9d8ec050b8bf06c0facdce7e)) * **database:** * make a backup before upgrading ([c1d471fa](https://github.com/qTox/qTox/commit/c1d471faa18ecb8f04001b97937e14d6c34a9a46)) * use own public key as salt to encrypt the database ([c4b9d302](https://github.com/qTox/qTox/commit/c4b9d302d0c3007102436f7f76498a7a0b8edcaf)) * **l10n:** * add Korean translation ([6e040b8e](https://github.com/qTox/qTox/commit/6e040b8e7c50b53b13c8bd46a6b38df7b34a0e33), [5267b195](https://github.com/qTox/qTox/commit/5267b19503a815a269b4cc7bc4fb77b270157467)) * add Pirate translation ([93feae0c](https://github.com/qTox/qTox/commit/93feae0c95bb9a6b3a8bbe1022c403e8167bc7b3), [c75f0b74](https://github.com/qTox/qTox/commit/c75f0b74a29449dac1c3320e4ea2c1702b03c4c9)) * add Swahili translation ([f28e136e](https://github.com/qTox/qTox/commit/f28e136ec45b0f7ec26c4ebaa6e7eef7b39f9c0d), [8b671323](https://github.com/qTox/qTox/commit/8b67132337f3c90a9df8ed0ed06a7021386b6774)) * update Arabic translation from Weblate ([1969084b](https://github.com/qTox/qTox/commit/1969084baa892a2fab957f5c326973efd0b938d8), [b0d03622](https://github.com/qTox/qTox/commit/b0d03622b2a244cef4fbe8902aca53efcbebd624)) * update Belarusian translation from Weblate ([10b09036](https://github.com/qTox/qTox/commit/10b09036ca9dab7a9670b5b421897bbc23250d62), [899d74f1](https://github.com/qTox/qTox/commit/899d74f1d95136eb9e97d1ebdcbcd0fde46753f5), [a3c876c1](https://github.com/qTox/qTox/commit/a3c876c14af4a9fa581c7c627b81a7ec1fe821c5), [b8aef1dc](https://github.com/qTox/qTox/commit/b8aef1dcebae378eb08c245ffb6d83cb3876e3b0), [bb445a8e](https://github.com/qTox/qTox/commit/bb445a8e7155b18683ff77235ba64428b1b20069), [fa550a12](https://github.com/qTox/qTox/commit/fa550a127f2a4bc9eb3c4f2e4f466ba773845a5b)) * update Bulgarian translation from Weblate ([595657c2](https://github.com/qTox/qTox/commit/595657c232f0826170c61ae605b831c495b58d34), [88049cc2](https://github.com/qTox/qTox/commit/88049cc2277810f0fa5e30c65c4732f4aa25db04)) * update Chinese translation from Weblate ([0f5b2151](https://github.com/qTox/qTox/commit/0f5b2151ee334b397e1eaaefd49e47e2a71e2c90), [3a656bb3](https://github.com/qTox/qTox/commit/3a656bb3ca623b9ec33b6130eeab0c8212874cf0), [b4c740b1](https://github.com/qTox/qTox/commit/b4c740b1b70bcf7c6ab78f2c3b99c018da9bae70), [db990ac7](https://github.com/qTox/qTox/commit/db990ac71f21120bfeb7fe5307b3ab9c3a0ba212), [dd4673cf](https://github.com/qTox/qTox/commit/dd4673cf6ad5a59e7cfc39ba3dd6faa25f76b85e)) * update Czech translation from Weblate ([7c5eaeae](https://github.com/qTox/qTox/commit/7c5eaeae7b990aef63d82445a7b8f356c554f14d), [b12b1cdc](https://github.com/qTox/qTox/commit/b12b1cdc2ed37d14facea45f0f775c1539883a88)) * update Esperanto translation from Weblate ([1e39d944](https://github.com/qTox/qTox/commit/1e39d94425789702c1222f71ccb0524d608d1d47)) * update Estonian translation from Weblate ([e6bb215a](https://github.com/qTox/qTox/commit/e6bb215a23363b6feab5a3f590ef0c0ea91378c6)) * update French translation from Weblate ([2519594a](https://github.com/qTox/qTox/commit/2519594aa2ec156bb6af3a192bdefe7a1ffa50f9), [cca4038f](https://github.com/qTox/qTox/commit/cca4038f655029b753e42cee45b46121c93fe3e6)) * update German translation from Weblate ([03197e5c](https://github.com/qTox/qTox/commit/03197e5c97140030d5bda334ecea3e1a464e6e6e), [9125060d](https://github.com/qTox/qTox/commit/9125060d35d00c71cb23167625f3ca3d35306ac2)) * update Hungarian translation from Weblate ([8cb357fd](https://github.com/qTox/qTox/commit/8cb357fda788b2f71f4cdd17a427f1b50ab16fed), [b0a564da](https://github.com/qTox/qTox/commit/b0a564dacde79ac361d6a9ba53db1e43ac0cfa08), [cdb47dce](https://github.com/qTox/qTox/commit/cdb47dce74d8991a12f93f1e2acf4965ffe34f41), [ff69b61f](https://github.com/qTox/qTox/commit/ff69b61f36541276b4a8764184f1927713ee900a)) * update Italian translation ([6a520c9c](https://github.com/qTox/qTox/commit/6a520c9c90eed3cc9eaab28f7b64550edc39eada), [ed472001](https://github.com/qTox/qTox/commit/ed47200181b29ec7b37651f6d450273f4002ec01)) * update Lithuanian translation from Weblate ([7d7a1845](https://github.com/qTox/qTox/commit/7d7a18454cef4118bf272b5849f122b68a2f1f5f)) * update Polish translation ([5ed1048e](https://github.com/qTox/qTox/commit/5ed1048e7254bb657436b852a8c4b3561932971d)) * update Portuguese translation from Weblate ([26ff01ba](https://github.com/qTox/qTox/commit/26ff01ba023c75e9f93914f7e082f3c3adab0643)) * update Russian translation from Weblate ([15638d0c](https://github.com/qTox/qTox/commit/15638d0c31a008dc54fe63d1ac264c212d18c590), [16accc74](https://github.com/qTox/qTox/commit/16accc745ce13ef6ca2a35c67bae2714849b261d), [2fa4073d](https://github.com/qTox/qTox/commit/2fa4073d903b4bc648580bb50fed51cede06ad5e), [eb15b157](https://github.com/qTox/qTox/commit/eb15b157d014555d5b91d60bd4c7421034e2a315)) * update Slovak translation from Weblate ([051672af](https://github.com/qTox/qTox/commit/051672afb52f893bd24a1aacc431354a6a0e0258), [cc6efe93](https://github.com/qTox/qTox/commit/cc6efe93bcaba5c696616ccaa231b7e93efe272c)) #### Bug Fixes * add missing Q_OBJECT macro to the PasswordEdit class ([f5011cdb](https://github.com/qTox/qTox/commit/f5011cdb2feb2da042e373b4e4094e967bde8120)) * Fixed compact layout ([f870bf19](https://github.com/qTox/qTox/commit/f870bf1953672d948907477415a5cd2143d14a03)) * remove unnecessary variable when checking whether save is a TES ([231e1d74](https://github.com/qTox/qTox/commit/231e1d746a9b97bb5da952c7565c0d6f052f26de)) * Status reflection for friend calls ([917812da](https://github.com/qTox/qTox/commit/917812daaabc8b8118526691e67a3f6e2dd8fcb2)) * dataToString correctness regression ([a4bda265](https://github.com/qTox/qTox/commit/a4bda2652550f3b2c2c8275dc8f607498f1c06ff)) * **UI:** * make link in `About` setting page point to all contributors ([326d5219](https://github.com/qTox/qTox/commit/326d5219bc5bdabc87028e1040b98cde1e7c8950)) * remove unnecessary space between a word and colon ([fba5f8cc](https://github.com/qTox/qTox/commit/fba5f8ccc44b3227dc7d064f6c4d2307b616b839)) * copy friend's non-truncated status message instead of truncated ([9d1275b3](https://github.com/qTox/qTox/commit/9d1275b3963bfa87a4c1321d2d590db994e87ff3)) * Changed tab order to be same with visual order ([f7899d7b](https://github.com/qTox/qTox/commit/f7899d7b5044706d339537ff9658087a7ad22e37)) * **core:** don't emit idSet signal twice ([355fb2ae](https://github.com/qTox/qTox/commit/355fb2ae413cad8f680ce3e8b1615c9415715135)) * **l10n:** fix wrongly positioned `%` in translations ([e831ba94](https://github.com/qTox/qTox/commit/e831ba9427f356513eb66e1c4c26f889cc1a129d)) * **videoButton:** Added disabling button while call is active ([eff27f69](https://github.com/qTox/qTox/commit/eff27f690e8a8af2e8f1fe0cd80f3b21dea2bed7)) ## v1.6.0 (2016-11-13) #### Bug Fixes * Fixed crash on profile import ([a130a60e](https://github.com/qTox/qTox/commit/a130a60e9a09096de5e562d6bb89edf3e9984cca)) * hide option to `Load automatically` for encrypted profiles ([b5efc2bb](https://github.com/qTox/qTox/commit/b5efc2bb7124ccbbd256792c524ffffdd02fab3f)) * use `qAbs()` instead of `abs()` for better platform compatibility ([79c249be](https://github.com/qTox/qTox/commit/79c249be55f084c1bf68dbe927c847a4ea45e045), closes [#3613](https://github.com/qTox/qTox/issues/3613)) * **UI:** * bump default emoticon size, since otherwise it can be too small ([bbdd4f04](https://github.com/qTox/qTox/commit/bbdd4f044c2c6b70fcb8794c8e5f01f1380af168)) * string for option auto-accepting files ([1d4ea1ec](https://github.com/qTox/qTox/commit/1d4ea1ec408c7c974f0a408835f599864320a15f)) * **audio:** * Clear audio buffer when ending audio loop ([244d1dc3](https://github.com/qTox/qTox/commit/244d1dc386f634a0a27e58e26a5f0fd22340081f)) * keep the data pointed to by tmpDevName in scope ([af37fa7b](https://github.com/qTox/qTox/commit/af37fa7b20faf22129fe8d949ba28ca01b0a462f), closes [#3786](https://github.com/qTox/qTox/issues/3786)) * **avform:** * Fixed empty value in audio and video lists ([15181c43](https://github.com/qTox/qTox/commit/15181c431a800347bd7bf82fa2703ea05bfe0dd5)) * Added shift screen region ([eaccbf3d](https://github.com/qTox/qTox/commit/eaccbf3da9a9e90897142370ec76defc85e7fe02)) * **build:** * enable backports repository for Debian Jessie ([fb5639b2](https://github.com/qTox/qTox/commit/fb5639b292c99c176fe91ca844f7360820ce68ac), closes [#3679](https://github.com/qTox/qTox/issues/3679)) * fix OpenAL build on Windows ([2f7076c1](https://github.com/qTox/qTox/commit/2f7076c1687dcf163ee4e63105e37bac9d9aebd3)) * **chatform:** remove std::dynamic_pointer_cast in favor of static cast ([775b6a32](https://github.com/qTox/qTox/commit/775b6a325be9a19104e73a90c7836446bf429536), closes [#3801](https://github.com/qTox/qTox/issues/3801)) * **chatlog:** silence warning about unused parameters ([8ff0d28a](https://github.com/qTox/qTox/commit/8ff0d28abf8b11940b13fa3ae861db0bc5dce9e1)) * **i18n:** * Remove HTML tags from translation ([f5f7b5ab](https://github.com/qTox/qTox/commit/f5f7b5abba756860e3c2e3949b92f6abafc0c3aa)) * remove HTML from translations ([c8bde5bc](https://github.com/qTox/qTox/commit/c8bde5bc452aaf68e88424bc448f86a16b282ffc)) * Removed extra tag from translation ([fde9ad53](https://github.com/qTox/qTox/commit/fde9ad535667e25869879fcde2dd24d8c7d8ca51)) * **l10n:** correct ru translation ([5c901d0e](https://github.com/qTox/qTox/commit/5c901d0ea5bd5fc32a05138f705023a316b0813b)) * **rawdatabase:** Added anonymizing SQL query in logs ([85ee69f0](https://github.com/qTox/qTox/commit/85ee69f0082105a6df187d85eb831ea5e7ce8c3e)) * **settings:** * Changed grid layout on form layout ([9fd0dd79](https://github.com/qTox/qTox/commit/9fd0dd790b766355377cb53f11cfb7d4a099b1b6)) * Properly update taskbar on disconnect ([6e165ceb](https://github.com/qTox/qTox/commit/6e165ceb475313655a80e166ea1d88577ee8afb0)) * Change test sound button to checkbox ([22b89bea](https://github.com/qTox/qTox/commit/22b89beaf14f82f623464967f1980e5d78040649), closes [#3800](https://github.com/qTox/qTox/issues/3800)) * Play test sound when user enables test sound ([9b46cf64](https://github.com/qTox/qTox/commit/9b46cf64041a4c65155d3db75ed2d08630a673f0), closes [#3735](https://github.com/qTox/qTox/issues/3735)) * correct empty listbox audio device ([84a95700](https://github.com/qTox/qTox/commit/84a95700398273129ff2e6d63ea884f64c7ee686)) * Use old settings instead of default values ([6f0431ae](https://github.com/qTox/qTox/commit/6f0431ae50499bc2a3a19ddc9e83f86f55e105d5)) * correct default value of dateformat ([e794acbc](https://github.com/qTox/qTox/commit/e794acbccc29ee7d1a262c283b838e8b297b5527)) * Fixed group to load makeToxPortable setting ([a3c201d3](https://github.com/qTox/qTox/commit/a3c201d3b2562c794450ba15117ff7a69408da57)) * Added loader for old settings ([740d91b6](https://github.com/qTox/qTox/commit/740d91b61a66f5a5929dafc5fa86095d02bcf35a)) * const getters ([e2f9d2cf](https://github.com/qTox/qTox/commit/e2f9d2cfe873a16841277f32ed581cbcdcb8bddc)) * **settingsform:** Fix layout spacing ([f1975231](https://github.com/qTox/qTox/commit/f197523155000fe719947b73aa84bffdc9fff385)) * **text:** Change idealSize calculation ([c8512fa9](https://github.com/qTox/qTox/commit/c8512fa98461a54f3febc076fa1d7f99eae1d593)) * **timeformat:** * Remove duplicate of timeformat ([661388d7](https://github.com/qTox/qTox/commit/661388d73195a539285fd6dc7acad4e86ba0e57b)) * Make timestamp editable same as date format ([d6f323ce](https://github.com/qTox/qTox/commit/d6f323cefc15b477ec588a3e88a133188ecbac84)) * Fixed list of available time formats ([35e7da85](https://github.com/qTox/qTox/commit/35e7da85d34fa8f5bd0c9ec6da10894c932af568)) * **ui:** prevent text style from being reset during retranslation ([28c29157](https://github.com/qTox/qTox/commit/28c29157f8ea7f956bd76a3979becb41d6968bb3), closes [#3805](https://github.com/qTox/qTox/issues/3805)) * **uiform:** Added format constraints ([13baf932](https://github.com/qTox/qTox/commit/13baf9326d869d8b410a6a7f9c447766fcdea45e)) * **video:** fix scaling issues under HiDPI displays with desktop video ([ef157ca8](https://github.com/qTox/qTox/commit/ef157ca8af14862c1d69d62ab609fe44754c4b3f)) * **videomode:** Added ability to store negative coordinates ([26a27cf3](https://github.com/qTox/qTox/commit/26a27cf3f8de49b0dd49e56b55d1ad0303b93975)) * **widget:** * do not block logout on Linux desktop environments v2 ([6b00779d](https://github.com/qTox/qTox/commit/6b00779ddecb4a28c20ef3d00eadd0ffd5490c48)) * do not block logout on Linux desktop environments ([7ee883bc](https://github.com/qTox/qTox/commit/7ee883bcc7cf09fe3b87ab7b4594165b7cc08ba9), closes [#1485](https://github.com/qTox/qTox/issues/1485)) #### Features * bump font size from undersized ([2d472004](https://github.com/qTox/qTox/commit/2d472004cd3a6afc73889f836e80c3c9fa6b29ac)) * **autoAnswer:** add auto answer feature ([7fe8a86c](https://github.com/qTox/qTox/commit/7fe8a86c0329481cd34f5ed4e134d2db7a11d096)) * **avform:** * Made warning and debug info more useful ([94783778](https://github.com/qTox/qTox/commit/9478377868506559f7b126c501e24c8f9c844806)) * Added automatic screen rescan ([08229735](https://github.com/qTox/qTox/commit/08229735b26cd849343810e1f84c2c3bf5807dc5)) * **dateformat:** Edit the date format string ([b3083250](https://github.com/qTox/qTox/commit/b3083250ff8f71936db56993a9d7b1216d8323f9)) * **l10n:** * add Slovak translation from Weblate ([ad176260](https://github.com/qTox/qTox/commit/ad176260d5d89412759a85755ef42b9b69d0fdaf), [52ae7093](https://github.com/qTox/qTox/commit/52ae7093f7af9d26fde1cdb37ec7051bb6c16516)) * update Arabic translation from Weblate ([7d704e8d](https://github.com/qTox/qTox/commit/7d704e8dc8e4830e85dba6711e58261ff5124f1d)) * update Belarusian translation from Weblate ([0842b50b](https://github.com/qTox/qTox/commit/0842b50b760882014bfbf85c27583f22cf34f3b3), [226f4eaf](https://github.com/qTox/qTox/commit/226f4eaf4f98c9c53edd370f68b91c79c2acd895), [4296d377](https://github.com/qTox/qTox/commit/4296d377eec0f8a136b3d62eb06a5fb122c766a8), [60af778f](https://github.com/qTox/qTox/commit/60af778fb8d0fbd8373c4d994d56d3b21281212a), [d898e7d4](https://github.com/qTox/qTox/commit/d898e7d40a4cac1b8e9c2cbbb8f7caa72c71d1a9)) * update Chinese translation from Weblate ([11b19e42](https://github.com/qTox/qTox/commit/11b19e42e8a45b53f8f0185a1073a36726de26a8), [3539e840](https://github.com/qTox/qTox/commit/3539e8405159ab4b0f15b23ae88ca11b34f32c5c), [4f20d032](https://github.com/qTox/qTox/commit/4f20d032d1cd67a7667f1403e816854d933507d4), [5f087194](https://github.com/qTox/qTox/commit/5f087194050bbb148cc3b9743e5fd3e0a4144e3a)) * update Czech translation from Weblate ([037f2da8](https://github.com/qTox/qTox/commit/037f2da82bd4b573fa29e69d1e050dcb2458eac6), [3c8b8d63](https://github.com/qTox/qTox/commit/3c8b8d63449968904934ea791010d14ede146c6a), [6b4fe28c](https://github.com/qTox/qTox/commit/6b4fe28c534fc12ce686240af682f5f31ef3f2f7), [a6d94e8f](https://github.com/qTox/qTox/commit/a6d94e8f839e34e73d7785ce2cf55f29d1713d7b)) * update Danish translation from Weblate ([ab312318](https://github.com/qTox/qTox/commit/ab3123187df057357356891c7d51db1823cd7c94)) * update Dutch translation from Weblate ([2b3eaa1b](https://github.com/qTox/qTox/commit/2b3eaa1bc54727ae2820883fc393593461020472), [2c5a38cc](https://github.com/qTox/qTox/commit/2c5a38cc586ff811b5b039e073c4a60f48cc2880)) * update Estonian translation from Weblate ([018a88df](https://github.com/qTox/qTox/commit/018a88dfe6e4e0ef57b5c476f8ef06c7c720230c), [bed51e88](https://github.com/qTox/qTox/commit/bed51e884c1971c2487b4119dcb14584a9dfedd0), [d8a525ac](https://github.com/qTox/qTox/commit/d8a525acb3ae68f98428a111ab366c08c97916ea), [e0c71216](https://github.com/qTox/qTox/commit/e0c7121666570743674f949baf663eae1cc2905d), [ee2b93ae](https://github.com/qTox/qTox/commit/ee2b93ae21d3af9298a68a34adb0aeeaae9ccc10), [f6aa3085](https://github.com/qTox/qTox/commit/f6aa3085ef5d47e1073bd5032d622e4244770dc9)) * update French translation from Weblate ([55d1fbeb](https://github.com/qTox/qTox/commit/55d1fbeb42e0e83fe6ef5af0441eac4eb3d82592), [a24a6df5](https://github.com/qTox/qTox/commit/a24a6df55076d4b0cf2f3accfcba8065f45e8edb), [f5371032](https://github.com/qTox/qTox/commit/f5371032a4d40e528628159db996b0fbd12908cd)) * update German translation from Weblate ([00fa4d3b](https://github.com/qTox/qTox/commit/00fa4d3b6203e0d0355b86fc1a39215672ba9077), [0a37f7ea](https://github.com/qTox/qTox/commit/0a37f7ea7151568e6bd02f5e57e40e3851310b61), [1cb65ce4](https://github.com/qTox/qTox/commit/1cb65ce42249e36bfc6a33a5fb3d31bbde73369f), [7942206b](https://github.com/qTox/qTox/commit/7942206ba988eda20dbdcfa3f79d81fa1a5ef6c8), [79a847f1](https://github.com/qTox/qTox/commit/79a847f14dd6d610ab88440f1ae200262e97de02), [a2e559d7](https://github.com/qTox/qTox/commit/a2e559d7597f63a2f519b90840f686834cd87a20)) * update Greek translation from Weblate ([ba2d62fe](https://github.com/qTox/qTox/commit/ba2d62feeddf7e054b9ec10f92ca997814d48b94)) * update Hungarian translation from Weblate ([0e47ecda](https://github.com/qTox/qTox/commit/0e47ecdad5b40856dc32c82de75ef8a8cb02044a), [16b32e3b](https://github.com/qTox/qTox/commit/16b32e3bbf55b6b4b486a1e51444183da0c9423c), [16e4198b](https://github.com/qTox/qTox/commit/16e4198b094d13a9be29ce9c806915e72a28ea15)) * update Japanese translation from Weblate ([f733f475](https://github.com/qTox/qTox/commit/f733f4755124dff89441c6e959dae888feb34c58)) * update Lithuanian translation from Weblate ([25e86e54](https://github.com/qTox/qTox/commit/25e86e5464fcf556bcd8d538cb07cc97acd83432), [55bb35ac](https://github.com/qTox/qTox/commit/55bb35acaa32c5b75189cb6b909f8eb4a016641b), [7e6e3970](https://github.com/qTox/qTox/commit/7e6e39703b0fc9810f7aa0c0a84a173b092d46ea)) * update Norwegian (old code) translation from Weblate ([548df204](https://github.com/qTox/qTox/commit/548df204979a3805795e5aed661767d38041f0f7)) * update Polish translation ([7fc98a9e](https://github.com/qTox/qTox/commit/7fc98a9ea7292dea74adb38d533fb3f74992a261)) * update Portuguese translation from Weblate ([9e39708e](https://github.com/qTox/qTox/commit/9e39708e76bc583025491be245879c1034e98c0e)) * update Russian translation from Weblate ([0865217d](https://github.com/qTox/qTox/commit/0865217d468bf9084c4e3755ba11a42046eec5b4), [628cfd23](https://github.com/qTox/qTox/commit/628cfd23222df3406ef0b39580565b75d94ae352), [dd2a5dad](https://github.com/qTox/qTox/commit/dd2a5dadc57b13eba98e2dbb21893c3ec7d3550b), [fb418c64](https://github.com/qTox/qTox/commit/fb418c64836e562a11f298319520add2cdc160ff)) * update Slovak translation from Weblate ([287e2bcc](https://github.com/qTox/qTox/commit/287e2bcc38ee08d59f7877ee9aaef397fe53abca), [dcfbdf8f](https://github.com/qTox/qTox/commit/dcfbdf8f95c865c8336ed5ffbc094b91fb2315b2)) * update Spanish translation from Weblate ([8a8859b7](https://github.com/qTox/qTox/commit/8a8859b7a93a980e010b3e1081553142143afaf3)) * **translation:** add update language Spanish ([a7c46fff](https://github.com/qTox/qTox/commit/a7c46fff2de152c77b54cd54aa8f9a209077b05e)) * **settings:** * Made `Reconnect` button wider ([4a5f89bb](https://github.com/qTox/qTox/commit/4a5f89bb98fc8657514e35c5022713358a453d49)) * Export and copy debug log ([3c6bd043](https://github.com/qTox/qTox/commit/3c6bd043a04edb0ae76444f5d54ce8eaf5fb0b14), closes [#2890](https://github.com/qTox/qTox/issues/2890)) * Moved proxy to personal settings ([4dfe3ec2](https://github.com/qTox/qTox/commit/4dfe3ec22636150074a0afc73644582e14f744ff)) * Added privacy block ([9499bdd4](https://github.com/qTox/qTox/commit/9499bdd45877f20bcb8f82606a5db7bc6e3a2db5)) * Deleted settings header ([a1041ed1](https://github.com/qTox/qTox/commit/a1041ed11a27e9b9de617a7e11c907d83ba55952)) * Added reset settings button ([9c9f1c11](https://github.com/qTox/qTox/commit/9c9f1c11d1fece24402384dbd6b41d1c05aec00b)) * Extracted user interface settings on new tab ([fb4aa4c8](https://github.com/qTox/qTox/commit/fb4aa4c8f628b47083402221380172951ab41532)) * add notification signals for changed settings values ([f00b9008](https://github.com/qTox/qTox/commit/f00b9008e669905e6178c84f4b2e81456ee9cb5e)) * **smileys:** * try to load smileys from XDG_DATA_DIR directories ([29da2210](https://github.com/qTox/qTox/commit/29da2210c98a2f1bf2dcaf6af18ba818ceef5d2c)) * allow compiling qTox with minimal or no smileys ([ae769106](https://github.com/qTox/qTox/commit/ae76910631a9cc15dd9673d5dfc0392aeffeb459)) * **uiform:** Separated time and date format and examples ([d909d99b](https://github.com/qTox/qTox/commit/d909d99bd22e2ba35459c7bb3be0fa8724f97346)) ## v1.5.0 (2016-08-09) The most important change is video improvements. Bored by waiting minutes for video call to start? Fixed. Among other things, qTox has been translated into 5 new languages. More information on features / fixes / changes below. #### Breaking Changes * **textstyle:** Change markdown syntax to be more intuitive ([32e48a97](https://github.com/qTox/qTox/commit/32e48a979ca78717a212800547c95ca0f1e67b8f)) * **widget:** Disable sound notification for `busy` status ([e7785ab4](https://github.com/qTox/qTox/commit/e7785ab4c2790b2c10b33c416bc78ad23a16cc63)) #### Features * **audio:** * add slider tickmarks, improving better visible orientation ([431a10f8](https://github.com/qTox/qTox/commit/431a10f82b6370153aa843856d109287078b0dc5)) * add real gain control of the input device ([f72baa61](https://github.com/qTox/qTox/commit/f72baa613f6f15de454783eeb4e709c691aef4ad)) * **avform, screenshotgrabber:** Added custom screen region selection ([9cfd678c](https://github.com/qTox/qTox/commit/9cfd678c262b223413dab30d656aff50ae7ce470)) * **bootstrap.sh:** add an option to install sqlcipher ([66f270ec](https://github.com/qTox/qTox/commit/66f270ecad591fc6e5547af12753358a63c4b171)) * **cameradevice, avform:** Added ability of screen selection ([d781a4f7](https://github.com/qTox/qTox/commit/d781a4f762a75a5766d9ca534ef0f034bf332ea0)) * **camerasource:** Change default video mode to preferred ([c3de6238](https://github.com/qTox/qTox/commit/c3de6238ca5efa9e42b484a755934c986d0d4b6e)) * **capslock:** Added caps lock checker ([97f95e7e](https://github.com/qTox/qTox/commit/97f95e7e91a5dc8f5b1136238efc7c5cb10d55f4)) * **chat:** * add the ui settings to alter font and size for chat messages ([41c96eb1](https://github.com/qTox/qTox/commit/41c96eb15962306e1da02c69a1f515b5223fd270)) * add settings to alter the chat view's base font ([8ba20541](https://github.com/qTox/qTox/commit/8ba205419048a039a8dbc84c87dfa4c6b2cb1252)) * **chatform:** Disable call buttons if friend is offline ([bbefe011](https://github.com/qTox/qTox/commit/bbefe0119d405cf7bcc0e7a90571b1c18b993817)) * **doxygen:** Created simple doxygen config file ([194c55a4](https://github.com/qTox/qTox/commit/194c55a4c5c217c060ca78e11eec8c3b349aeffe)) * **emoticons:** add ASCII-less version of emojione emoticons ([c4b4155a](https://github.com/qTox/qTox/commit/c4b4155a53d72fbcad7475590d860c94bea336b3), closes [#3398](https://github.com/qTox/qTox/issues/3398)) * **emoticonswidget:** Keep emoticon option open ([d0ea5bb4](https://github.com/qTox/qTox/commit/d0ea5bb4fd6e5ffdb2bb11c4e63d1c518af36b65)) * **genericchatform:** add "Quote selected text" feature to chat window ([40a805c2](https://github.com/qTox/qTox/commit/40a805c2fd66d7c5cd618fb4974dcd65bf7df650)) * **gui, setpassworddialog:** Added buttons translation ([58e503bb](https://github.com/qTox/qTox/commit/58e503bb14e747b16a30c22dffc7397999098bac)) * **importProfile:** Add way to import profile ([9ea25d1f](https://github.com/qTox/qTox/commit/9ea25d1fbd928657cfcc0e73e868783f866e5ea9)) * **i18n:** * Make activity by time labels translated by locale ([f2aada8f](https://github.com/qTox/qTox/commit/f2aada8f4fd404947ca4fa2d34e00df45c25f76f)) * make Markdown settings translatable ([3e22593a](https://github.com/qTox/qTox/commit/3e22593ae71f388972a59079ccf6d719f980d035)) * **l10n:** * Add Danish translations ([c8c7bda3](https://github.com/qTox/qTox/commit/c8c7bda38e7e9167c0eab084452df192b8485f59)) * Add Hebrew translation ([83b89f12](https://github.com/qTox/qTox/commit/83b89f1233c49e3c8d92a2368f4b0cb5df870937)) * add initial Belarusian translation ([684835de](https://github.com/qTox/qTox/commit/684835de1b3a26aac83d1fa99f2bb5a78eab411e)) * add initial Esperanto translation ([7971975c](https://github.com/qTox/qTox/commit/7971975cbe5f53ad328bb8af76b09170d341f869)) * add Japanese translation ([d06efd38](https://github.com/qTox/qTox/commit/d06efd387bdaa85143e6924352a0bcc038537a98), closes [#3223](https://github.com/qTox/qTox/issues/3223)) * add Lojban translation ([237351fd](https://github.com/qTox/qTox/commit/237351fdd28841a61fcc733d9f039471fcf05e40)) * add Uighur translation ([3ee8f72a](https://github.com/qTox/qTox/commit/3ee8f72a6c79e53d19d0083d1d0d2c3f0b01d97c)) * Update Arabic translation ([91af5c95](https://github.com/qTox/qTox/commit/91af5c951a51e6398e74dd4c85065a942a0a5da4)) * update Belarusian translation ([1b16466c](https://github.com/qTox/qTox/commit/1b16466cf924576e09ed92691eb82c7d6300b439)) ([44420953](https://github.com/qTox/qTox/commit/444209537828eb4aab20ed47f7ec304863c3e48d)) ([526f13aa](https://github.com/qTox/qTox/commit/526f13aa0df27eba245918b6b15f1f981e29a1e2)) ([7c6ba752](https://github.com/qTox/qTox/commit/7c6ba75200e8e3cd5461d99d85002ce1ccd1e488)) ([97d8c7a1](https://github.com/qTox/qTox/commit/97d8c7a108f3f6541eacd6c8dda3eb11c0f0813a)) ([daabda84](https://github.com/qTox/qTox/commit/daabda84c47c4ea5a29c45e045689b917fa9c750)) ([f2c19912](https://github.com/qTox/qTox/commit/f2c19912c46c89f212bb6c809d6a54619018252d)) * update Bulgarian translation ([10d913ee](https://github.com/qTox/qTox/commit/10d913ee4ac80c54f79060915e37f32eb14ec385)) ([b6b149a7](https://github.com/qTox/qTox/commit/b6b149a756841ca901dee893992239161d1d7399)) ([6052364b](https://github.com/qTox/qTox/commit/6052364bca754ac3e49638737b76c597fe158e8b)) ([e0b41d57](https://github.com/qTox/qTox/commit/e0b41d5764d76c35e627f3b23a24fffd265b2d49)) * Update Chinese translation ([fe432dea](https://github.com/qTox/qTox/commit/fe432dead428cbdd9d18b93fd60a9744a1ea210d)) ([f8ee4484](https://github.com/qTox/qTox/commit/f8ee448412b5bca7a97a106d9e2507fe63bce998)) * update Czech translation ([1e9efbfe](https://github.com/qTox/qTox/commit/1e9efbfe694f0eabdc42edb7901d4282c28c6498)) ([83f874e5](https://github.com/qTox/qTox/commit/83f874e5ceb1ac814974a927d415a18c6c98b95f)) ([8d94ca92](https://github.com/qTox/qTox/commit/8d94ca92275b552e39cc91f8c48cf96f0e0fc0e5)) ([d951cb75](https://github.com/qTox/qTox/commit/d951cb7589ce9e3908b36deeac151797453576b1)) * update Dutch translation ([8ac47bf0](https://github.com/qTox/qTox/commit/8ac47bf06b038cd7decf62a114347579488559a7)) * Update Estonian translation ([2cd35e17](https://github.com/qTox/qTox/commit/2cd35e17360dd1a9b66f0eb5d0ed163c1903895b)) ([4137a19f](https://github.com/qTox/qTox/commit/4137a19fbc1286f5c66e1814c3b0cde94fdd0617)) ([6d7d9c33](https://github.com/qTox/qTox/commit/6d7d9c33a5c3d2a2948832f2d9a5807865537c3d)) ([85a701f5](https://github.com/qTox/qTox/commit/85a701f5f820874d0f1c2993f8ffffc124bc030f)) ([9c8335fa](https://github.com/qTox/qTox/commit/9c8335fa16eb8ab7e0a74b8585561816f0cc6285)) ([ba0d7ec7](https://github.com/qTox/qTox/commit/ba0d7ec76814ceb4b272cefddf15e427a94af7e2)) ([c6fba9c5](https://github.com/qTox/qTox/commit/c6fba9c54872a35f708f9344647a4258d206dbb6)) * update French translation ([2a368436](https://github.com/qTox/qTox/commit/2a368436dbed906d928d44771aa96d93e64951e4)) ([402f9eb9](https://github.com/qTox/qTox/commit/402f9eb936ac3b1bad75ea35a6bdf9e8e9a0eac3)) ([4b42a6db](https://github.com/qTox/qTox/commit/4b42a6dba385da8e730f028291e06afae079e4d7)) ([525db227](https://github.com/qTox/qTox/commit/525db2276a49ec24a17b89966a626a1252ce2b13)) ([5a147646](https://github.com/qTox/qTox/commit/5a147646c59a21a27cc342e454a6c6b2078035ab)) ([774f3c16](https://github.com/qTox/qTox/commit/774f3c1641cdf0ece2e5a8193a1468bcc3229031)) ([d9fc36db](https://github.com/qTox/qTox/commit/d9fc36db4bf097b02e37fa8874432ed44309cd8e)) ([f6f336a7](https://github.com/qTox/qTox/commit/f6f336a7a7a40eac4fc7210d60a020d9cecac4b4)) * Update German translation ([beca3a9c](https://github.com/qTox/qTox/commit/beca3a9c45a65f5085bd6d8c1dff91a73215619f)) ([750d1b50](https://github.com/qTox/qTox/commit/750d1b50cc5f05fce2348bf352d2565201cd5d3c)) ([1107b642](https://github.com/qTox/qTox/commit/1107b6421be5a2ef3f945b2c78456fba42e31c27)) ([2b65fac3](https://github.com/qTox/qTox/commit/2b65fac36f5c04878dded3a456832ebe9f4cf1ca)) ([351c4166](https://github.com/qTox/qTox/commit/351c4166b26bd93877cea973212043f42513ad18)) ([65019117](https://github.com/qTox/qTox/commit/6501911730c1937f0b71116898e4023f4a6fc039)) ([8a0a8f1f](https://github.com/qTox/qTox/commit/8a0a8f1f75ef80019165f208485e6f567a30cbed)) ([962206db](https://github.com/qTox/qTox/commit/962206db769112c7e2ee35743d31081bfa3f42f8)) * update Hungarian translation ([0c3f3817](https://github.com/qTox/qTox/commit/0c3f3817c3e9e18abc168761f5d60b6de850aac0)) ([9bc642ee](https://github.com/qTox/qTox/commit/9bc642eee9def1a7c594216c1a6c23e09fc5c18f)) ([c6938d6c](https://github.com/qTox/qTox/commit/c6938d6c4e809b7d99357641914c9f08dea3ffb6)) * update Italian translation ([7d308f99](https://github.com/qTox/qTox/commit/7d308f99ce306333a1c285633070bfe8cab45bf7)) ([e7089a3d](https://github.com/qTox/qTox/commit/e7089a3d1a46c799c6c8e7b9f149306a8d59272d)) ([e6f870f4](https://github.com/qTox/qTox/commit/e6f870f4b408e4df3f95e0159adb1f5e3d0169e5)) * Update Japanese translation ([75d64dc6](https://github.com/qTox/qTox/commit/75d64dc68dc86fe8479e5c48dd54f375e4c63d34)) * update Lithuanian translation ([0bb416cd](https://github.com/qTox/qTox/commit/0bb416cd76b8191a57d8dcd24d135972f64c024c)) ([9d108840](https://github.com/qTox/qTox/commit/9d10884029f1dae69fdad3ba03f691acf2da5d18)) ([281d94ef](https://github.com/qTox/qTox/commit/281d94ef4e9db5edf1fe208a834f2a5648a8b4d9)) ([e19f4c70](https://github.com/qTox/qTox/commit/e19f4c70092b6e9e022da45a6b594014db837a10)) * Update Norwegian translation ([1466fbf5](https://github.com/qTox/qTox/commit/1466fbf554ba810b07305969a55a0f5425df488e)) * update Polish translation ([9a3ba021](https://github.com/qTox/qTox/commit/9a3ba02145a3357c1e4f3044372d03dfebcfb568)) ([e7c0159f](https://github.com/qTox/qTox/commit/e7c0159fce86df7b9c2f7627faea41425b432f55)) ([6f074061](https://github.com/qTox/qTox/commit/6f074061cb02c9013ab6ccb493755509485a73e6)) ([88b839c1](https://github.com/qTox/qTox/commit/88b839c1af1e0b5fabb7a6ac8034d208620cc638)) ([a49e7f27](https://github.com/qTox/qTox/commit/a49e7f276a9a528d34d29245c21a5886108ff2b8)) * Update Russian translation ([0856d4dd](https://github.com/qTox/qTox/commit/0856d4dd13dc61b7b4ff8c69b01eab4a6f56f430)) ([1826e2ae](https://github.com/qTox/qTox/commit/1826e2aebb2458f90c91bdd8168a78c1b5569ee5)) ([21b5cc3f](https://github.com/qTox/qTox/commit/21b5cc3f9db21068eef0b7138d362d7295b1e5ed)) ([29dbd030](https://github.com/qTox/qTox/commit/29dbd030765a5f382ceb0e386fe381cfc51ee5de)) ([31ecfd8b](https://github.com/qTox/qTox/commit/31ecfd8b12996cea6439e03dd97c54b952a6436f)) ([379aaa0f](https://github.com/qTox/qTox/commit/379aaa0fdb85a0fd384110a387e10aa7de49d17f)) ([6beea2bd](https://github.com/qTox/qTox/commit/6beea2bda594ac9d32144765e4ba3873e102684b)) ([861cf7d9](https://github.com/qTox/qTox/commit/861cf7d93de8800224c145f6806dc986cd237874)) ([d4ff03c8](https://github.com/qTox/qTox/commit/d4ff03c82b66a7efddcb453a53c5898f7625075b)) * update Spanish translation ([17f43668](https://github.com/qTox/qTox/commit/17f43668a417192155d9c41be5b6cb6550728d5e)) ([f81f20f0](https://github.com/qTox/qTox/commit/f81f20f0cfba9e0bdec7599e0ae9eca55bf14e5a)) ([090a715b](https://github.com/qTox/qTox/commit/090a715b4cf2fc731d864b7de7fdeeec47d33e05)) * Update Ukrainian translation ([2ab5af56](https://github.com/qTox/qTox/commit/2ab5af566f6030425b596b84af5274a5fa3d0e4d)) ([3a5e91a2](https://github.com/qTox/qTox/commit/3a5e91a20854f24a33e4be422054309f993f88b1)) * **loginform:** Added caps lock indicator to newPass ([cbe8fb8e](https://github.com/qTox/qTox/commit/cbe8fb8ef989e6e178343f1d5d5e016a62ce1d70)) * **loginscreen:** Created new CapsLockIndicator class ([fb7fcaaa](https://github.com/qTox/qTox/commit/fb7fcaaa8ca98fe32ebdc3d4cab2834ac951b50a)) * **main:** Changed time in logs to UTC. ([4018c004](https://github.com/qTox/qTox/commit/4018c0041b179bbd03f152c43ab0e70dbf00a9f0)) * **notificationscrollarea:** Add ability to delete widget from traced widgets list ([e3d74117](https://github.com/qTox/qTox/commit/e3d74117caa1e1289879388640797a1a9594c35a)) * **profile:** * add a dialog to indicate profile deletion error ([78fd245e](https://github.com/qTox/qTox/commit/78fd245e4cb0a73570b5d6a24d612ac099dd16ef)) * show warning on failure to delete profile ([1dabbca9](https://github.com/qTox/qTox/commit/1dabbca94c172bf21f83e23400ef99e5be45c084)) * **profileform:** Added log toxme errors ([d2d5b230](https://github.com/qTox/qTox/commit/d2d5b2306450032ae485909dc0c480d3c02b3596)) * **settings, generalform, widget:** Added setting for sound notification with busy status ([e23eb1c5](https://github.com/qTox/qTox/commit/e23eb1c5f770b84a41ab061c2e6163ea8c9337a9)) * **smileys:** add emojione emoji-pack and make it the default ([3f4a0abe](https://github.com/qTox/qTox/commit/3f4a0abe6b10ddf335c89a6220e079bb858c9fd6), closes [#3315](https://github.com/qTox/qTox/issues/3315)) * **status:**: add ability to copy status messages ([57ce030f](https://github.com/qTox/qTox/commit/57ce030f1d19f4a16f309149addc80bed53b8b2d)) * **systemtray:** add "Show" action to context menu ([a851a5b1](https://github.com/qTox/qTox/commit/a851a5b18da8c295fba441c817920cf65db2cdbb)) * **textstyle:** Change markdown syntax to be more intuitive ([32e48a97](https://github.com/qTox/qTox/commit/32e48a979ca78717a212800547c95ca0f1e67b8f), closes [#3404](https://github.com/qTox/qTox/issues/3404)) * **video:** * redesign and improve VideoFrame class ([38b1a9b6](https://github.com/qTox/qTox/commit/38b1a9b63dabdd9b71e7824c602f9ff77e99eb1e)) * add setting for 120p very-low-res video ([6045ced3](https://github.com/qTox/qTox/commit/6045ced3f8b538b0c3044370878d4feb76103480)) * **videomode:** Added possible video shift ([fd701df1](https://github.com/qTox/qTox/commit/fd701df1012763cba98bbfbbf7bf9ccd898f1c03)) * **widget:** Disable sound notification for `busy` status ([e7785ab4](https://github.com/qTox/qTox/commit/e7785ab4c2790b2c10b33c416bc78ad23a16cc63)) #### Bug Fixes * increase timer for checking offline messages timeout (again) ([a77afca1](https://github.com/qTox/qTox/commit/a77afca1ec8f86cda68fe7ba853f7e4d854c346f)) * correctly tab-complete nicks starting with `$` ([dbd16ae6](https://github.com/qTox/qTox/commit/dbd16ae6a362809e8fc2936968cd7f0677b7e6f0)) * **.gitattributes:** bootstrap.sh execution fails on MSYS ([ad828621](https://github.com/qTox/qTox/commit/ad828621359963e6eaadb07cf2b834eb9d53cf01)) * **about-qtox:** fix QString "missing argument" warning ([f2f48a8f](https://github.com/qTox/qTox/commit/f2f48a8f07106dd4c3828e202b184062ab39798f)) * **addfriendform:** Fixed problem with reading friend request ([7be8ad01](https://github.com/qTox/qTox/commit/7be8ad01da50c9f95312dea4a5e56ec2df8517a2)) * **audio:** actually disable the audio in/out device in settings, when selected ([9694d6b6](https://github.com/qTox/qTox/commit/9694d6b6d434bb7557c9766e89dce5fb4cb89502)) * **avform:** * display true video height in video mode selection ([192c1e8f](https://github.com/qTox/qTox/commit/192c1e8ff53e6c29c92d4e99107c70c9f9ae00cc)) * add missing "first" video mode back to video modes ([5324e768](https://github.com/qTox/qTox/commit/5324e768c31643fa1741bf94105c4604be482a7c), closes [#3588](https://github.com/qTox/qTox/issues/3588)) * Add skipped camera open call ([1f9b7b13](https://github.com/qTox/qTox/commit/1f9b7b13de54668e0dbe185fbbc3b09c369b5f77), closes [#3476](https://github.com/qTox/qTox/issues/3476)) * Added rounding height in mode name. ([c2e3358d](https://github.com/qTox/qTox/commit/c2e3358dd2eca3839a5f9fc858d8f4546dc842a2)) * Changed "best modes" search algorithm. ([6e1ef706](https://github.com/qTox/qTox/commit/6e1ef70651647d01e55057d48bd5d482b8530777)) * initialize slider value from settings ([c9dbfa5e](https://github.com/qTox/qTox/commit/c9dbfa5eacd07c58d234c73dc58d91d1ad5a8f12)) * make "Screen" translatable ([24f0b11a](https://github.com/qTox/qTox/commit/24f0b11a4d9d9020f7146c0c94b0b65de89a9d0f)) * Added restoring selected region ([1c515821](https://github.com/qTox/qTox/commit/1c5158213dd6f38acdcf202e660b6e383764c436)) * Took default resolution from middle of list ([2d861ee2](https://github.com/qTox/qTox/commit/2d861ee25b2f6ac5607005ea89f6866d7321e66d)) * **bootstrap.sh:** add instructions for missing unzip & adjust path ([fa5ee5b1](https://github.com/qTox/qTox/commit/fa5ee5b1adbfa2cfde3595cdee1a42ca4cac8a11), closes [#3153](https://github.com/qTox/qTox/issues/3153)) * **build:** * Link qrencode statically on Jenkins ([0a976c7a](https://github.com/qTox/qTox/commit/0a976c7a50649ae0874bbf341a273b669901a183)) * Jenkins ffmpeg link order ([9de833ad](https://github.com/qTox/qTox/commit/9de833ad396b5842431fdbb3908f4d3ead2522aa)) * Fix jenkins static builds ([790f9ffc](https://github.com/qTox/qTox/commit/790f9ffc670997172fffad2e4a6c4eaf794aed71)) * **capslockindicator:** * also update indicator when the app gets focus ([2fe41071](https://github.com/qTox/qTox/commit/2fe41071bedb689ba1776affe97e50138b9d6984)) * fix altering the line edit height ([653e0b5a](https://github.com/qTox/qTox/commit/653e0b5af2dc176e80eb617df89f32bf8a00427e), closes [#3379](https://github.com/qTox/qTox/issues/3379)) * Tooltip color was changed. Tooltip translation was added ([bbe158c7](https://github.com/qTox/qTox/commit/bbe158c7d99dff54f2f96cd1ed7f31b8cfc2816a)) * **chat:** cleanup chat css base style ([989b15e6](https://github.com/qTox/qTox/commit/989b15e6560c2bd8fbdfafcb4b9736a49af72774)) * **chat window:** prevent right click from opening chat window ([b9a392d5](https://github.com/qTox/qTox/commit/b9a392d59ee2760a68712b835e21375425bdaebe), closes [#3205](https://github.com/qTox/qTox/issues/3205)) * **chatform:** * Fixed call buttons ([dbe0a159](https://github.com/qTox/qTox/commit/dbe0a1596376a3edc4c1c10d604c9aaa54c8af73)) * Markdown after emojis ([998f0915](https://github.com/qTox/qTox/commit/998f0915db515851d92e32873e6ac4c528cd9a16)) * **chatform, screenshotgrabber:** Fixed memory leak ([bf7c62d6](https://github.com/qTox/qTox/commit/bf7c62d6fa57b3f8b5078d55bf20372fe6aa47b7)) * **chatlog:** Don't delete active transfer widget ([abf7b423](https://github.com/qTox/qTox/commit/abf7b42324f85c00ca1105ac11a8c989e8488aa2)) * **chattextedit.cpp:** fix drag-and-drop to be consistent across systems ([70fc247b](https://github.com/qTox/qTox/commit/70fc247b7092b0bd320b3a59bcaf57d896257da8)) * **contentdialog, widget:** Remove "new message" bar after reading message ([b2c1f468](https://github.com/qTox/qTox/commit/b2c1f468946aff73b23fb9ac6539331f6c0c7fbe)) * **corevideosource:** Partial revert of [ef641ce6d3398792c10b30bf24a81c5a6005fe06](https://github.com/qTox/qTox/commit/ef641ce6d3398792c10b30bf24a81c5a6005fe06) ([b1adef2f](https://github.com/qTox/qTox/commit/b1adef2fd0b9aeef58cea34c6637b8cd3f6e16a8), closes [#3527](https://github.com/qTox/qTox/issues/3527)) * **directshow:** Fixed problem with crosses initialization ([504ad534](https://github.com/qTox/qTox/commit/504ad534e0e9d27077a49222f2c7f9e0d568b22d)) * **doc:** CONTRIBUTING.md typos ([4eed2549](https://github.com/qTox/qTox/commit/4eed2549aaef7c72f2e0ddf92696f35f209ae1a4)) * **friendlistwidget:** use nullptr instead of `0` ([f1543144](https://github.com/qTox/qTox/commit/f1543144be7726a9d2dbb6e04ca9b0a4c1000737)) * **friendwidget:** the limitation of the group's name in the shortcut menu ([d357fe1c](https://github.com/qTox/qTox/commit/d357fe1c650f57f4dad34294b544640f2d06eb88)) * **generalform:** call UI retranslation when date or time format changes ([d601599d](https://github.com/qTox/qTox/commit/d601599de8c2d18b98f5fabd1b8ac78468fada8e)) * **genericchatform:** * Fixed position of screenshot button ([86e44143](https://github.com/qTox/qTox/commit/86e44143ad7e473198ff6aa340be4136f2cca569)) * separate messages from different days ([8ebad59a](https://github.com/qTox/qTox/commit/8ebad59a3e3aa42324a5821760b0752546a1e5ac)) * **groupinviteform:** * escape HTML ([e4bc8570](https://github.com/qTox/qTox/commit/e4bc857037cf7cecd88929af8af453a800af2201)) * consider dateTime format in group invites ([6030b083](https://github.com/qTox/qTox/commit/6030b083b131cb367971eb205403d80764c2564e), closes [#3058](https://github.com/qTox/qTox/issues/3058)) * **i18n:** Divide getting and translating Toxme error message ([98a1f23b](https://github.com/qTox/qTox/commit/98a1f23bfbbe341c50ed9ee2cc52c1f9dfc15110)) * **l10n:** * remove unnecessary space in Czech translation ([47153b3d](https://github.com/qTox/qTox/commit/47153b3d77011c4a944bd597ad3a059e9668ed7c)) * missing argument in German translation ([e6e666fa](https://github.com/qTox/qTox/commit/e6e666fa8ce36cd3792d4536a59ab2b65fd5b546)) * incorrect/missing arguments in Arabic translation ([82bd897b](https://github.com/qTox/qTox/commit/82bd897ba46e388ecdb60c16484e056a607e45fa)) * **loginscreen.cpp:** fix password input focus after mouse click ([6e8ea15a](https://github.com/qTox/qTox/commit/6e8ea15a15c82ce29a5d0d1267d699af75a5f3cd)) * **main:** Closing file before removing ([29ab61ef](https://github.com/qTox/qTox/commit/29ab61efdf2e4c335c9f32d3552934bc59057830)) * **markdown:** Remove spaces from markdown translation ([fca5f155](https://github.com/qTox/qTox/commit/fca5f15532ddbc21d70e6295d0742218cbc6fe1e)) * **passwordedit.cpp:** Fix build issue with Qt 5.3 ([f18db4fd](https://github.com/qTox/qTox/commit/f18db4fd508c170fb93ae1e04fc7f2e0b5486bb7), closes [#3416](https://github.com/qTox/qTox/issues/3416)) * **passwordfields:** use PasswordEdit widget for all password fields ([e3d0cc0e](https://github.com/qTox/qTox/commit/e3d0cc0e55ee91d85942b3beddac716435b78bee), closes [#3378](https://github.com/qTox/qTox/issues/3378)) * **platform:** Added checkCapsLock OSX implementation ([35a0e1fb](https://github.com/qTox/qTox/commit/35a0e1fb6f0e0417ddc24408904a334d95e05c65)) * **profile:** * Fix for opening file dialog using Nautilus file manager ([881409b9](https://github.com/qTox/qTox/commit/881409b91fe133ae53b2e0344a6d764fd0d0a6d7), closes [#3436](https://github.com/qTox/qTox/issues/3436)) * change password buttons behaviour ([f9edd39b](https://github.com/qTox/qTox/commit/f9edd39bba64a0fd92c6e9820f3265618d2057fc), closes [#3300](https://github.com/qTox/qTox/issues/3300)) * **profileform:** set parent for validator ([93c6aa8a](https://github.com/qTox/qTox/commit/93c6aa8ac0519110e4de249346811e60dbb006c6)) * **qtox.pro:** don't depend on GTK in order to build on Linux ([2d06b996](https://github.com/qTox/qTox/commit/2d06b9960c60f1fc886ae04c595bd9a921cff316)) * **screen-grabber:** fix crash ([780a0179](https://github.com/qTox/qTox/commit/780a017928cfa5613fddada6e787bd6f3965baa9)) * **settings:** * Look for portable setting in module path, not CWD ([17e57982](https://github.com/qTox/qTox/commit/17e57982dffa0dabffb7839cfd34fb34ef51ec88)) * correct ordering of languages ([7c63594a](https://github.com/qTox/qTox/commit/7c63594adf16d033c2ad40b369bb79fa98f8a95b)) * make it clear that `Markdown` is about text formatting ([67d01a73](https://github.com/qTox/qTox/commit/67d01a73c440f6b4ffeb9d5f0dbf560c832cf41f)) * **simple_make.sh:** * add sqlite dependencies for Fedora ([5cb271b0](https://github.com/qTox/qTox/commit/5cb271b0c0ce2d3a1700d05559c7a0a3ce34af25)) * add missing dependencies for Fedora ([5b51f71f](https://github.com/qTox/qTox/commit/5b51f71ff86a0784aa0e74e13face02eda5d6c47), closes [#2998](https://github.com/qTox/qTox/issues/2998)) * **systemtray:** don't activate qTox widget on tray icon click in Unity backend ([2f0ffdd2](https://github.com/qTox/qTox/commit/2f0ffdd27e8750f1554d0d10174ce3e831e2ac9a)) * **systemtrayicon:** * don't set an invalid and useless icon on GTK ([a13c5667](https://github.com/qTox/qTox/commit/a13c566736b11880e43fe3af870b521175aeabe7), closes [#3154](https://github.com/qTox/qTox/issues/3154)) * **toxsave, profileimporter:** Added `remove` function call before overwrite file ([58ea0afe](https://github.com/qTox/qTox/commit/58ea0afed1c699badecb6310786b6b018e23c0e5)) * **translator:** Added layout direction reset on translation. ([927d512f](https://github.com/qTox/qTox/commit/927d512fa2a9925db59a22e0a46a7ae992995924)) * **ui:** Prevent suicide crash on logout ([2bdd9824](https://github.com/qTox/qTox/commit/2bdd9824c758a7ce3ff0cb06fa59ed7d8c81455a), closes [#2480](https://github.com/qTox/qTox/issues/2480)) * **updater:** Use module path, not working dir ([0a2e96ab](https://github.com/qTox/qTox/commit/0a2e96ab07a9c0427660124a2f1ff45bec0ba0b8)) * **video:** * guard storeVideoFrame() against freeing in-use memory ([5b31b5db](https://github.com/qTox/qTox/commit/5b31b5db723e97daa7404d5c17ee9340d97198e8)) * force the use of non-deprecated pixel formats for YUV ([df3345dc](https://github.com/qTox/qTox/commit/df3345dce5827672a4106b11575728690fcd38f2)) * use a QReadWriteLock to manage camera access ([de6475f3](https://github.com/qTox/qTox/commit/de6475f3d362442122b574c6f5b3f84a60b90784)) * specify color ranges for pixel formats that are not YUV ([00270ee4](https://github.com/qTox/qTox/commit/00270ee4d21ea5cc28591d568f1b68bd2c35bf73)) * fix invalid VideoSource ID allocation ([707f7af2](https://github.com/qTox/qTox/commit/707f7af29ab55d75957cf62fc731797881c018ce)) * added declaration for missing biglock in CameraSource ([c4f88df7](https://github.com/qTox/qTox/commit/c4f88df7c986b1fb61caead9ce58d6c9b37cfbfe)) * fix a use-after-free with VideoFrame ([8487dcec](https://github.com/qTox/qTox/commit/8487dcecf846043733d1c77083169d9599bfc8a1)) * fix slanted video when video size is not divisible by 8 ([904495d2](https://github.com/qTox/qTox/commit/904495d2bf2b85da4c2f381778a0bb09b4ce1bbf)) * fix memory leak caused by unfreed buffers in CoreVideoSource ([3df6b990](https://github.com/qTox/qTox/commit/3df6b990ae4e137ae4069961131ebb39e1dbdf06)) * fix CoreAV and VideoSurface to conform to new VideoFrame ([277ddc3d](https://github.com/qTox/qTox/commit/277ddc3d2f31078fdb4320ce834785c2120d02a1)) * Changed minimum window size with video ([f8a45b40](https://github.com/qTox/qTox/commit/f8a45b40519de8f38cef80f5ffc41c19417e0139)) * do not list the same mode twice ([03c39236](https://github.com/qTox/qTox/commit/03c392369477870273cfc6cc69986d5ffad82bd3)) * fix video resolution setting ([b4df3c8b](https://github.com/qTox/qTox/commit/b4df3c8b4aabf62f2170586a6a9a49d648d60040), closes [#1033](https://github.com/qTox/qTox/issues/1033)) * **videoframe:** Added correct image copy ([1ddc1371](https://github.com/qTox/qTox/commit/1ddc1371a0ea12de9621bd387b633ee8cdd575b3)) * **widget:** * change received files execution method ([def2e880](https://github.com/qTox/qTox/commit/def2e880c9a4190b697bc97edd6d775b15ea1978), closes [#3140](https://github.com/qTox/qTox/issues/3140)) * Added saving window state before closing ([bfb5dae6](https://github.com/qTox/qTox/commit/bfb5dae6faa12fdcc09952d27e9904314e01762a)) * properly open chat window ([c17c3405](https://github.com/qTox/qTox/commit/c17c3405bfd14acd494fb81dd624740ea0f0ccd4), closes [#3386](https://github.com/qTox/qTox/issues/3386)) * rename "Activate" to "Show" ([6173199a](https://github.com/qTox/qTox/commit/6173199a5b8eb81c9957044bc31edb2a77248a45)) * delete icon in destructor ([f82f49da](https://github.com/qTox/qTox/commit/f82f49da4d88fa89349146f5083366b3773187c3)) * open a chat window instead of contacts list in multi-window mode ([fdf0cbb1](https://github.com/qTox/qTox/commit/fdf0cbb1e1b82f4f4327bb0a1d6d64da340ffadd), closes [#3212](https://github.com/qTox/qTox/issues/3212)) * show unread messages notification ([c81e6e2d](https://github.com/qTox/qTox/commit/c81e6e2dd1d499a0b14bf2a0f494f31cbfcc7329)) * properly show status messages ([dcb8c3f3](https://github.com/qTox/qTox/commit/dcb8c3f3232c5458d86e1595aa704ce3362cb41b), closes [#3123](https://github.com/qTox/qTox/issues/3123)) * **x11grab:** try and use the current display ([294bdab7](https://github.com/qTox/qTox/commit/294bdab77f8fb6c2b29917f5318ffe4ec2bc2ab6)) ## v1.4.1 (2016-05-09) This release fixes an issue with the updater not installing updates correctly. This update also fixes some problems with portable mode, which could affect where the updater downloaded files. #### Bug Fixes * **settings:** Look for portable setting in module path, not CWD ([95634f1c](https://github.com/qTox/qTox/commit/95634f1cfebdec0aadd71077175d8258a4e89d67)) * **updater:** Use module path, not working dir ([0f1c8a78](https://github.com/qTox/qTox/commit/0f1c8a783beb9d09f3af01b68a2054379e16b1dd)) ## v1.4.0 (2016-04-24) Time flows, and with the flow come new features, new bugfixes, and hopefully no new bugs. With this release, a partial changelog of changes since 1.3.0 is added. Next release will contain a changelog with all changes. We are hoping that you'll enjoy the new stuff. Cheers. :) *PS.* If it's not clear from the changelog below, audio groupchats have been ~fixed. #### Breaking Changes * disable building with filter_audio by default ([116cc936](https://github.com/qTox/qTox/commit/116cc9366cd45eaff7262b92a8eb4d0fdbede096)) #### Bug Fixes * close groupcall if alone ([98d51399](https://github.com/qTox/qTox/commit/98d513990eabb0038112aa2242d45b22e58b2e43)) * disable netcamview if no peer left ([622b543d](https://github.com/qTox/qTox/commit/622b543d9a8d29fca004cf41d7701b3dbef4eebf)) * audiocall button disabled in groupchats ([db4f02a0](https://github.com/qTox/qTox/commit/db4f02a0c4065014bdbadab4a622aae14a112c76)) * Close logfile only after the disabling logging to file ([de487890](https://github.com/qTox/qTox/commit/de4878909d75d9d4312db025f8c0fc30f9f79f39)) * Make logMessageHandler thread-safe ([a7ffc08c](https://github.com/qTox/qTox/commit/a7ffc08cdb9521e1291e768d29aa39ee53a30dbe)) * Deadlock while rotating logs ([c1e2a3c5](https://github.com/qTox/qTox/commit/c1e2a3c5b670c246c1acc138a8698a1f54591173)) * increase faux offline message timeout ([76d8e193](https://github.com/qTox/qTox/commit/76d8e19320d2f15aeb019f72ecaaac2d6a23feab)) * remove unnecessary qDebug call ([66f96019](https://github.com/qTox/qTox/commit/66f96019cb7389eb847b880a2e76bbd68a0ac288)) * **Widget::updateIcons:** * workaround QIcon fallback bug ([0b53c4fd](https://github.com/qTox/qTox/commit/0b53c4fd5c71f43a240f2d980bac4e0166fffd2a)) * fix the way systray icons are loaded ([90874a47](https://github.com/qTox/qTox/commit/90874a478fb3cc6d953a0e37aeb110b95066eb19)) * **addfriendform:** Removed extra connect return press ([66bcfdae](https://github.com/qTox/qTox/commit/66bcfdae3c8ae1f9d0b4a4fb1b3bf8f53440d53d)) * **addfriendform, widget:** Remove Accepted Request ([53071e95](https://github.com/qTox/qTox/commit/53071e952e6d04f97fc8b3e1ab82e0fff8dd5293)) * **chatform:** regression in detecting `tox:` type IDs ([48f3fb7d](https://github.com/qTox/qTox/commit/48f3fb7dcbf5ce990229114158f38098320ede97)) * **core, widget:** Added checks ([f28c3a16](https://github.com/qTox/qTox/commit/f28c3a16ae6d54ed66ce64fbe7c5605badd34e65)) * **file transfer widget:** QPushButton allows image to overflow ([32d588a4](https://github.com/qTox/qTox/commit/32d588a499a76a37340e930e17cb096ec4c27f24), closes [#3042](https://github.com/qTox/qTox/issues/3042)) * **genericchatitemwidget, micfeedbackwidget:** Added members init in the constructor ([27faec91](https://github.com/qTox/qTox/commit/27faec918a023c5bba4b6ee67854438507220b35)) * **groupaudio:** * don't set button to green while call running ([6d355154](https://github.com/qTox/qTox/commit/6d3551548b2568d5587992be1ecb3b559dd827f5)) * don't play audio while call is inactive ([5339ad97](https://github.com/qTox/qTox/commit/5339ad978bd2f962e0799e48452ac2549edf8bc7)) * avoid deadlock when ending groupcall ([afcd146a](https://github.com/qTox/qTox/commit/afcd146a5bd78d10083a5bf3eb009face24f02b6)) * **groupinviteform:** * make list of groups scrollable ([b74ecd92](https://github.com/qTox/qTox/commit/b74ecd92d2f925ff5defee1a4ca0cd50ecf6a2e6)) * translation invite message ([24efaf05](https://github.com/qTox/qTox/commit/24efaf0594576ba4e761653c6d612f98b440c28a)) * remove deleted buttons from set ([f137ba71](https://github.com/qTox/qTox/commit/f137ba710cc823e920adf2976ee1061f5a61f9aa)) * **l18n:** make typing notification & groupchat name translatable ([43e61041](https://github.com/qTox/qTox/commit/43e610415a094a649160bdfab1c571a8fc6fee1f)) * **login screen:** Change text on login tooltip ([4e065f13](https://github.com/qTox/qTox/commit/4e065f1395b8feb35431c7736e531b6a0a6fee45)) * **main:** Added check sodium_init result ([64a19d34](https://github.com/qTox/qTox/commit/64a19d34192e2ca9f864b1ccc3a320fb90bc780b)) * **profile:** Don't require .ini to load profile ([56a36e2e](https://github.com/qTox/qTox/commit/56a36e2e0a1023a77f0b047a7273295a35aa1833)) * **profileform:** * Add toxme username limitation ([132f87c0](https://github.com/qTox/qTox/commit/132f87c05e89e21824efa2c4dfef83d807e0f5bf)) * Deleted extra check and extra url ([1f7e23d0](https://github.com/qTox/qTox/commit/1f7e23d007887a0e198b8643961f391a06faf36c)) * Fixed very quick relogin segfault ([88de3a0a](https://github.com/qTox/qTox/commit/88de3a0a7a09b89b0a621452e53b4d6d4ec3bfe8)) * Fixed segfault on logut ([2e9295f4](https://github.com/qTox/qTox/commit/2e9295f420fc6832091337813010319166450270)) * Fix tab order, fix loop ([65ab1f4e](https://github.com/qTox/qTox/commit/65ab1f4e14d3ebc9dd13d4308988d999c83a5a47)) * **screenshot:** incorrect screenshot capture resolution under HiDPI ([a36248b5](https://github.com/qTox/qTox/commit/a36248b5013c41c332df3bc13cac428bb0d3b18e)) * **systemtrayicon:** only delete the systray backend that was used ([1d6f32c9](https://github.com/qTox/qTox/commit/1d6f32c9f9e481dc2fed445bb96ea2666f6d69d8)) * **systemtrayicon, widget:** Added deallocate memory ([cbb7eeca](https://github.com/qTox/qTox/commit/cbb7eeca62f9ec8ad2047b41a7cb914b27d6c618)) * **title:** Change title on initial startup on "Add friend" ([47d94045](https://github.com/qTox/qTox/commit/47d940455d35e4b0f76081a8877a881ebf843c86), closes [#3100](https://github.com/qTox/qTox/issues/3100)) * **toxme:** * Delete extra check ([d1b706a4](https://github.com/qTox/qTox/commit/d1b706a4b3926fc3bd1f7af115358cf171080021)) * Fixed potential memory leaks ([8f4b6869](https://github.com/qTox/qTox/commit/8f4b6869f178c12a43d7d37336f32f8ecf7b1427)) * Fix possible segfault ([11ec3947](https://github.com/qTox/qTox/commit/11ec3947f566e7083a6345ce2eea317f31219c5e)) * Use format strings ([fc2a5723](https://github.com/qTox/qTox/commit/fc2a5723092ed1c2fe6b0991d9bd9def6ec24a98)) * Translation fixes ([9565a817](https://github.com/qTox/qTox/commit/9565a8175558d64c9ffdc2ec6b64e69d9ecdc58d)) * **video:** * uses explicit default screen from QGuiApplication ([d2189f38](https://github.com/qTox/qTox/commit/d2189f3891b01ca9c4fa55b080e4457887c00f28)) * usage of invalid file descriptors on error ([556a8750](https://github.com/qTox/qTox/commit/556a8750a1d4c57d02bfe3a4caaffceaf816c783)) * incorrect desktop video resolution when using HiDPI ([75b40d0a](https://github.com/qTox/qTox/commit/75b40d0a6f82110aac62c864149210f8b491df4b)) * **widget:** Change focus after creating group ([b111c509](https://github.com/qTox/qTox/commit/b111c509a7dcf3a0c3d7a72d92c080ff7fd92731)) * **widget, contentdialog:** Added reset icon after activate chat window ([4edc5996](https://github.com/qTox/qTox/commit/4edc5996c74d8679c270c50328642665ed6b3aed)) #### Performance * **camerasource:** Passed parameter by reference ([910c41f4](https://github.com/qTox/qTox/commit/910c41f4fab106e1c736685ffad33141c1528107)) * **contentdialog:** Delete redundant conditions ([904a1d49](https://github.com/qTox/qTox/commit/904a1d490973ed57327bc093aeaae1e7317b52e8)) #### Features * install icons with make install on unix ([218228b6](https://github.com/qTox/qTox/commit/218228b696367e688537a04afeb62055037afdc3)) * disable building with filter_audio by default ([116cc936](https://github.com/qTox/qTox/commit/116cc9366cd45eaff7262b92a8eb4d0fdbede096)) * **audio:** add (repair) support for group audio calls ([356543ca](https://github.com/qTox/qTox/commit/356543ca3ba9d084e9739bcd7b37c4597a245d1d)) * **chatform:** add support for non-local file and samba share links ([47764c03](https://github.com/qTox/qTox/commit/47764c0397a51da2662021a781cefe29af46bf25)) * **profileform:** Added ability to change toxme server ([41c5d4bf](https://github.com/qTox/qTox/commit/41c5d4bf14986348f04f9e60f538e79c1edc4a04)) * **toxme:** * Add save toxme info ([204fe1d3](https://github.com/qTox/qTox/commit/204fe1d3dec00c0f4408f8a98c100377759f1218)) * Add ToxMe registration ([cb8bf134](https://github.com/qTox/qTox/commit/cb8bf134d2e8d0aaf60e136c51f79201b62f67f9)) qTox/CMakeLists.txt000066400000000000000000000507221415623743500145710ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see ################################################################################ # # :: CMake configuration # ################################################################################ cmake_minimum_required(VERSION 2.8.11) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) option(PLATFORM_EXTENSIONS "Enable platform specific extensions, requires extra dependencies" ON) option(UPDATE_CHECK "Enable automatic update check" ON) option(USE_CCACHE "Use ccache when available" ON) option(SPELL_CHECK "Enable spell cheching support" ON) option(SVGZ_ICON "Compress the SVG icon of qTox" ON) option(ASAN "Compile with AddressSanitizer" OFF) option(DESKTOP_NOTIFICATIONS "Use snorenotify for desktop notifications" OFF) option(STRICT_OPTIONS "Enable strict compile options, used by CI" OFF) # process generated files if cmake >= 3.10 if(POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "Options are: None, Debug, Release, RelWithDebInfo, MinSizeRel." FORCE) endif() if(ASAN) set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") endif() set(ENV{PKG_CONFIG_PATH} ${CMAKE_SOURCE_DIR}/libs/lib/pkgconfig:/opt/ffmpeg/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}) # necessary to find openal-soft on mac os if(APPLE) set(ENV{PKG_CONFIG_PATH} /usr/local/opt/openal-soft/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}) endif() execute_process( COMMAND brew --prefix qt5 OUTPUT_VARIABLE QT_PREFIX_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) project(qtox) # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) set(RCC_OPTIONS -compress 9 -threshold 0) # Use C++11. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") # Hardening flags (ASLR, warnings, etc) set(POSITION_INDEPENDENT_CODE True) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wstrict-overflow") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wstrict-aliasing") # Extra-strict compile options that we don't want to subject all users to by default if (STRICT_OPTIONS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") endif() # avoid timestamps in binary for reproducible builds, not added until GCC 4.9 include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG(-Wdate-time COMPILER_SUPPORTS_WDATE_TIME) if (COMPILER_SUPPORTS_WDATE_TIME) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wdate-time") endif() if (NOT WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-all") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wstack-protector") endif() if (UNIX AND NOT APPLE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,now") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") endif() include(CheckAtomic) # Use ccache when available to speed up builds. if (USE_CCACHE) find_program(CCACHE_FOUND ccache) if(CCACHE_FOUND) set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) message(STATUS "using ccache") else() message(STATUS "ccache not found") endif() else() message(STATUS "ccache disabled; set option USE_CCACHE=ON to use ccache if available") endif() # Search for headers in current directory. include_directories(${CMAKE_BINARY_DIR}) include_directories(${CMAKE_SOURCE_DIR}) include(Dependencies) if(NOT Qt5Widgets_VERSION VERSION_LESS "5.9") # Drop the file modification time of source files from generated files # to help with reproducible builds. We do not use QFileInfo.lastModified # so this has no unwanted side effects. This mtime started appearing in # Qt 5.8. The option to force the old file format without mtime was # added in Qt 5.9. See https://bugreports.qt.io/browse/QTBUG-58769 set(RCC_OPTIONS ${RCC_OPTIONS} -format-version 1) endif() ################################################################################ # # :: qTox main library sources # ################################################################################ qt5_wrap_ui(${PROJECT_NAME}_FORMS src/chatlog/content/filetransferwidget.ui src/loginscreen.ui src/mainwindow.ui src/widget/about/aboutfriendform.ui src/widget/form/loadhistorydialog.ui src/widget/form/profileform.ui src/widget/form/removefrienddialog.ui src/widget/form/searchsettingsform.ui src/widget/form/setpassworddialog.ui src/widget/form/settings/aboutsettings.ui src/widget/form/settings/advancedsettings.ui src/widget/form/settings/avform.ui src/widget/form/settings/generalsettings.ui src/widget/form/settings/privacysettings.ui src/widget/form/settings/userinterfacesettings.ui) qt5_add_translation(${PROJECT_NAME}_QM_FILES translations/ar.ts translations/be.ts translations/bg.ts translations/cs.ts translations/da.ts translations/de.ts translations/el.ts translations/eo.ts translations/es.ts translations/et.ts translations/fa.ts translations/fi.ts translations/fr.ts translations/he.ts translations/hr.ts translations/hu.ts translations/it.ts translations/ja.ts translations/jbo.ts translations/ko.ts translations/lt.ts translations/mk.ts translations/nl.ts translations/no_nb.ts translations/pl.ts translations/pr.ts translations/pt.ts translations/pt_BR.ts translations/ro.ts translations/ru.ts translations/sk.ts translations/sl.ts translations/sr.ts translations/sr_Latn.ts translations/sv.ts translations/sw.ts translations/ta.ts translations/tr.ts translations/ug.ts translations/uk.ts translations/zh_CN.ts translations/zh_TW.ts ) qt5_add_resources( ${PROJECT_NAME}_RESOURCES res.qrc ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc DEPENDS ${${PROJECT_NAME}_QM_FILES} OPTIONS ${RCC_OPTIONS} ) if(NOT SMILEYS) set(SMILEYS "") endif() if(NOT "${SMILEYS}" STREQUAL "DISABLED") qt5_add_resources( ${PROJECT_NAME}_RESOURCES smileys/emojione.qrc OPTIONS ${RCC_OPTIONS}) if(NOT "${SMILEYS}" STREQUAL "MIN") qt5_add_resources( ${PROJECT_NAME}_RESOURCES smileys/smileys.qrc OPTIONS ${RCC_OPTIONS}) endif() endif() file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc.in" " ") foreach(qm ${${PROJECT_NAME}_QM_FILES}) get_filename_component(qm_name ${qm} NAME) file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc.in" " ${qm}\n") endforeach(qm) file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc.in" " ") execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) set(${PROJECT_NAME}_SOURCES src/audio/audio.cpp src/audio/audio.h src/audio/backend/alsink.cpp src/audio/backend/alsink.h src/audio/backend/alsource.cpp src/audio/backend/alsource.h src/audio/backend/openal.cpp src/audio/backend/openal.h src/audio/iaudiosettings.h src/audio/iaudiocontrol.h src/audio/iaudiosink.h src/audio/iaudiosource.h src/chatlog/chatlinecontent.cpp src/chatlog/chatlinecontent.h src/chatlog/chatlinecontentproxy.cpp src/chatlog/chatlinecontentproxy.h src/chatlog/chatline.cpp src/chatlog/chatline.h src/chatlog/chatlog.cpp src/chatlog/chatlog.h src/chatlog/chatmessage.cpp src/chatlog/chatmessage.h src/chatlog/content/filetransferwidget.cpp src/chatlog/content/filetransferwidget.h src/chatlog/content/image.cpp src/chatlog/content/image.h src/chatlog/content/notificationicon.cpp src/chatlog/content/notificationicon.h src/chatlog/content/spinner.cpp src/chatlog/content/spinner.h src/chatlog/content/text.cpp src/chatlog/content/text.h src/chatlog/content/timestamp.cpp src/chatlog/content/timestamp.h src/chatlog/content/broken.cpp src/chatlog/content/broken.h src/chatlog/customtextdocument.cpp src/chatlog/customtextdocument.h src/chatlog/documentcache.cpp src/chatlog/documentcache.h src/chatlog/pixmapcache.cpp src/chatlog/pixmapcache.h src/chatlog/toxfileprogress.cpp src/chatlog/toxfileprogress.h src/chatlog/textformatter.cpp src/chatlog/textformatter.h src/core/coreav.cpp src/core/coreav.h src/core/core.cpp src/core/corefile.cpp src/core/corefile.h src/core/core.h src/core/dhtserver.cpp src/core/dhtserver.h src/core/icoresettings.h src/core/toxcall.cpp src/core/toxcall.h src/core/toxencrypt.cpp src/core/toxencrypt.h src/core/toxfile.cpp src/core/toxfile.h src/core/toxfilepause.h src/core/toxid.cpp src/core/toxid.h src/core/groupid.cpp src/core/groupid.h src/core/toxlogger.cpp src/core/toxlogger.h src/core/toxoptions.cpp src/core/toxoptions.h src/core/toxpk.cpp src/core/toxpk.h src/core/contactid.cpp src/core/contactid.h src/core/toxstring.cpp src/core/toxstring.h src/friendlist.cpp src/friendlist.h src/grouplist.cpp src/grouplist.h src/ipc.cpp src/ipc.h src/model/about/aboutfriend.cpp src/model/about/aboutfriend.h src/model/about/iaboutfriend.h src/model/chatroom/chatroom.h src/model/chatroom/friendchatroom.cpp src/model/chatroom/friendchatroom.h src/model/chatroom/groupchatroom.cpp src/model/chatroom/groupchatroom.h src/model/contact.cpp src/model/contact.h src/model/chatlogitem.cpp src/model/chatlogitem.h src/model/friend.cpp src/model/friend.h src/model/message.h src/model/message.cpp src/model/imessagedispatcher.h src/model/friendmessagedispatcher.h src/model/friendmessagedispatcher.cpp src/model/groupmessagedispatcher.h src/model/groupmessagedispatcher.cpp src/model/message.h src/model/message.cpp src/model/groupinvite.cpp src/model/groupinvite.h src/model/group.cpp src/model/group.h src/model/status.cpp src/model/status.h src/model/interface.h src/model/profile/iprofileinfo.h src/model/profile/profileinfo.cpp src/model/profile/profileinfo.h src/model/dialogs/idialogs.h src/model/ichatlog.h src/model/sessionchatlog.h src/model/sessionchatlog.cpp src/model/chathistory.h src/model/chathistory.cpp src/model/toxclientstandards.h src/net/bootstrapnodeupdater.cpp src/net/bootstrapnodeupdater.h src/net/avatarbroadcaster.cpp src/net/avatarbroadcaster.h src/net/toxuri.cpp src/net/toxuri.h src/nexus.cpp src/nexus.h src/persistence/db/rawdatabase.cpp src/persistence/db/rawdatabase.h src/persistence/history.cpp src/persistence/history.h src/persistence/ifriendsettings.h src/persistence/offlinemsgengine.cpp src/persistence/offlinemsgengine.h src/persistence/paths.cpp src/persistence/paths.h src/persistence/profile.cpp src/persistence/profile.h src/persistence/profilelocker.cpp src/persistence/profilelocker.h src/persistence/serialize.cpp src/persistence/serialize.h src/persistence/settings.cpp src/persistence/settings.h src/persistence/settingsserializer.cpp src/persistence/settingsserializer.h src/persistence/smileypack.cpp src/persistence/smileypack.h src/persistence/toxsave.cpp src/persistence/toxsave.h src/video/cameradevice.cpp src/video/cameradevice.h src/video/camerasource.cpp src/video/camerasource.h src/video/corevideosource.cpp src/video/corevideosource.h src/video/genericnetcamview.cpp src/video/genericnetcamview.h src/video/groupnetcamview.cpp src/video/groupnetcamview.h src/video/ivideosettings.h src/video/netcamview.cpp src/video/netcamview.h src/video/videoframe.cpp src/video/videoframe.h src/video/videomode.cpp src/video/videomode.h src/video/videosource.cpp src/video/videosource.h src/video/videosurface.cpp src/video/videosurface.h src/widget/about/aboutfriendform.cpp src/widget/about/aboutfriendform.h src/widget/categorywidget.cpp src/widget/categorywidget.h src/widget/chatformheader.cpp src/widget/chatformheader.h src/widget/circlewidget.cpp src/widget/circlewidget.h src/widget/contentdialog.cpp src/widget/contentdialog.h src/widget/contentdialogmanager.cpp src/widget/contentdialogmanager.h src/widget/contentlayout.cpp src/widget/contentlayout.h src/widget/emoticonswidget.cpp src/widget/emoticonswidget.h src/widget/flowlayout.cpp src/widget/flowlayout.h src/widget/searchform.cpp src/widget/searchform.h src/widget/searchtypes.h src/widget/form/addfriendform.cpp src/widget/form/addfriendform.h src/widget/form/chatform.cpp src/widget/form/chatform.h src/widget/form/filesform.cpp src/widget/form/filesform.h src/widget/form/genericchatform.cpp src/widget/form/genericchatform.h src/widget/form/groupchatform.cpp src/widget/form/groupchatform.h src/widget/form/groupinviteform.cpp src/widget/form/groupinviteform.h src/widget/form/groupinvitewidget.cpp src/widget/form/groupinvitewidget.h src/widget/form/loadhistorydialog.cpp src/widget/form/loadhistorydialog.h src/widget/form/profileform.cpp src/widget/form/profileform.h src/widget/form/searchsettingsform.cpp src/widget/form/searchsettingsform.h src/widget/form/setpassworddialog.cpp src/widget/form/setpassworddialog.h src/widget/form/settings/aboutform.cpp src/widget/form/settings/aboutform.h src/widget/form/settings/advancedform.cpp src/widget/form/settings/advancedform.h src/widget/form/settings/avform.cpp src/widget/form/settings/avform.h src/widget/form/settings/generalform.cpp src/widget/form/settings/generalform.h src/widget/form/settings/genericsettings.cpp src/widget/form/settings/genericsettings.h src/widget/form/settings/privacyform.cpp src/widget/form/settings/privacyform.h src/widget/form/settings/userinterfaceform.h src/widget/form/settings/userinterfaceform.cpp src/widget/form/settings/verticalonlyscroller.cpp src/widget/form/settings/verticalonlyscroller.h src/widget/form/settingswidget.cpp src/widget/form/settingswidget.h src/widget/form/tabcompleter.cpp src/widget/form/tabcompleter.h src/widget/friendlistlayout.cpp src/widget/friendlistlayout.h src/widget/friendlistwidget.cpp src/widget/friendlistwidget.h src/widget/friendwidget.cpp src/widget/friendwidget.h src/widget/genericchatitemlayout.cpp src/widget/genericchatitemlayout.h src/widget/genericchatitemwidget.cpp src/widget/genericchatitemwidget.h src/widget/genericchatroomwidget.cpp src/widget/genericchatroomwidget.h src/widget/groupwidget.cpp src/widget/groupwidget.h src/widget/gui.cpp src/widget/gui.h src/widget/loginscreen.cpp src/widget/loginscreen.h src/widget/maskablepixmapwidget.cpp src/widget/maskablepixmapwidget.h src/widget/notificationedgewidget.cpp src/widget/notificationedgewidget.h src/widget/notificationscrollarea.cpp src/widget/notificationscrollarea.h src/widget/passwordedit.cpp src/widget/passwordedit.h src/widget/qrwidget.cpp src/widget/qrwidget.h src/widget/splitterrestorer.cpp src/widget/splitterrestorer.h src/widget/style.cpp src/widget/style.h src/widget/tool/activatedialog.cpp src/widget/tool/activatedialog.h src/widget/tool/adjustingscrollarea.cpp src/widget/tool/adjustingscrollarea.h src/widget/tool/callconfirmwidget.cpp src/widget/tool/callconfirmwidget.h src/widget/tool/chattextedit.cpp src/widget/tool/chattextedit.h src/widget/tool/croppinglabel.cpp src/widget/tool/croppinglabel.h src/widget/tool/flyoutoverlaywidget.cpp src/widget/tool/flyoutoverlaywidget.h src/widget/tool/friendrequestdialog.cpp src/widget/tool/friendrequestdialog.h src/widget/tool/identicon.cpp src/widget/tool/identicon.h src/widget/tool/movablewidget.cpp src/widget/tool/movablewidget.h src/widget/tool/profileimporter.cpp src/widget/tool/profileimporter.h src/widget/tool/recursivesignalblocker.cpp src/widget/tool/recursivesignalblocker.h src/widget/tool/removefrienddialog.cpp src/widget/tool/removefrienddialog.h src/widget/tool/screengrabberchooserrectitem.cpp src/widget/tool/screengrabberchooserrectitem.h src/widget/tool/screengrabberoverlayitem.cpp src/widget/tool/screengrabberoverlayitem.h src/widget/tool/screenshotgrabber.cpp src/widget/tool/screenshotgrabber.h src/widget/tool/toolboxgraphicsitem.cpp src/widget/tool/toolboxgraphicsitem.h src/widget/translator.cpp src/widget/translator.h src/widget/widget.cpp src/widget/widget.h ) if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/camera/directshow.cpp src/platform/camera/directshow.h ) set(${PROJECT_NAME}_RESOURCES ${${PROJECT_NAME}_RESOURCES} windows/qtox.rc ) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/camera/v4l2.cpp src/platform/camera/v4l2.h ) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/install_osx.cpp src/platform/install_osx.h ) endif() if (UNIX) set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/posixsignalnotifier.cpp src/platform/posixsignalnotifier.h ) endif() if (PLATFORM_EXTENSIONS) set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/autorun.h src/platform/capslock.h src/platform/timer.h ) if (WIN32) set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/autorun_win.cpp src/platform/capslock_win.cpp src/platform/timer_win.cpp ) elseif (${X11_EXT}) set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/autorun_xdg.cpp src/platform/capslock_x11.cpp src/platform/timer_x11.cpp src/platform/x11_display.cpp ) elseif (${APPLE_EXT}) set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/autorun_osx.cpp src/platform/capslock_osx.cpp src/platform/timer_osx.cpp ) endif() endif() add_definitions(-DQT_MESSAGELOGCONTEXT=1) if (NOT DEFINED ENABLE_STATUSNOTIFIER AND UNIX AND NOT APPLE) set(ENABLE_STATUSNOTIFIER True) endif() if(AVFOUNDATION_FOUND) set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/camera/avfoundation.mm src/platform/camera/avfoundation.h) endif() if(${UPDATE_CHECK}) add_definitions(-DUPDATE_CHECK_ENABLED=1) set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/net/updatecheck.cpp src/net/updatecheck.h) message(STATUS "using update check") else() message(STATUS "NOT using update check") endif() if (${DESKTOP_NOTIFICATIONS}) add_definitions(-DDESKTOP_NOTIFICATIONS=1) set(${PROJECT_NAME}_SOURCES ${${PROJECT_NAME}_SOURCES} src/platform/desktop_notifications/desktopnotify.cpp src/platform/desktop_notifications/desktopnotify.h) message(STATUS "using desktop notifications") else() add_definitions(-DDESKTOP_NOTIFICATIONS=0) message(STATUS "not using desktop notifications") endif() if (MINGW) STRING(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if (CMAKE_BUILD_TYPE_LOWER MATCHES debug) # Allows wine to display source code file names and line numbers on crash in its backtrace set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -gdwarf-2") endif() endif() # the compiler flags for compiling C sources MESSAGE( STATUS "CMAKE_C_FLAGS: " ${CMAKE_C_FLAGS} ) # the compiler flags for compiling C++ sources MESSAGE( STATUS "CMAKE_CXX_FLAGS: " ${CMAKE_CXX_FLAGS} ) add_library(${PROJECT_NAME}_static STATIC ${${PROJECT_NAME}_FORMS} ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_QM_FILES} ${${PROJECT_NAME}_RESOURCES}) target_link_libraries(${PROJECT_NAME}_static ${CMAKE_REQUIRED_LIBRARIES} ${ALL_LIBRARIES}) add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE ${${PROJECT_NAME}_RESOURCES} src/main.cpp) target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_static ${CMAKE_REQUIRED_LIBRARIES} ${ALL_LIBRARIES}) include(Testing) include(Installation) qTox/CONTRIBUTING.md000066400000000000000000000331211415623743500142540ustar00rootroot00000000000000- [Filing an issue](#filing-an-issue) - [Must read](#must-read) - [Good to know](#good-to-know) - [How to start contributing](#how-to-start-contributing) - [Before you start…](#before-you-start) - [Must read](#must-read) - [Pull request](#pull-request) - [How to open a pull request](#how-to-open-a-pull-request) - [How to deal with large amounts of merge conflicts](#merge-conflicts) - [Git Commit Guidelines](#commit) - [Commit Message Format](#commit-message-format) - [Header](#header) - [Type](#type) - [Scope](#scope) - [Subject](#subject) - [Body](#body) - [Reviewing](#reviewing) - [Testing PRs](#testing-prs) - [Git config](#git-config) - [Coding guidelines](#coding-guidelines) Note that you don't need to know all of the `CONTRIBUTING.md` – it is there to help you with things as you go, and make things easier, not harder. Skim through it, and when you will be doing something that relevant section will apply to, just go back to it and read in more detail about what is the best course of action. You don't even need to memorize the section – after all, it still will be there next time you might need it. `:-)` # Filing an issue ### Must read * If you aren't sure, you can ask on the [**IRC channel**](https://webchat.freenode.net/?channels=qtox) or read our [**wiki**](https://github.com/qTox/qTox/wiki) first. * Do a quick **search**. Others might have already reported the issue. * Write in **English**! * Provide **version** information (you can find version numbers in menu `Settings → About`): ``` OS: qTox version: Commit hash: toxcore: Qt: ``` * Provide **steps** to reproduce the problem, it will be easier to pinpoint the fault. * **Screenshots**! A screenshot is worth a thousand words. Just upload it. [(How?)](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests) ### Good to know * **Patience**. The dev team is small and resource limited. Devs have to find time, analyze the problem and fix the issue, it all takes time. :clock3: * If you can code, why not become a **contributor** by fixing the issue and opening a pull request? :wink: * Harsh words or threats won't help your situation. What's worse, your complaint will (very likely) be **ignored**. :fearful: # How to start contributing ## Before you start… Before you start contributing, first decide for a specific topic you want to work on. Pull requests, which are spanning multiple topics (e.g. "general qTox code cleanup") or introduce fundamental architectural changes are rare and require additional attention and maintenance. Please also read the following simple rules we need to keep qTox a "smooth experience" for everybody involved. ## Must read: * Use [**commit message format**](#commit-message-format). * Read our [**coding guidelines**](#coding-guidelines). * Keep the title **short** and provide a **clear** description about what your pull request does. * Provide **screenshots** for UI related changes. * Keep your git commit history **clean** and **precise** by continuously rebasing/amending your PR. Commits like `xxx fixup` are not needed and rejected during review. * Commit message should state not only what has been changed, but also why a change is needed. * If your commit fixes a reported issue (for example #4134), add the following message to the commit `Fixes #4134.`. [Here is an example](https://github.com/qTox/qTox/commit/87160526d5bafcee7869d6741a06045e13d731d5). ## Pull request *PR = Pull request* Ideally for simple PRs (most of them): * One topic per PR * One commit per PR * If you have several commits on different topics, close the PR and create one PR per topic * If you still have several commits, squash them into only one commit * Amend commit after making changes (`git commit --amend path/to/file`) * Rebase your PR branch on top of upstream `master` before submitting the PR For complex PRs (big refactoring, etc): * Squash only the commits with uninteresting changes like typos, docs improvements, etc… and keep the important and isolated steps in different commits. It's important to keep amount of changes in the PR small, since smaller PRs are easier to review and merging them is quicker. PR diff shouldn't exceed `300` changed lines, unless it has to. ## How to open a pull request 1. Fork the qTox repository on Github to your existing account. 2. Open a Terminal and do the following steps: ```bash # Go to a directory of your choice, where the qTox directory will be created: cd /to/the/directory # Clone the forked repo: git clone git@github.com:/qTox.git # Add the "upstream" remote to be able to fetch from the qTox upstream repository: git remote add upstream https://github.com/qTox/qTox.git # Fetch from the "upstream" repository git fetch upstream # Point the local "master" branch to the "upstream" repository git branch master --set-upstream-to=upstream/master ``` You're now all set to create your first pull request! Hooray! :) Still in Terminal, do the following steps to actually create the pull request: ```bash # Fetch from the "upstream" repository: git fetch upstream master:master # Checkout a local branch on up-to-date "master" and give it a sane name, e.g.: git checkout -b feat/brandnew-feature master ``` Now do your changes and commit them by your heart's desire. When you think you're ready to push for the first time, do the following: ```bash # Push to the new upstream branch and link it for synchronization git push -u origin feat/brandnew-feature # From now on, you can simply… git push # ...to your brand new pull request. ``` That's it! Happy contributing! ## How to deal with large amounts of merge conflicts Usually you want to avoid conflicts and they should be rare. If conflicts appear anyway, they are usually easy enough to solve quickly and safely. However, if you find yourself in a situation with large amounts of merge conflicts, this is an indication that you're doing something wrong and you should change your strategy. Still… you probably don't want to throw away and lose all your valuable work. So don't worry, there's a way to get out of that mess. The basic idea is to divide the conflicts into smaller – easier to solve – chunks and probably several (topic) branches. Here's a little "Rule of Thumb" list to get out of it: 1. Split your commit history into topic related chunks (by rebasing/cherry-picking "good" commits). 2. Split "API" and "UI" (widget related) changes into separate commits. 3. Probably split PR into several smaller ones. In addition it helps to regularly keep rebasing on the upstream repository's recent master branch. If you don't have the upstream remote in your repo, add it as described in [How to open a pull request](#how-to-open-a-pull-request). ~~~bash # If not on PR branch, check it out: git checkout my/pr-branch # Now fetch master ALWAYS from upstream repo git fetch upstream master:master # Last, rebase PR branch onto master… git rebase -i master # …and, if everything's clear, force push to YOUR repo (your "origin" Git remote) git push -f ~~~ ## Good to know * **Search** the pull request history! Others might have already implemented your idea and it could be waiting to be merged (or have been rejected already). Save your precious time by doing a search first. * When resolving merge conflicts, do `git rebase `, don't do `git pull`. Then you can start fixing the conflicts. [Here is a good explanation](https://www.atlassian.com/git/tutorials/merging-vs-rebasing). ## Git Commit Guidelines We have very precise rules over how our git commit messages can be formatted. This leads to **more readable messages** that are easy to follow when looking through the **project history**. But also, we use the git commit messages to **generate the qTox change log** using [clog-cli](https://github.com/clog-tool/clog-cli). ### Commit Message Format Each commit message consists of a **header** and a **body**. The header has a special format that includes a **type**, a **scope** and a **subject**: ``` (): ``` The **header** is mandatory and the **body** is optional. The **scope** of the header is also optional. #### Header The header must be a short (72 characters or less) summary of the changes made. #### Type Must be one of the following: * **feat**: A new feature * **fix**: A bug fix * **docs**: Documentation only changes * **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc), but change the style to a more appropriate one * **refactor**: A code change that only improves code readability and reduces complexity, without changing any functionality * **perf**: A code change that improves performance * **revert**: Reverts a previous commit * **test**: Adding missing tests * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation ##### Revert If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit .`, where the hash is the SHA of the commit being reverted. #### Scope The scope could be anything specifying place of the commit change. Note that "place" doesn't necessarily mean location in source code. For example: * `audio` – change affects audio * `video` – change affects video * `settings` – change affects qTox settings * `chatform` * `tray` – change affects tray icon * `l10n` – translation update * `i18n` – something has been made translatable * `build` – change affects build system / scripts, e.g. `CMakeLists.txt`, `simple_make.sh`, etc. * `travis` – change affects Travis CI * `CONTRIBUTING` – change to the contributing guidelines Since people were abusing length of the scope, it's limited to 12 characters. If you're running into the limit, you're doing it wrong. #### Subject The subject contains succinct description of the change: * use the imperative, present tense: "change" not "changed" nor "changes" * don't capitalize first letter * no dot (.) at the end A properly formed git commit subject line should always be able to complete the following sentence: > If applied, this commit will ___your subject line here___ ### Body Wrap the body at 72 characters whenever possible (for example, don't modify long links to follow this rule). Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior. The body contains (in order of appearance): * A detailed **description** of the committed changes. * References to GitHub issues that the commit **closes** (e.g., `Closes #000` or `Fixes #000`). * Any **breaking changes**. Include every section of the body that is relevant for your commit. **Breaking changes** should start with the phrase `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this. ## Reviewing Currently `reviewable.io` is being used to review changes that land in qTox. How to review: 1. Click on the `Reviewable` button in [pull request]. 2. Once Reviewable opens, comment on the lines that need changes. 3. Mark as reviewed only those files that don't require any changes – this makes it easier to see which files need to be changed & reviewed again once change is made. 4. If pull request is good to be merged, press `LGTM` button in Reviewable. 5. Once you're done with evaluating PR, press `Publish` to make comments visible on GitHub. When responding to review: 1. Click on the `Reviewable` button in [pull request]. 2. Once you push changes to the pull request, make drafts of responses to the change requests. - if you're just informing that you've made a requested change, use `Reviewable`'s provided `Done` button. - if you want discuss the change, write a response draft. 3. When discussion points are addressed, press `Publish` button to make response visible on GitHub. Note: * when no one is assigned to the PR, *anyone* can review it * when there are assigned people, only they can mark review as passed ### Testing PRs The easiest way is to use [`test-pr.sh`] script to get PR merged on top of current `master`. E.g. to get pull request `#1234`: ```bash ./test-pr.sh 1234 ``` That should create branches named `1234` and `test1234`. `test1234` is what you would want to test. If script fails to merge branch because of conflicts, fret not, it doesn't need testing until PR author fixes merge conflicts. You might want to leave a comment on the PR saying that it needs a rebase :smile: As for testing itself, there's a nice entry on the wiki: https://github.com/qTox/qTox/wiki/Testing ## Git config *Not a requirement, just a friendly tip. :wink:* It's nice when commits and tags are being GPG-signed. Github has a few articles about configuring & signing. https://help.github.com/articles/signing-commits-using-gpg/ And *tl;dr* version: ```sh gpg --gen-key gpg --send-keys git config --local commit.gpgsign true # also force signing tags git config --local tag.forceSignAnnotated true ``` # Coding Guidelines See [coding_standards.md]. ## Limitations ### Filesystem Windows' unbeaten beauty and clarity: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx Symbols that should be forbidden for filenames under Windows: `<` `>` `:` `"` `/` `\` `|` `?` `*` [pull request]: https://github.com/qTox/qTox/pulls [`test-pr.sh`]: /test-pr.sh [coding_standards.md]: /doc/coding_standards.md qTox/INSTALL.fa.md000066400000000000000000001020671415623743500140460ustar00rootroot00000000000000
# راهنمای نصب - [پیشنیاز‌ها](#پیشنیازها) - [لینوکس](#لینوکس) - [نصب آسان](#نصب-آسان) - [آرچ لینوکس](#آرچ-آسان) - [فدورا](#فدورا-آسان) - [جنتوو](#جنتو-آسان) - [اسلکوار](#اسلکوار-آسان) - [فری بی‌اس‌دی](#فری-بی-اس-دی-آسان) - [نصب Git](#نصب-Git) - [آرچ](#گیت-آرچ) - [دبیان](#گیت-دبیان) - [فدورا](#فدورا-گیت) - [اوپن‌سوزه](#اوپن‌سوزه-گیت) - [اوبونتو](#اوبونتو-گیت) - [کپی کردن qTox](#کپی-کیوتاکس) - [GCC, Qt, FFmpeg, OpenAL Soft و qrencode](#سایر-نیازمندیها) - [آرچ](#سایر-نیاز-آرچ) - [دبیان](#سایر-نیاز-دبیان) - [فدورا](#سایر-نیزا-فدورا) - [اوپن‌سوزه](#سایر-نیاز-اوپن‌سوزه) - [اسلک‌وار](#سایر-نیازها-اسلکوار) - [اوبونتو نسخه بالاتر یا برابر 15.04](#سایر-نیاز-اوبونتو) - [اوبونتو نسخه بالاتر یا برابر با 16.04](#سایر-نیاز-اوبونتو16) - [نیازمندی های toxcore](#نیاز-تاکس-کر) - [آرچ](#آرچ-تاکس-کر) - [دبیان](#دبیان-تاکس-کر) - [فدورا](#فدورا-تاکس-کر) - [اوپن‌سوزه](#اوپن‌سوزه-تاکس-کر) - [اسلک‌وار](#اسلک-وار-تاکس-کر) - [اوبونتو نسخه بالاتر یا برابر 15.04](#اوبونتو-تاکس-کر) - [sqlcipher](#اس-کیو-ال) - [کامپایل toxcore](#کامپایل-تاکس-کر) - [کامپایل qTox](#کامپایل-کیو-تاکس) - [OS X](#او-اس-ایکس) - [ویندوز](#ویندوز) - [کامپایل برای سایر سیستم عامل ها در لینوکس](#کامپایل-برای-سایر-سیتمها) - [کامپایل در ویندوز](#نیتیو) - [سویچ های زمان کامپایل](#سویچهای-کامپایل) ## پیشنیاز‌ها | نام | نسخه | ماژولها | |---------------|-------------|----------------------------------------------------------| | [Qt] | >= 5.5.0 | concurrent, core, gui, network, opengl, svg, widget, xml | | [GCC]/[MinGW] | >= 4.8 | C++11 enabled | | [toxcore] | >= 0.2.10 | core, av | | [FFmpeg] | >= 2.6.0 | avformat, avdevice, avcodec, avutil, swscale | | [CMake] | >= 2.8.11 | | | [OpenAL Soft] | >= 1.16.0 | | | [qrencode] | >= 3.0.3 | | | [sqlcipher] | >= 3.2.0 | | | [pkg-config] | >= 0.28 | | | [filteraudio] | >= 0.0.1 | optional dependency | ## نیازمندیهای اختیاری این نیازمندی‌ها اختیاری هستند و میتوان آنها را در زمان کامپایل کردن فعال یا غیر فعال کرد. فعال کردن یا غیر فعال کردن آنها را میتوان با دادن دستورات لازم به `cmake` در زمان ساخت qTox انجام داد. اگر فایلهای این نیازمندی ها وجود نداشته باشند، qTox بدون قابلیت های مربوطه ساخته خواهد شد. ### نیازمندیهای توسعه این فایلها و برنامه ها برای انجام تست و ویرایش کد و سایر کارهای مربوط به توسعه برنامه مورد نیاز هستند. اگر این فایل ها موجود نباشند این قابلیت ها وجود نخواهند داشت. | نام | نسخه | |---------|---------| | [Check] | >= 0.9 | ### لینوکس #### پشتیبانی از Auto-way | نام | نسخه | |-----------------|----------| | [libXScrnSaver] | >= 1.2 | | [libX11] | >= 1.6.0 | اگر پیشنیازهای مطرح شده در جدول فوق موجود نباشند. این قابلیت ارایه نخواهد شد. #### آیکون برنامه در KDE / آیکون کنار ساعت در GTK | نام | نسخه | |-------------|---------| | [Atk] | >= 2.14 | | [Cairo] | | | [GdkPixbuf] | >= 2.31 | | [GLib] | >= 2.0 | | [GTK+] | >= 2.0 | | [Pango] | >= 1.18 | برای غیرفعال کردن:
`-DENABLE_STATUSNOTIFIER=False -DENABLE_GTK_SYSTRAY=False`
#### آیکن کنار ساعت در محیط Unity به شکل پیش فرض غیر فعال میباشد. | نام | نسخه | |-------------------|-----------| | [Atk] | >= 2.14 | | [Cairo] | | | [DBus Menu] | >= 0.6 | | [GdkPixbuf] | >= 2.31 | | [GLib] | >= 2.0 | | [GTK+] | >= 2.0 | | [libappindicator] | >= 0.4.92 | | [Pango] | >= 1.18 | برای فعال سازی:
`-DENABLE_APPINDICATOR=True`
## لینوکس ### نصب آسان نصب آسان qTox برای ویرایش های مختلفی از لینوکس ارایه شده است: * [آرچ لینوکس](#آرچ-آسان) * [فدورا](#فدورا-آسان) * [جنتوو](#جنتو-آسان) * [اسلکوار](#اسلکوار-آسان) * [فری بی اس دی](#فری-بی-اس-دی-آسان) --- #### آرچ لینوکس PKGBUILD در مخزن `community` موجود است، برای نصب کافی است از این دستور استفاده شود:
```bash pacman -S qtox ```
#### فدورا qTox در مخزن [RPMFusion](https://rpmfusion.org/) آماده دانلود است، برای نصب کافی است:
```bash dnf install qtox ```
#### جنتوو qTox در جنتوو آماده دانلود است. برای نصب آن دستور زیر را اجرا کنید:
```bash emerge qtox ```
#### اسلکوار ساخت SlackBuild برنامه qTox را میتوان در آدرس زیر یافت:
http://slackbuilds.org/repository/14.2/network/qTox/
#### فری بی اس دی qTox به صورت بسته های باینری آماده قابل دانلود است. برای نصب میتوان از دستور زیر استفاده کرد:
```bash pkg install qTox ```
پورت qTox همچنین در ``net-im/qTox`` قابل دسترسی است. برای ساخت و نصب qTox از روی سورس کد برنامه با استفاده از پورت میتوان از دستورات زیر استفاده کرد:
```bash cd /usr/ports/net-im/qTox make install clean ```
---- اگر توزیع مورد نظر شما لیست نشده است، یا میخواهید/نیاز دارید که qTox را کامپایل نمایید، در اینجا راهنمای لازم برای این کار ارایه میشود. ---- قسمت عمده پکیج ها و فایل های پیشنیاز در اکثر منابع مدیریت بسته سیستم های عامل موجود هستند. شما میتوانید یا راهنمای زیر را دنبال نمایید یا تنها فایل
`./simple_make.sh`
را بعد از دانلود و کپی کردن منبع اجرا نمایید. که این فایل به شکل خودکار پیشنیاز های مورد نظر را دانلود و به کامپایل کردن برنامه اقدام میکند. ### نصب کردن Git برای کپی کردن کد منبع برنامه به ابزار Git نیاز است. #### آرچ
```bash sudo pacman -S --needed git ```
#### دبیان
```bash sudo apt-get install git ```
#### فدورا
```bash sudo dnf install git ```
#### اوپن‌سوزه
```bash sudo zypper install git ```
#### اوبونتو
```bash sudo apt-get install git ```
### کپی کردن qTox در مرحله بعد از نصب Git یک ترمینال باز کرده و به پوشه دلخواه خود تغیر مسیر دهید. در این پوشه اقدام به بارگیری و کپی کردن کد منبع qTox نمایید:
```bash cd /home/$USER/qTox git clone https://github.com/qTox/qTox.git qTox ```
اقدامات بعدی چنین فرض میکنند که شما کد منبع را در مسیر
`/home/$USER/qTox`
کپی کرده اید. اگر کد را در مسیر دیگری کپی نموده اید، دستورات زیر را به شکل مناسب تغییر دهید. ### GCC, Qt, FFmpeg, OpenAL Soft و qrencode #### آرچ
```bash sudo pacman -S --needed base-devel qt5 openal libxss qrencode ffmpeg ```
#### دبیان **نکته: تنها دبیان نسخه های بالاتر از 9 پایدار (توزیع stretch) پشتیبانی میشوند**
```bash sudo apt-get install \ build-essential \ cmake \ ffmpeg \ libavcodec-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libgtk2.0-dev \ libopenal-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ libsqlcipher-dev \ libxss-dev \ pkg-config \ qrencode \ qt5-default \ qttools5-dev \ qttools5-dev-tools \ yasm ```
#### فدورا **توجه داشته باشید که sqlcipher هنوز در همه ویرایش های فدورا موجود نیست** در زمان نگارش این قسمت (نوامبر 2016)، فدورا 25 با sqlcipher ارایه میشود، اما فدورا 24 و ویرایش های قدیمی تر هنوز این کتابخانه را ندارند. **این بدان معنی است که اگر نمیتوان sqlcipher را از منابع فدورا دانلود کرد و نصب نمود، میبایست به شکل جداگانه این کتابخانه دانلود و نصب شود.**
```bash sudo dnf groupinstall "Development Tools" "C Development Tools and Libraries" # (can also use sudo dnf install @"Development Tools") sudo dnf install \ ffmpeg-devel \ gtk2-devel \ libexif-devel \ libXScrnSaver-devel \ libtool \ openal-soft-devel \ openssl-devel \ qrencode-devel \ qt-creator \ qt-devel \ qt-doc \ qt5-linguist \ qt5-qtsvg \ qt5-qtsvg-devel \ qtsingleapplication \ sqlcipher \ sqlcipher-devel ```
**در صورت نیاز برای کامپایل به قسمت [sqlcipher](#اس-کیو-ال) مراجعه شود.** #### اوپن‌سوزه
```bash sudo zypper install \ libexif-devel \ libQt5Concurrent-devel \ libQt5Network-devel \ libQt5OpenGL-devel \ libQt5Xml-devel \ libXScrnSaver-devel \ libffmpeg-devel \ libqt5-linguist \ libqt5-qtbase-common-devel \ libqt5-qtsvg-devel \ openal-soft-devel \ patterns-openSUSE-devel_basis \ qrencode-devel \ sqlcipher-devel ```
#### اسلک وار لیست تمام پیش نیاز های qTox و SlackBuilds مربوط به آنها را میتوان در آدرس زیر مشاهده نمود:
http://slackbuilds.org/repository/14.2/network/qTox/
#### اوبونتو نسخه بالاتر یا برابر 15.04
```bash sudo apt-get install \ build-essential cmake \ libavcodec-ffmpeg-dev \ libavdevice-ffmpeg-dev \ libavfilter-ffmpeg-dev \ libavutil-ffmpeg-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ libopenal-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ libsqlcipher-dev \ libswresample-ffmpeg-dev \ libswscale-ffmpeg-dev \ libxss-dev \ qrencode \ qt5-default \ qttools5-dev-tools ```
#### اوبونتو نسخه بالاتر یا برابر با 16.04
```bash sudo apt-get install \ build-essential \ cmake \ libavcodec-dev \ libavdevice-dev \ libavfilter-dev \ libavutil-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ libopenal-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ libsqlcipher-dev \ libswresample-dev \ libswscale-dev \ libxss-dev \ qrencode \ qt5-default \ qttools5-dev-tools \ qttools5-dev ```
### نیازمندی های toxcore نصب تمامی پیش نیاز های toxcore. #### آرچ
```bash sudo pacman -S --needed opus libvpx libsodium ```
#### دبیان
```bash sudo apt-get install libtool autotools-dev automake checkinstall check \ libopus-dev libvpx-dev libsodium-dev libavdevice-dev ```
#### فدورا
```bash sudo dnf install libtool autoconf automake check check-devel libsodium-devel \ opus-devel libvpx-devel ```
#### اوپن‌سوزه
```bash sudo zypper install libsodium-devel libvpx-devel libopus-devel \ patterns-openSUSE-devel_basis ```
#### اسلک وار لیست تمامی پیش نیازهای toxcore و SlackBuilds آنها را میتوان در آدرس زیر مشاهده نمود:
http://slackbuilds.org/repository/14.2/network/toxcore/
#### اوبونتو نسخه بالاتر یا برابر 15.04
```bash sudo apt-get install libtool autotools-dev automake checkinstall check \ libopus-dev libvpx-dev libsodium-dev ```
### sqlcipher اگر از یک نسخه قدیمی فدورا استفاده نمیکنید، این قسمت را رها کرده، و مستقیما به [**toxcore**](#کامپایل-تاکس-کر) مراجعه کنید.
```bash git clone https://github.com/sqlcipher/sqlcipher cd sqlcipher ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" \ LDFLAGS="-lcrypto" make sudo make install cd .. ```
### کامپایل toxcore اگر تمامی پیش نیاز های مورد نیاز را نصب داشته باشید، میتوانید به راحتی این دستورات را اجرا کنید و toxcore را کامپایل کنید:
```bash git clone https://github.com/toktok/c-toxcore.git toxcore cd toxcore git checkout v0.2.13 autoreconf -if ./configure make -j$(nproc) sudo make install echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf sudo ldconfig ```
### کامپایل qTox **مطمئن شوید که تمامی پیش نیاز ها را نصب کرده اید** اگر در حین کامپایل با مشکلی مواجه شوید بدون تردید پیش نیازی را نصب ندارید. بنابراین مطمئن شوید که *همه آنها را نصب کرده اید*. اگر دارید qTox را در فدورا 25 کامپایل میکنید، باید `PKG_CONFIG_PATH` را به متغیر environment به شکل دستی اضفاه کنید:
``` export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig" ```
سپس این دستورات را در پوشه qTox اجرا کنید تا برنامه کامپایل شود:
```bash cmake . make ```
بعد از اتمام کامپایل میتوان qTox را با استفاده از فرمان
`./qtox`
اجرا نمود. ضمن عرض تبریک، شما موفق به کامپایل qTox شده اید `:)` #### دبیان / ابونتو / مینت اگر پروسه کامپایل به خاطر عدم وجود یک پیش نیاز متوقف میشود، مانند `... libswscale/swscale.h missing` این دستور را امتحان کنید:
```bash apt-file search libswscale/swscale.h ```
سپس بسته هایی را نصب کنید که فایل های مورد نیاز را فراهم میکنند. دوباره فرمان make را اجرا کنید، در صورت نیاز دوباره این مراحل را تکرار کنید. اگر برایتان امکان پذیر است لیستی از فایلهای مورد نیاز تهیه کنید و برای ما ارسال نمایید تا بتوانیم مشخص کنیم که چه فایلهایی به شکل معمول مورد نیاز کاربران است `;)` --- ### ساخت بسته ها در یک روش دیگر، qTox به شکل آزمایشی و شاید غیر قابل اطمینان میتواند خود را بسته بندی کند. (منظور بسته بندی به فرمت `.deb` به شکل خودکار، و بسته بندی به فرمت `.rpm` با استفاده از [alien](http://joeyh.name/code/alien/) است). بعد از نصب پیش نیاز های مورد نیاز، `bootstrap.sh` را اجرا کنید، و سپس اسکریپت `buildPackages.sh` را اجرا نمایید، که میتوان آن را در پوشه tools پیدا نمود. اجرای این دستور به شکل خودکار بسته های مورد نیاز برای ساخت `.deb` را دانلود و نصب میکند، پس برای تایپ پسورد sudo آماده باشد. ## OS X OS X ویرایشهای بالاتر از نسخه 10.8 پشتیبانی میشوند. کامپایل qTox روی OS X برای توسعه به سه ابزار زیر نیاز دارد: [Xcode](https://developer.apple.com/xcode/), [Qt 5.4+](https://www.qt.io/qt5-4/) and [homebrew](https://brew.sh). ### اسکریپت خودکار حالا میتوانید به شکل خودکار سیستم OS X خود را برای کامپایل qTox با استفاده از اسکریپت
`./osx/qTox-Mac-Deployer-ULTIMATE.sh`
آماده کنید. میتوان این اسکریپت را به شکل مجزای از منبع qTox اجرا نمود، و این اسکریپت همه چیزی است که برای ساخت و کامپایل روی OS X مورد نیاز است. برای استفاده از این اسکریپت ابتدا ترمینال را از مسیر زیر اجرا کنید:
`Applications > Utilities > Terminal.app`
در صورتی که میخواهید بیشتر یاد بگیرید میتوانید این دستور را اجرا کنید:
`./qTox-Mac-Deployer-ULTIMATE.sh -h`
توجه داشته باشید که این اسکریپت همه تغیراتی که ذخیره نشده باشند را در منبع qTox به شکل اولیه بر مگرداند و این اتفاق در مرحله `update` رخ میدهد. #### اجرای اولیه / نصب اگر برای بار اول است که اسکریپت را اجرا میکنید میبایست مطمئن شوید که سیستم شما آماده است. برای این کار میتوانید به سادگی دستور
`./qTox-Mac-Deployer-ULTIMATE.sh -i`
را اجرا کنید تا شما را در مسیر آماده سازی راهنمایی کند. بعد از نصب اولیه حالا شما میتوانید qTox را از روی کد برنامه بسازید. که این کار با اجرای :
`./qTox-Mac-Deployer-ULTIMATE.sh -b`
قابل انجام است. اگر خطایی رخ ندهد، آنوقت شما یک نسخه قابل اجرا از برنامه qTox در پوشه خانه خود (home) در زیرشاخه
`~/qTox-Mac_Build`
دارید. #### به روز رسانی اگر به منظور آزمایش یا اجرای آخرین ویرایش برنامه نیاز به به روز رسانی برنامه دارید میتوانید
`./qTox-Mac-Deployer-ULTIMATE.sh -u`
را اجرا کنید و به فرامین ارایه شده عمل کنید. (توجه داشته باشید که اگر میدانید که قبل از اجرای این دستور منابع را به روز رسانی کرده اید Y را وارد کنید). سپس
`./qTox-Mac-Deployer-ULTIMATE.sh -b`
را اجرا کنید تا بار دیگر برنامه ساخته شود. (توجه داشته باشید که این کار ساخت های قبلی را پاک کرده و جایگزین میکند) #### گسترش و توزیع سیستم عامل OS X به منظور اضافه کردن قابلیت به اشتراک گذاری در فایل
`qTox.app`
با سیستم هایی که کتابخانه های لازم را ندارند به یک مرحله دیگر نیاز دارد. اگر میخواهید برنامه ای را که کامپایل کرده اید با سایر دوستانتان که OS X دارند به اشتراک بگذارید قبل از این کار، دستور
`./qTox-Mac-Deployer-ULTIMATE.sh -d`
را اجرا کنید. ### کامپایل دستی #### کتابخانه های مورد نیاز homebrew را نصب کنید اگر آن را ندارید:
```bash ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ```
در مرحله اول، تمامی پیش نیاز های موجود در `brew` را نصب کنید.
```bash brew install git ffmpeg qrencode libtool automake autoconf check qt5 libvpx \ opus sqlcipher libsodium ```
در مرحله بعد [toxcore](https://github.com/toktok/c-toxcore/blob/master/INSTALL.md#osx)را نصب کنید سپس qTox را کپی کنید:
```bash git clone https://github.com/qTox/qTox ```
از این به بعد، در مرحله آخر، همه فایلهای مورد نیاز را کپی کنید. هر بار که بسته های brew را آپدیت میکنید، میتوانید تمامی گام های بالا را انجام ندهید، و تنها دستورات زیر را اجرا کنید:
```bash cd ./git/qTox sudo bash bootstrap-osx.sh ```
#### کامپایل کردن شما میتوانید qTox را با استفاده از Qt Creator بسازید. [seperate download](http://www.qt.io/download-open-source/#section-6) یا آن را به شکل دستی با استفاده از cmake ایجاد نمایید. اگر بخواهید از cmake استفاده کنید، میتوانید qTox را در پوشه git کامپایل کنید:
```bash cmake . make ```
یا یک راه تمیز تر میتواند این باشد:
```bash cd ./git/dir/qTox mkdir ./build cd build cmake .. ```
#### گسترش و توزیع اگر qTox را به شکل صحیح کامپایل کرده باشید، حالا میتوانید `qTox.app` ایجاد شده را با دیگران در اشتراک بگذارید. با استفاده از qt5 homebrew نصب شده در پوشه build:
```bash /usr/local/Cellar/qt5/5.5.1_2/bin/macdeployqt ./qTox.app ```
#### اجرای qTox شما دو انتخاب دارید، یا روی qTox کلیک کنید که سریعا خارج میشود، یا دستور زیر را اجرا نمایید:
```bash qtox.app/Contents/MacOS/qtox ```
میتوانید از محیط CLI ای که ایجاد کرده اید لذت ببرید و احتمال زیاد دوستان و خانواده شما فکر میکنند که شما یک هکر شده اید. ## ویندوز ### کامپایل برای سایر سیستم عامل ها در لینوکس مراجعه کنید به [`windows/cross-compile`](windows/cross-compile) ### کامپایل در ویندوز #### Qt در ابتدا فایل نصب Qt را از [qt.io](https://www.qt.io/download-open-source/) دانلود کنید. در طول نصب میباید toolchian مربوط به Qt را اسمبل کنید. میتوانید آخرین نسخه Qt را با استفاده از MinGW کامپایل کنید. هرچند که خود فایل نصب یک کامپایلر MinGW ارایه میکند، اما توصیه میشود که آن را به شکل جداگانه نصب کرد، چرا که Qt فاقد MSYS است که برای کامپایل و نصب OpenAL مورد نیاز است. به همین جهت میتوانید در صورت نیاز سربرگ `Tools` را غیر فعال کنید. گام های بعدی این چنین فرض میکنند که Qt در `C:\Qt` نصب شده است. اگر Qt را در مسیر دیگری نصب میکنید، دستورات زیر را به شکل مناسب تغیر دهید. #### MinGW فایل نصب MinGW را برای ویندوز از [sourceforge.net](http://sourceforge.net/projects/mingw/files/Installer/) دانلود و نصب کنید. مطمئن شوید که MSYS را انتخاب و نصب میکنید. MSYS مجموعه ای از ابزار های Unix برای ویندوز است. گام های بعدی در نظر میگیرند که MinGW در `C:\MinGW` نصب شده است. اگر مسیر نصب متفاوت است دستورات زیر را به شکل مناسب تغییر دهید. در برنامه نصب MinGW (mingw-get.exe) بسته های `mingw-developer-toolkit`, `mingw32-base`, `mingw32-gcc-g++`, `msys-base` و `mingw32-pthreads-w32` را انتخاب کنید. دقت داشته باشید که نسخه MinGW با ورژن و نسخه Qt سازگاری داشته باشد. #### Wget فایل نصب Wget برای ویندوز را از http://gnuwin32.sourceforge.net/packages/wget.htm دانلود و نصب کنید. این فایل ها را نصب کنید. گام های بعدی چنین فرض میکنند که Wget در مسیر
`C:\Program Files (x86)\GnuWin32\`
نصب شده است. اگر تصمیم دارید این فایل را در مسیر دیگری نصب کنید، دستورات زیر را به شکل مناسب تغییر دهید. #### UnZip فایل نصب UnZip برای ویندوز را از http://gnuwin32.sourceforge.net/packages/unzip.htm دانلود و آن را نصب کنید. گام هایی که در ادامه میآیند چنین فرض میکنند که UnZip در
`C:\Program Files (x86)\GnuWin32\`
نصب شده است. در صورتی که مسیر دیگری را انتخاب کرده اید دستورات را به شکل مناسب ویرایش نمایید. #### تغییر PATH سیستم فایل های اجرایی
MinGW/MSYS/CMake
را به متغیر PATH سیستم اضافه کنید که بتوان به شکل سراسری به آنها دسترسی داشت. به مسیر زیر بروید:
`Control Panel` -> `System and Security` -> `System` -> `Advanced system settings` -> `Environment Variables...`
یا دستور `sysdm.cpl` را اجرا کنید، سر برگ `Advanced system settings` را انتخاب کنید و روی دکمه `Environment Variables` کلیک کنید. در جعبه دوم (پایینی) به دنبال متغیر `PATH` بگردید و روی دکمه `Edit...` کلیک کنید. جعبه ورودی `Variable value:` به احتمال زیاد دارای چندین پوشه از قبل میباشد. هر پوشه با استفاده از نقطه ویرگول (;) جدا شده است. این جعبه ورودی را طوری تغییر دهید که پوشه های زیر را نیز شامل شود:
`;C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\Program Files (x86)\CMake 2.8\bin;C:\Program Files (x86)\GnuWin32\bin`
. به احتمال زیاد پوشه CMake به شکل خودکار به مسیر اضافه شده است. توجه داشته باشید که مسیر هایی که دارای برنامه های `sh` و `bash` هستند، مانند مسیر
`C:\Program Files\OpenSSH\bin`
در آخر مسیر متغیر `PATH` قرار داشته باشند، در غیر اینصورت کامپایل انجام نخواهد شد. #### کپی کردن منبع کد منبع (https://github.com/qTox/qTox.git) را با استفاده از برنامه Git مورد نظر خود کپی کنید. برنامه های [SmartGit](http://www.syntevo.com/smartgit/) و [TorteiseGit](https://tortoisegit.org) هردو برای انجام این کار برنامه های خوبی هستند (توجه داشته باشید که شاید لازم باشد `git.exe` را به `PATH` اضافه کنید). گام های بعدی چنین فرض میکنند که کد ها را در مسیر `C:\qTox` کپی کرده اید. اگر تصمیم گرفته اید که مسیر دیگری را انتخاب کنید، دستورات را اصلاح نمایید. #### دریافت پیش نیاز ها اسکریپت `bootstrap.bat` که در مسیر `C:\qTox` قرار دارد را اجرا کنید. این اسکریپت به شکل خودکار پیش نیاز های لازم را دانلود خواهد کرد، آنها را کامپایل و در مسیر های لازم کپی خواهد نمود. توجه داشته باشید که برخی از ابزار های ویروسیابی برخی از کتابخانه ها را به اشتباه ویروس شناسایی میکنند. اگر با این مشکل مواجه میشوید، به منظور کسب اطلاعات بیشتر به صفحه [problematic antiviruses](https://github.com/qTox/qTox/wiki/Problematic-antiviruses) مراجعه کنید. ## سویچ های زمان کامپایل این سویچ ها به عنوان مولفه به دستور `cmake` اضافه میشوند. به عنوان مثال یک سویچ `SWITCH` که مقدار `YES` به آن اختصاص داده میشود، این مقدار به `cmake` منتقل میشود. و این اتفاق به شکل زیر انجام میشود:
```bash cmake -DSWITCH=yes ```
سویچها: - `SMILEYS`, مقادیر معنی دار: - اگر تعریف نشود یا میزان معنی داری به آن اختصاص پیدا نکند، همه شکلک ها اضافه خواهند شد - `DISABLED` – هیچ بسته شکلکی را اضافه نکن، تنها بسته های کاربر اضافه میشوند - `MIN` – تنها یک بسته شکلک ها اضافه شود [Atk]: https://wiki.gnome.org/Accessibility [Cairo]: https://www.cairographics.org/ [Check]: https://libcheck.github.io/check/ [CMake]: https://cmake.org/ [DBus Menu]: https://launchpad.net/libdbusmenu [FFmpeg]: https://www.ffmpeg.org/ [GCC]: https://gcc.gnu.org/ [GdkPixbuf]: https://developer.gnome.org/gdk-pixbuf/ [GLib]: https://wiki.gnome.org/Projects/GLib [GTK+]: https://www.gtk.org/ [libappindicator]: https://launchpad.net/libappindicator [libX11]: https://www.x.org/wiki/ [libXScrnSaver]: https://www.x.org/wiki/Releases/ModuleVersions/ [MinGW]: http://www.mingw.org/ [OpenAL Soft]: http://kcat.strangesoft.net/openal.html [Pango]: http://www.pango.org/ [pkg-config]: https://www.freedesktop.org/wiki/Software/pkg-config/ [qrencode]: https://fukuchi.org/works/qrencode/ [Qt]: https://www.qt.io/ [sqlcipher]: https://www.zetetic.net/sqlcipher/ [toxcore]: https://github.com/TokTok/c-toxcore/ [filteraudio]: https://github.com/irungentoo/filter_audio qTox/INSTALL.md000066400000000000000000000520431415623743500134570ustar00rootroot00000000000000# Install Instructions - [Dependencies](#dependencies) - [Linux](#linux) - [Simple install](#simple-install) - [Arch](#arch-easy) - [Fedora](#fedora-easy) - [Gentoo](#gentoo-easy) - [openSUSE](#opensuse-easy) - [Slackware](#slackware-easy) - [FreeBSD](#freebsd-easy) - [Install git](#install-git) - [Arch](#arch-git) - [Debian](#debian-git) - [Fedora](#fedora-git) - [openSUSE](#opensuse-git) - [Ubuntu](#ubuntu-git) - [Clone qTox](#clone-qtox) - [GCC, Qt, FFmpeg, OpenAL Soft and qrencode](#other-deps) - [Arch](#arch-other-deps) - [Debian](#debian-other-deps) - [Fedora](#fedora-other-deps) - [openSUSE](#opensuse-other-deps) - [Slackware](#slackware-other-deps) - [Ubuntu >=15.04](#ubuntu-other-deps) - [Ubuntu >=16.04](#ubuntu-other-1604-deps) - [sqlcipher](#sqlcipher) - [Compile toxcore](#compile-toxcore) - [Compile qTox](#compile-qtox) - [Security hardening with AppArmor](#security-hardening-with-apparmor) - [OS X](#osx) - [Windows](#windows) - [Cross-compile from Linux](#cross-compile-from-linux) - [Native](#native) - [Compile-time switches](#compile-time-switches) ## Dependencies | Name | Version | Modules | |---------------|-------------|----------------------------------------------------------| | [Qt] | >= 5.5.0 | concurrent, core, gui, network, opengl, svg, widget, xml | | [GCC]/[MinGW] | >= 4.8 | C++11 enabled | | [toxcore] | >= 0.2.10 | core, av | | [FFmpeg] | >= 2.6.0 | avformat, avdevice, avcodec, avutil, swscale | | [CMake] | >= 2.8.11 | | | [OpenAL Soft] | >= 1.16.0 | | | [qrencode] | >= 3.0.3 | | | [sqlcipher] | >= 3.2.0 | | | [pkg-config] | >= 0.28 | | | [snorenotify] | >= 0.7.0 | optional dependency | ## Optional dependencies They can be disabled/enabled by passing arguments to `cmake` command when building qTox. If they are missing, qTox is built without support for the functionality. ### Development dependencies Dependencies needed to run tests / code formatting, etc. Disabled if dependencies are missing. | Name | Version | |---------|---------| | [Check] | >= 0.9 | ### Spell checking support | Name | Version | |----------|---------| | [sonnet] | >= 5.45 | Use `-DSPELL_CHECK=OFF` to disable it. **Note:** Specified version was tested and works well. You can try to use older version, but in this case you may have some errors (including a complete lack of spell check). ### Linux #### Auto-away support | Name | Version | |-----------------|----------| | [libXScrnSaver] | >= 1.2 | | [libX11] | >= 1.6.0 | Disabled if dependencies are missing during compilation. #### Snorenotify desktop notification backend Disabled by default | Name | Version | |-------------------|-----------| | [snorenotify] | >= 0.7.0 | To enable: `-DDESKTOP_NOTIFICATIONS=True` ## Linux ### Simple install Easy qTox install is provided for variety of distributions: * [Arch](#arch) * [Fedora](#fedora) * [Gentoo](#gentoo) * [Slackware](#slackware) --- #### Arch PKGBUILD is available in the `community` repo, to install: ```bash pacman -S qtox ``` #### Fedora qTox is available in the [RPMFusion](https://rpmfusion.org/) repo, to install: ```bash dnf install qtox ``` #### Gentoo qTox is available in Gentoo. To install: ```bash emerge qtox ``` #### openSUSE qTox is available in openSUSE Factory. To install in openSUSE 15.0 or newer: ```bash zypper in qtox ``` To install in openSUSE 42.3: ```bash zypper ar -f https://download.opensuse.org/repositories/server:/messaging/openSUSE_Leap_42.3 server:messaging zypper in qtox ``` #### Slackware qTox SlackBuild and all of its dependencies can be found here: http://slackbuilds.org/repository/14.2/network/qTox/ #### FreeBSD qTox is available as a binary package. To install the qTox package: ```bash pkg install qTox ``` The qTox port is also available at ``net-im/qTox``. To build and install qTox from sources using the port: ```bash cd /usr/ports/net-im/qTox make install clean ``` ---- If your distribution is not listed, or you want / need to compile qTox, there are provided instructions. ---- Most of the dependencies should be available through your package manager. You may either follow the directions below, or simply run `./simple_make.sh` after cloning this repository, which will attempt to automatically download dependencies followed by compilation. ### Install git In order to clone the qTox repository you need Git. #### Arch Linux ```bash sudo pacman -S --needed git ``` #### Debian ```bash sudo apt-get install git ``` #### Fedora ```bash sudo dnf install git ``` #### openSUSE ```bash sudo zypper install git ``` #### Ubuntu ```bash sudo apt-get install git ``` ### Clone qTox Afterwards open a new terminal, change to a directory of your choice and clone the repository: ```bash cd /home/$USER/qTox git clone https://github.com/qTox/qTox.git qTox ``` The following steps assumes that you cloned the repository at `/home/$USER/qTox`. If you decided to choose another location, replace corresponding parts. ### GCC, Qt, FFmpeg, OpenAL Soft and qrencode #### Arch Linux ```bash sudo pacman -S --needed base-devel qt5 openal libxss qrencode ffmpeg opus libvpx libsodium ``` #### Debian **Note that only Debian >=9 stable (stretch) is supported.** ```bash sudo apt-get install \ automake \ autotools-dev \ build-essential \ check \ checkinstall \ cmake \ ffmpeg \ libavcodec-dev \ libavdevice-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libgtk2.0-dev \ libkdeui5 \ libopenal-dev \ libopus-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ libsodium-dev \ libsqlcipher-dev \ libtool \ libvpx-dev \ libxss-dev \ pkg-config \ qrencode \ qt5-default \ qttools5-dev \ qttools5-dev-tools \ yasm ``` #### Fedora **Note that sqlcipher is not included in all versions of Fedora yet.** As of writing this section (November 2016), Fedora 25 ships sqlcipher, but Fedora 24 and older don't ship it yet. **This means that if you can't install sqlcipher from repositories, you'll have to compile it yourself, otherwise compiling qTox will fail.** ```bash sudo dnf groupinstall "Development Tools" "C Development Tools and Libraries" # (can also use sudo dnf install @"Development Tools") sudo dnf install \ autoconf \ automake \ check \ check-devel \ ffmpeg-devel \ gtk2-devel \ kf5-sonnet \ libexif-devel \ libsodium-devel \ libtool \ libvpx-devel \ libXScrnSaver-devel \ openal-soft-devel \ openssl-devel \ opus-devel \ qrencode-devel \ qt5-linguist \ qt5-qtsvg \ qt5-qtsvg-devel \ qt-creator \ qt-devel \ qt-doc \ qtsingleapplication \ sqlcipher \ sqlcipher-devel ``` **Go to [sqlcipher](#sqlcipher) section to compile it if necessary.** #### openSUSE ```bash sudo zypper install \ libexif-devel \ libffmpeg-devel \ libopus-devel \ libQt5Concurrent-devel \ libqt5-linguist \ libQt5Network-devel \ libQt5OpenGL-devel \ libqt5-qtbase-common-devel \ libqt5-qtsvg-devel \ libQt5Xml-devel \ libsodium-devel \ libvpx-devel \ libXScrnSaver-devel \ openal-soft-devel \ patterns-openSUSE-devel_basis \ qrencode-devel \ sqlcipher-devel \ sonnet-devel ``` #### Slackware List of all the toxcore dependencies and their SlackBuilds can be found here: http://slackbuilds.org/repository/14.2/network/toxcore/ List of all the qTox dependencies and their SlackBuilds can be found here: http://slackbuilds.org/repository/14.2/network/qTox/ #### Ubuntu >=15.04 ```bash sudo apt-get install \ automake \ autotools-dev \ build-essential cmake \ check \ checkinstall \ libavcodec-ffmpeg-dev \ libavdevice-ffmpeg-dev \ libavfilter-ffmpeg-dev \ libavutil-ffmpeg-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ libkdeui5 \ libopenal-dev \ libopus-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ libsodium-dev \ libsqlcipher-dev \ libswresample-ffmpeg-dev \ libswscale-ffmpeg-dev \ libtool \ libvpx-dev \ libxss-dev \ qrencode \ qt5-default \ qttools5-dev-tools ``` #### Ubuntu >=16.04: ```bash sudo apt-get install \ build-essential \ cmake \ libavcodec-dev \ libavdevice-dev \ libavfilter-dev \ libavutil-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ libkdeui5 \ libopenal-dev \ libopus-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ libsodium-dev \ libsqlcipher-dev \ libswresample-dev \ libswscale-dev \ libvpx-dev \ libxss-dev \ qrencode \ qt5-default \ qttools5-dev-tools \ qttools5-dev ``` ### sqlcipher If you are not using an old version of Fedora, skip this section, and go directly to compiling [**toxcore**](#compile-toxcore). ```bash git clone https://github.com/sqlcipher/sqlcipher cd sqlcipher ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" \ LDFLAGS="-lcrypto" make sudo make install cd .. ``` ### Compile toxcore Normally you don't want to do that, `bootstrap.sh` will do it for you. Provided that you have all required dependencies installed, you can simply run: ```bash git clone https://github.com/toktok/c-toxcore.git toxcore cd toxcore git checkout v0.2.13 cmake . -DBOOTSTRAP_DAEMON=OFF make -j$(nproc) sudo make install # we don't know what whether user runs 64 or 32 bits, and on some distros # (Fedora, openSUSE) lib/ doesn't link to lib64/, so add both echo '/usr/local/lib64/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf sudo ldconfig ``` ### Compile qTox **Make sure that all the dependencies are installed.** If you experience problems with compiling, it's most likely due to missing dependencies, so please make sure that you did install *all of them*. If you are compiling on Fedora 25, you must add libtoxcore to the `PKG_CONFIG_PATH` environment variable manually: ``` # we don't know what whether user runs 64 or 32 bits, and on some distros # (Fedora, openSUSE) lib/ doesn't link to lib64/, so add both export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/lib64/pkgconfig" export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig" ``` Run in qTox directory to compile: ```bash cmake . make -j$(nproc) ``` Now you can start compiled qTox with `./qtox` Congratulations, you've compiled qTox `:)` #### Debian / Ubuntu / Mint If the compiling process stops with a missing dependency like: `... libswscale/swscale.h missing` try: ```bash apt-file search libswscale/swscale.h ``` And install the package that provides the missing file. Start make again. Repeat if necessary until all dependencies are installed. If you can, please note down all additional dependencies you had to install that aren't listed here, and let us know what is missing `;)` --- ### Building packages Alternately, qTox now has the experimental and probably-dodgy ability to package itself (in `.deb` form natively, and `.rpm` form with [alien](http://joeyh.name/code/alien/)). After installing the required dependencies, run `bootstrap.sh` and then run the `buildPackages.sh` script, found in the tools folder. It will automatically get the packages necessary for building `.deb`s, so be prepared to type your password for sudo. --- ### Security hardening with AppArmor See [AppArmor] to enable confinement for increased security. ## OS X Supported OS X versions: >=10.8. (NOTE: only 10.13 is tested during CI) Compiling qTox on OS X for development requires 2 tools: [Xcode](https://developer.apple.com/xcode/) and [homebrew](https://brew.sh). ### Automated Script You can now set up your OS X system to compile qTox automatically thanks to the script in: `./osx/qTox-Mac-Deployer-ULTIMATE.sh` This script can be run independently of the qTox repo and is all that's needed to build from scratch on OS X. To use this script you must launch terminal which can be found: `Applications > Utilities > Terminal.app` If you wish to lean more you can run `./qTox-Mac-Deployer-ULTIMATE.sh -h` Note that the script will revert any non-committed changes to qTox repository during the `update` phase. #### First Run / Install If you are running the script for the first time you will want to make sure your system is ready. To do this simply run `./qTox-Mac-Deployer-ULTIMATE.sh -i` to run you through the automated install set up. After running the installation setup you are now ready to build qTox from source, to do this simply run: `./qTox-Mac-Deployer-ULTIMATE.sh -b` If there aren't any errors then you'll find a locally working qTox application in your home folder under `~/qTox-Mac_Build` #### Updating If you want to update your application for testing purposes or you want to run a nightly build setup then run: `./qTox-Mac-Deployer-ULTIMATE.sh -u` and follow the prompts. (NOTE: If you know you updated the repos before running this hit Y) followed by `./qTox-Mac-Deployer-ULTIMATE.sh -b` to build the application once more. (NOTE: This will delete your previous build.) #### Deploying OS X requires an extra step to make the `qTox.app` file shareable on a system that doesn't have the required libraries installed already. If you want to share the build you've made with your other friends who use OS X then simply run: `./qTox-Mac-Deployer-ULTIMATE.sh -d` ### Manual Compiling #### Required Libraries Install homebrew if you don't have it: ```bash ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ``` First, let's install the dependencies available via `brew`. ```bash brew install git ffmpeg qrencode libtool automake autoconf check qt5 libvpx \ opus sqlcipher libsodium ``` Next, install [toxcore](https://github.com/toktok/c-toxcore/blob/master/INSTALL.md#osx) Then, clone qTox: ```bash git clone https://github.com/qTox/qTox ``` Finally, copy all required files. Whenever you update your brew packages, you may skip all of the above steps and simply run the following commands: ```bash cd ./git/qTox sudo bash bootstrap-osx.sh ``` #### Compiling You can build qTox with Qt Creator [seperate download](http://www.qt.io/download-open-source/#section-6) or manually with cmake With that; in your terminal you can compile qTox in the git dir: ```bash cmake . make ``` Or a cleaner method would be to: ```bash cd ./git/dir/qTox mkdir ./build cd build cmake .. ``` #### Deploying If you compiled qTox properly you can now deploy the `qTox.app` that's created where you built qTox so you can distribute the package. Using your qt5 homebrew installation from the build directory: ```bash /usr/local/Cellar/qt5/5.5.1_2/bin/macdeployqt ./qTox.app ``` #### Running qTox You've got 2 choices, either click on the qTox app that suddenly exists, or do the following: ```bash qtox.app/Contents/MacOS/qtox ``` Enjoy the snazzy CLI output as your friends and family congratulate you on becoming a hacker ## Windows ### Cross-compile from Linux See [`windows/cross-compile`](windows/cross-compile). ### Native #### Qt Download the Qt online installer for Windows from [qt.io](https://www.qt.io/download-open-source/). While installation you have to assemble your Qt toolchain. Take the most recent version of Qt compiled with MinGW. Although the installer provides its own bundled MinGW compiler toolchain its recommend installing it separately because Qt is missing MSYS which is needed to compile and install OpenAL. Thus you can - if needed - deselect the tab `Tools`. The following steps assume that Qt is installed at `C:\Qt`. If you decided to choose another location, replace corresponding parts. #### MinGW Download the MinGW installer for Windows from [sourceforge.net](http://sourceforge.net/projects/mingw/files/Installer/). Make sure to install MSYS (a set of Unix tools for Windows). The following steps assume that MinGW is installed at `C:\MinGW`. If you decided to choose another location, replace corresponding parts. Select `mingw-developer-toolkit`, `mingw32-base`, `mingw32-gcc-g++`, `msys-base` and `mingw32-pthreads-w32` packages using MinGW Installation Manager (`mingw-get.exe`). Check that the version of MinGW, corresponds to the version of the QT component! #### Wget Download the Wget installer for Windows from http://gnuwin32.sourceforge.net/packages/wget.htm. Install them. The following steps assume that Wget is installed at `C:\Program Files (x86)\GnuWin32\`. If you decided to choose another location, replace corresponding parts. #### UnZip Download the UnZip installer for Windows from http://gnuwin32.sourceforge.net/packages/unzip.htm. Install it. The following steps assume that UnZip is installed at `C:\Program Files (x86)\GnuWin32\`. If you decided to choose another location, replace corresponding parts. #### Setting up Path Add MinGW/MSYS/CMake binaries to the system path to make them globally accessible. Open `Control Panel` -> `System and Security` -> `System` -> `Advanced system settings` -> `Environment Variables...` (or run `sysdm.cpl` select tab `Advanced system settings` -> button `Environment Variables`). In the second box search for the `PATH` variable and press `Edit...`. The input box `Variable value:` should already contain some directories. Each directory is separated with a semicolon. Extend the input box by adding `;C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\Program Files (x86)\CMake 2.8\bin;C:\Program Files (x86)\GnuWin32\bin`. The very first semicolon must only be added if it is missing. CMake may be added by installer automatically. Make sure that paths containing alternative `sh`, `bash` implementations such as `C:\Program Files\OpenSSH\bin` are at the end of `PATH` or build may fail. #### Cloning the Repository Clone the repository (https://github.com/qTox/qTox.git) with your preferred Git client. [SmartGit](http://www.syntevo.com/smartgit/) or [TorteiseGit](https://tortoisegit.org) are both very nice for this task (you may need to add `git.exe` to your `PATH` system variable). The following steps assume that you cloned the repository at `C:\qTox`. If you decided to choose another location, replace corresponding parts. #### Getting dependencies Run `bootstrap.bat` in the previously cloned `C:\qTox` repository. The script will download the other necessary dependencies, compile them and put them into their appropriate directories. Note that there have been detections of false positives by some anti virus software in the past within some of the libraries used. Please refer to the wiki page [problematic antiviruses](https://github.com/qTox/qTox/wiki/Problematic-antiviruses) for more information if you run into troubles on that front. ## Compile-time switches They are passed as an argument to `cmake` command. E.g. with a switch `SWITCH` that has value `YES` it would be passed to `cmake` in a following manner: ```bash cmake -DSWITCH=yes ``` Switches: - `SMILEYS`, values: - if not defined or an unsupported value is passed, all emoticon packs are included - `DISABLED` – don't include any emoticon packs, custom ones are still loaded - `MIN` – minimal support for emoticons, only a single emoticon pack is included [AppArmor]: /security/apparmor/README.md [Atk]: https://wiki.gnome.org/Accessibility [Cairo]: https://www.cairographics.org/ [Check]: https://libcheck.github.io/check/ [CMake]: https://cmake.org/ [DBus Menu]: https://launchpad.net/libdbusmenu [FFmpeg]: https://www.ffmpeg.org/ [GCC]: https://gcc.gnu.org/ [libX11]: https://www.x.org/wiki/ [libXScrnSaver]: https://www.x.org/wiki/Releases/ModuleVersions/ [MinGW]: http://www.mingw.org/ [OpenAL Soft]: http://kcat.strangesoft.net/openal.html [Pango]: http://www.pango.org/ [pkg-config]: https://www.freedesktop.org/wiki/Software/pkg-config/ [qrencode]: https://fukuchi.org/works/qrencode/ [Qt]: https://www.qt.io/ [sqlcipher]: https://www.zetetic.net/sqlcipher/ [toxcore]: https://github.com/TokTok/c-toxcore/ [sonnet]: https://github.com/KDE/sonnet [snorenotify]: https://techbase.kde.org/Projects/Snorenotify qTox/LICENSE000066400000000000000000001045131415623743500130340ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . qTox/MAINTAINING.md000066400000000000000000000226521415623743500141120ustar00rootroot00000000000000**Guidelines, overview of maintenance process, etc.** *“Thou shall GPG-sign.”* # Git config ## GPG signing While contributors are suggested to use GPG, as a maintainer you **are required** to use GPG to sign commits & merges. If you don't have GPG signing set up yet, now is the moment to do it. [Config, etc.](/CONTRIBUTING.md#git-config) ## SSH Preferably use SSH. There are quite a few articles about that: https://help.github.com/categories/ssh/ ## Useful aliases Check whether commits are GPG-signed with `git logs` ``` git config --global alias.logs 'log --show-signature' ``` # Commits - **always** use [commit message format] - **always** GPG-sign your commits. - it's preferable to make a PR with changes that you're about to commit. Yes, there might be a situation where something has to be fixed "right now" on master.. Perhaps a security fix, who knows what future holds. If it's not *that* important, you're still better off making a PR. Even when you'll just fast-forward commits from PR onto the `master` branch. Reasoning for it is that it's always hard to catch bugs/mistakes that you create, while someone else who just briefly looked at the changeset possibly can see a problem `:)` # Pull requests - **do not** push any `Merge`, `Squash & Merge`, etc. buttons on the website! The only allowed way of merging is locally, since otherwise merge will not be signed, and websites can fairly well mess things up. - **always** test PR that is being merged. - **always** GPG-sign PR that you're merging. Commits that are about to be merged don't have to be signed, but the merge-commit **must** be signed. To simplify the process, and ensure that things are done "right", it's preferable to use the [`merge-pr.sh`] script, which does that for you automatically. - **use** [`merge-pr.sh`] script to merge PRs. First checkout the target branch, usually either `master` or a release dev branch e.g. `v1.17-dev`, make sure it's up to date with qTox/qTox, then e.g. `./merge-pr.sh 1234`. You don't have to use it, but then you're running into risk of breaking travis build of master & other PRs, since it verifies all commit messages, indlucing merge messages. Risk, that can be avoided when one doesn't type manually merge message :wink: - **might want** to use [`test-pr.sh`]. - give a PR some "breathing space" right after it's created – i.e. merging something right away can lead to bugs & regressions suddenly popping up, thus it's preferable to wait at least a day or so, to let people test & comment on the PR before merging. - with trivial changes, like fixing typos or something along those lines, feel free to merge right away. - if PR requires some changes, comment what parts need to be adjusted, and assign the `PR-needs-changes` label – after requested changes are done, remove the label. - if PR doesn't apply properly on top of current master (when using [`merge-pr.sh`] script), request a rebase and tag PR with `PR-needs-rebase`. - if a PR requires changes but there has been no activity from the PR submitter for more than 2 months, close the PR. # Continous Integration qTox nightly builds can be found in [qTox-nightly-release]. Should one build fail, it is important to restart the whole Travis CI build and not just a single job. The tool managing the nightly builds deletes all build artifacts on any job failure, so all need to be rebuilt. # Issues - tag issues - `up for grabs` tag should be used whenever no one is currently working on the issue, and you're not going to work on it in foreseeable future (hours, day or two). - when you request more info to be provided in the issue, tag it with `I-need-info`. Remove tag once needed info has been provided. - sometimes there are issue with only one comment, and no reply to query for more info. Those issues usually can be closed after some period, preferably after a month or more with no reply. To search for them, you can specify time period when issue with a given tag was last updated, e.g.: `label:I-need-info updated:<2016-03-01`. - if you're going to fix the issue, assign yourself to it. - when closing an issue, preferably state the reason why it was closed, unless it was closed automatically by commit message. - when issue is a duplicate, tag it with `duplicate`, and issue that it was a duplicate of, tag with higher `duplicates:#` # Translations from Weblate Weblate provides an easy way for people to translate qTox. On one hand, it does require a bit more attention & regular checking whether there are new translations, on the other, it lessened problems that were happening with "manual" way of providing translations. To get translations into qTox: 1. Go to `https://hosted.weblate.org/projects/tox/qtox/#repository` and lock the repository for translations. 2. Make sure you have git setup to automatically gpg sign commits. 3. To update translated strings from Weblate, in the root of the qTox repository execute the script `tools/update-weblate.sh` 4. If a new translation language has been added, update the following files: - `CMakeLists.txt` - `src/widget/form/settings/generalform.cpp` - `translations/README.md` - `translations/i18n.pri` - `translations/translations.qrc` 5. To update translatable strings from qTox for Weblate, run `./tools/update-translation-files.sh ALL` 6. Checkout a new branch with e.g. `git checkout -b update_weblate` and open a Pull Request for it on Github. 7. After the Pull Request has been merged, `reset` Weblate to master and unlock it. # Releases ## Tagging scheme - tag versions that are to be released, make sure that they are GPG-signed, i.e. `git tag -s v1.8.0` - use semantic versions for tags: `vMAJOR.MINOR.PATCH` - `MAJOR` – bump version when there are breaking changes to video, audio, text chats, groupchats, file transfers, and any other basic functionality. For other things, `MINOR` and `PATCH` are to be bumped. - `MINOR` – bump version when there are: - new features added - UI/feature breaking changes - other non-breaking changes - `PATCH` – bump when there have been only fixes added. If changes include something more than just bugfixes, bump `MAJOR` or `MINOR` version accordingly. - bumping a higher-level version "resets" lower-version numbers, e.g. `v1.7.1 → v2.0.0` ## Steps for release ### Before tagging - Format all code using the [`./tools/format-code.sh`] script - Update version number for windows/osx packages using the [`./tools/update-versions.sh`] script, e.g. `./tools/update-versions.sh 1.11.0` - Update toxcore version number to the latest tag. Currently this needs to be done manually by `grep`ing for the current tag. - Update the bootstrap nodelist at `./res/nodes.json` from https://nodes.tox.chat/json. This can be done by running [`./tools/update-nodes.sh`] - Generate changelog with `clog`. - In a `MAJOR`/`MINOR` release tag should include information that changelog is located in the `CHANGELOG.md` file, e.g. `For details see CHANGELOG.md` - To release a `PATCH` version after non-fix changes have landed on `master` branch, checkout latest `MAJOR`/`MINOR` version and `git cherry-pick -x` commits from `master` that you want `PATCH` release to include. Once cherry-picking has been done, tag HEAD of the branch. - When making a `PATCH` tag, include in tag message short summary of what the tag release fixes, and to whom it's interesting (often only some OSes/distributions would find given `PATCH` release interesting). ### After tagging - Create and GPG-sign the tar.lz and tar.gz archives using [`./tools/create-tarballs.sh`] script, and upload both archives plus both signature files to the github release that was created by a Travis OSX release job. - Update download links on https://tox.chat to point to the new release. - Write a short blog post for https://github.com/qTox/blog/ and advertise the post on Tox IRC channels, popular Tox groups, reddit, or whatever other platforms. - Open a PR to update the Flatpak manifest of our [Flathub repository] with the changes from [`./flatpak/io.github.qtox.qTox.json`]. - Comment to the PR with `bot, build` to execute a test build - After the build passed for qTox on all architectures on [the Flathub build bot], merge the PR into the master branch of our [Flathub repository]. # How to become a maintainer? Contribute, review & test pull requests, be active, oh and don't forget to mention that you would want to become a maintainer :P Aside from contents of [`CONTRIBUTING.md`] you should also know the contents of this file. Once you're confident about your knowledge and you've been around the project helping for a while, ask to be added to the `qTox` organization on GitHub. [commit message format]: /CONTRIBUTING.md#commit-message-format [`CONTRIBUTING.md`]: /CONTRIBUTING.md [`merge-pr.sh`]: /merge-pr.sh [`test-pr.sh`]: /test-pr.sh [`./tools/deweblate-translation-file.sh`]: /tools/deweblate-translation-file.sh [`./tools/create-tarball.sh`]: /tools/create-tarball.sh [`./tools/update-nodes.sh`]: /tools/update-nodes.sh [`./tools/update-versions.sh`]: /tools/update-versions.sh [`./tools/format-code.sh`]: /tools/format-code.sh [Flathub repository]: https://github.com/flathub/io.github.qtox.qTox [`./flatpak/io.github.qtox.qTox.json`]: flatpak/io.github.qtox.qTox.json [the Flathub build bot]: https://flathub.org/builds/#/ [qTox-nightly-release]: https://github.com/qTox/qTox-nightly-releases qTox/OSX-Migrater.sh000077500000000000000000000025241415623743500146060ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # A qTox profile migrater for OSX now=$(date +"%m_%d_%Y-%H.%M.%S") bak="~/.Tox-Backup-$now" echo "Figuring out if action is required ..." if [ -d ~/Library/Preferences/tox ]; then echo "Moving profile(s) ..." cp -r ~/Library/Preferences/tox ~/Library/Application\ Support/ mv ~/Library/Application\ Support/tox/ ~/Library/Application\ Support/Tox mv ~/Library/Preferences/tox ~/.Tox-Backup-$now echo "Done! You profile(s) have been moved! A back up coppy still exists at:" echo "$bak" else echo "Cannot locate old profile directory, profile migration not performed" fi exit 0qTox/README.md000066400000000000000000000156711415623743500133140ustar00rootroot00000000000000

qTox

---

GPLv3+ Travis CI Translate on Weblate Gitter

---

qTox is a chat, voice, video, and file transfer instant messaging client using the encrypted peer-to-peer Tox protocol.

**[User Manual] |** **[Install/Build] |** **[Roadmap] |** **[Report bugs] |** **[Jenkins builds] |** **[Mailing list] |** **IRC:** [#qtox@freenode] --- Windows | Linux | OS X | FreeBSD --------|-------|------|-------- **[64 bit release]**| **[Arch]**, **[Debian]**, **[Fedora]**, **[Gentoo]**, **[openSUSE]**, **[Ubuntu]** | **[Latest release]** | **[Package & Port]** [32 bit release]|**[AppImage]**, [Flatpak] | [Building instructions] | [64 bit][64nightly], [32 bit][32nightly] nightly | [From Source] | | _**Bold** options are recommended._ Builds other than installer/packages don't receive updates automatically, so make sure you get back to this site and regularly download the latest version of qTox. ### Help us If you're wondering how could you help, fear not, there are plenty of ways :smile: Some of them are: * Spread the good word about qTox to make it more popular :smile: * Have an opinion? Make sure to [voice it in the issues that need it] :wink: * Fixing [easy issues] or [issues that need help]. Make sure to read [Contributing] first though :wink: * [Testing] and [reporting bugs] * [Translating, it's easy] * [Reviewing and testing pull requests] – you don't need to be able to code to do that :wink: * Take a task from our Roadmap below ### Roadmap Currently qTox is under a feature freeze to clean up our codebase and tools. During this time we want to prepare qTox for upcoming new features of toxcore. The next steps are: * move all toxcore abstractions into their own subproject * write basic tests for this Core * format the code base * rework our TravisCI setup for faster PR checks * rethink our Issue tracker The current state is tracked in the [Code cleanup] project. ### Screenshots Note: The screenshots may not always be up to date, but they should give a good idea of the general look and features. ![Screenshot 01](https://i.imgur.com/olb89CN.png) ![Screenshot 02](https://i.imgur.com/tmX8z9s.png) ### Features - One to one chat with friends - Group chats - File transfers, with previewing of images - Audio calls, including group calls - Video calls - ToxMe and Tox URI support - Translations in over 30 languages - Avatars - Faux offline messages - History - Screenshots - Emoticons - Auto-updates on Windows and packages on Linux - And many more options! ### Organizational stuff Happens in both IRC channel [#qtox@freenode] and on [qTox-dev mailing list]. If you are interested in participating, **join the channel** and **subscribe to the mailing list**. There are [IRC logs] available. ### GPG fingerprints List of GPG fingerprints used by qTox developers to sign commits, merges, tags, and possibly other stuff. Active qTox maintainers: ``` 7EB3 39FE 8817 47E7 01B7 D472 EBE3 6E66 A842 9B99 - Anthony Bilinski 3103 9166 FA90 2CA5 0D05 D608 5AF9 F2E2 9107 C727 – Diadlo CA92 21C5 389B 7C50 AA5F 7793 52A5 0775 BE13 DF17 - noavarice DA26 2CC9 3C0E 1E52 5AD2 1C85 9677 5D45 4B8E BF44 – sudden6 141C 880E 8BA2 5B19 8D0F 850F 7C13 2143 C1A3 A7D4 – tox-user 2880 C860 D95C 909D 3DA4 5C68 7E08 6DD6 6126 3264 – tux3 ``` Past qTox maintainers: ``` C7A2 552D 0B25 0F98 3827 742C 1332 03A3 AC39 9151 – initramfs BA78 83E2 2F9D 3594 5BA3 3760 5313 7C30 33F0 9008 – zetok F365 8D0A 04A5 76A4 1072 FC0D 296F 0B76 4741 106C – agilob 1157 616B BD86 0C53 9926 F813 9591 A163 FF9B E04C – antis81 1D29 8BC7 25B7 BE82 65BA EAB9 3DB8 E053 15C2 20AA – Dubslow ``` Windows updates, managed by `tux3`: ``` AED3 1134 9C23 A123 E5C4 AA4B 139C A045 3DA2 D773 ``` [#qtox@freenode]: https://webchat.freenode.net/?channels=qtox [64 bit release]: https://github.com/qTox/qTox/releases/download/v1.17.4/setup-qtox-x86_64-release.exe [32 bit release]: https://github.com/qTox/qTox/releases/download/v1.17.4/setup-qtox-i686-release.exe [32nightly]: https://build.tox.chat/view/qtox/job/qTox-cmake-nightly_build_windows_x86_release/lastSuccessfulBuild/artifact/qTox-cmake-nightly_build_windows_x86_release.zip [64nightly]: https://build.tox.chat/view/qtox/job/qTox-cmake-nightly_build_windows_x86-64_release/lastSuccessfulBuild/artifact/qTox-cmake-nightly_build_windows_x86-64_release.zip [Flatpak]: https://github.com/qTox/qTox/releases/download/v1.17.4/qTox-v1.17.4.x86_64.flatpak [AppImage]: https://github.com/qTox/qTox/releases/download/v1.17.4/qTox-v1.17.4.x86_64.AppImage [Arch]: /INSTALL.md#arch [Building instructions]: /INSTALL.md#os-x [Contributing]: /CONTRIBUTING.md#how-to-start-contributing [Debian]: https://packages.debian.org/search?keywords=qtox [easy issues]: https://github.com/qTox/qTox/labels/E-easy [Latest release]: https://github.com/qTox/qTox/releases/download/v1.17.4/qTox.dmg [Fedora]: /INSTALL.md#fedora [Gentoo]: /INSTALL.md#gentoo [openSUSE]: /INSTALL.md#opensuse [Install/Build]: /INSTALL.md [IRC logs]: https://github.com/qTox/qtox-irc-logs [issues that need help]: https://github.com/qTox/qTox/labels/help%20wanted [Jenkins builds]: https://build.tox.chat/ [Mailing list]: https://lists.tox.chat [From Source]: /INSTALL.md#linux [qTox-dev mailing list]: https://lists.tox.chat/listinfo/qtox-dev [Package & Port]: /INSTALL.md#freebsd-easy [Report bugs]: https://github.com/qTox/qTox/wiki/Writing-Useful-Bug-Reports [reporting bugs]: https://github.com/qTox/qTox/wiki/Writing-Useful-Bug-Reports [Reviewing and testing pull requests]: /CONTRIBUTING.md#reviews [Roadmap]: https://github.com/qTox/qTox/milestones [sig-32]: https://qtox-win.pkg.tox.chat/qtox/win32/download-sig [sig-64]: https://qtox-win.pkg.tox.chat/qtox/win64/download-sig [Testing]: https://github.com/qTox/qTox/wiki/Testing [Translating, it's easy]: /translations/README.md [User Manual]: /doc/user_manual_en.md [Ubuntu]: https://packages.ubuntu.com/search?keywords=qtox [voice it in the issues that need it]: https://github.com/qTox/qTox/labels/I-feedback-wanted [Code cleanup]: https://github.com/qTox/qTox/projects/3?fullscreen=true qTox/appimage/000077500000000000000000000000001415623743500136065ustar00rootroot00000000000000qTox/appimage/build-appimage.sh000077500000000000000000000066421415623743500170350ustar00rootroot00000000000000#!/usr/bin/env bash # MIT License # # Copyright © 2019 by The qTox Project Contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # usage: ./appimage/build-appimage.sh [Debug] # # If [Debug] is set to "Debug" the container will run in interactive mode and # stay open to poke around in the filesystem. readonly DEBUG="$1" # Set this to True to upload the PR version of the # AppImage to transfer.sh for testing. readonly UPLOAD_PR_APPIMAGE="False" # Fail out on error set -exo pipefail # This script should be run from the root of the repository if [ ! -f ./appimage/build-appimage.sh ]; then echo "" echo "You are attempting to run the build-appimage.sh from a wrong directory." echo "If you wish to run this script, you'll have to have" echo "the repository root directory as the working directory." echo "" exit 1 fi mkdir -p ./output if [ "$DEBUG" == "Debug" ] then echo "Execute: /qtox/appimage/build.sh to start the build script" echo "Execute: exit to leave the container" docker run --rm -it \ -v $PWD:/qtox \ -v $PWD/output:/output \ debian:stretch-slim \ /bin/bash else docker run --rm \ -e CIRP_GITHUB_REPO_SLUG \ -e TRAVIS_EVENT_TYPE \ -e TRAVIS_COMMIT \ -e TRAVIS_TAG \ -v $PWD:/qtox \ -v $PWD/output:/output \ debian:stretch-slim \ /bin/bash -c "/qtox/appimage/build.sh" fi # use the version number in the name when building a tag on Travis CI if [ -n "$TRAVIS_TAG" ] then # the aitool should have written the appimage in the same name # as below so no need to move things , it should have also written # the .zsync meta file as the given name below with .zsync # extension. readonly OUTFILE=./output/qTox-"$TRAVIS_TAG".x86_64.AppImage # just check if the files are in the right place eval "ls $OUTFILE" eval "ls $OUTFILE.zsync" sha256sum "$OUTFILE" > "$OUTFILE".sha256 else if [ "$UPLOAD_PR_APPIMAGE" == "True" ] then # upload PR builds to test them. echo "Uploading AppImage to transfer.sh" curl --upload-file "./output/qTox-$TRAVIS_COMMIT-x86_64.AppImage" \ "https://transfer.sh/qTox-$TRAVIS_COMMIT-x86_64.AppImage" > ./upload echo "$(cat ./upload)" echo -n "$(cat ./upload)\\n" >> ./uploaded-to rm -rf ./upload ./uploaded-to sha256sum "./output/qTox-$TRAVIS_COMMIT-x86_64.AppImage" fi fi qTox/appimage/build.sh000077500000000000000000000136151415623743500152520ustar00rootroot00000000000000#!/usr/bin/env bash # MIT License # # Copyright © 2019 by The qTox Project Contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # Fail out on error set -exuo pipefail # directory paths readonly QTOX_SRC_DIR="/qtox" readonly OUTPUT_DIR="/output" readonly BUILD_DIR="/build" readonly QTOX_BUILD_DIR="$BUILD_DIR"/qtox readonly QTOX_APP_DIR="$BUILD_DIR"/appdir # "linuxdeployqt" is to long, shortened to ldqt readonly LDQT_BUILD_DIR="$BUILD_DIR"/ldqt # "appimagetool" becomes aitool readonly AITOOL_BUILD_DIR="$BUILD_DIR"/aitool # ldqt binary readonly LDQT_BIN="/usr/lib/x86_64-linux-gnu/qt5/bin/linuxdeployqt" # aitool binary readonly AITOOL_BIN="/usr/local/bin/appimagetool" readonly APPRUN_BIN="/usr/local/bin/AppRun" readonly APT_FLAGS="-y --no-install-recommends" # snorenotify source readonly SNORE_GIT="https://github.com/KDE/snorenotify" # snorenotify build directory readonly SNORE_BUILD_DIR="$BUILD_DIR"/snorenotify # update information to be embeded in AppImage if [ "cron" == "${TRAVIS_EVENT_TYPE:-}" ] then # update information for nightly version readonly NIGHTLY_REPO_SLUG=$(echo "$CIRP_GITHUB_REPO_SLUG" | tr "/" "|") readonly UPDATE_INFO="gh-releases-zsync|$NIGHTLY_REPO_SLUG|ci-master-latest|qTox-*-x86_64.AppImage.zsync" else # update information for stable version readonly UPDATE_INFO="gh-releases-zsync|qTox|qTox|latest|qTox-*.x86_64.AppImage.zsync" fi # use multiple cores when building export MAKEFLAGS="-j$(nproc)" # Get packages apt-get update apt-get install $APT_FLAGS sudo ca-certificates wget build-essential fuse xxd \ git patchelf tclsh libssl-dev cmake extra-cmake-modules build-essential \ check checkinstall libavdevice-dev libexif-dev libgdk-pixbuf2.0-dev \ libgtk2.0-dev libopenal-dev libopus-dev libqrencode-dev libqt5opengl5-dev \ libqt5svg5-dev libsodium-dev libtool libvpx-dev libxss-dev \ qt5-default qttools5-dev qttools5-dev-tools qtdeclarative5-dev \ fcitx-frontend-qt5 uim-qt5 libsqlcipher-dev # get version cd "$QTOX_SRC_DIR" # linuxdeployqt uses this for naming the file export VERSION=$(git rev-parse --short HEAD) # create build directory mkdir -p "$BUILD_DIR" # install snorenotify because it's not packaged cd "$BUILD_DIR" git clone "$SNORE_GIT" "$SNORE_BUILD_DIR" cd "$SNORE_BUILD_DIR" git checkout tags/v0.7.0 # HACK: Kids, don't do this at your home system cmake -DCMAKE_INSTALL_PREFIX=/usr/ make make install cd "$BUILD_DIR" # copy qtox source cp -r "$QTOX_SRC_DIR" "$QTOX_BUILD_DIR" cd "$QTOX_BUILD_DIR" ./bootstrap.sh # ensure this directory is empty rm -rf ./_build mkdir -p ./_build # build dir of simple_make cd _build # need to build with -DDESKTOP_NOTIFICATIONS=True for snorenotify cmake -DDESKTOP_NOTIFICATIONS=True \ -DUPDATE_CHECK=True \ -DSTRICT_OPTIONS=True \ ../ make make DESTDIR="$QTOX_APP_DIR" install ; find "$QTOX_APP_DIR" # is release #6 as of 2019-10-23 LDQT_HASH="37631e5640d8f7c31182fa72b31266bbdf6939fc" # build linuxdeployqt git clone https://github.com/probonopd/linuxdeployqt.git "$LDQT_BUILD_DIR" cd "$LDQT_BUILD_DIR" git checkout "$LDQT_HASH" qmake make make install # is release #12 as of 2019-10-23 AITOOL_HASH="effcebc1d81c5e174a48b870cb420f490fb5fb4d" # build appimagetool git clone -b master --single-branch --recursive \ https://github.com/AppImage/AppImageKit "$AITOOL_BUILD_DIR" cd "$AITOOL_BUILD_DIR" git checkout "$AITOOL_HASH" bash -ex install-build-deps.sh # Fetch git submodules git submodule update --init --recursive # Build AppImage mkdir build cd build # make sure that deps in separate install tree are found export PKG_CONFIG_PATH=/deps/lib/pkgconfig/ cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=ON \ -DAPPIMAGEKIT_PACKAGE_DEBS=ON make make install # build qtox AppImage cd "$OUTPUT_DIR" unset QTDIR; unset QT_PLUGIN_PATH; unset LD_LIBRARY_PATH; readonly QTOX_DESKTOP_FILE="$QTOX_APP_DIR"/usr/local/share/applications/*.desktop eval "$LDQT_BIN $QTOX_DESKTOP_FILE -bundle-non-qt-libs -extra-plugins=libsnore-qt5" # Move the required files to the correct directory mv "$QTOX_APP_DIR"/usr/* "$QTOX_APP_DIR/" rm -rf "$QTOX_APP_DIR/usr" # copy OpenSSL libs to AppImage # Warning: This is hard coded to debain:stretch. cp /usr/lib/x86_64-linux-gnu/libssl.so* "$QTOX_APP_DIR/local/lib/" cp /usr/lib/x86_64-linux-gnu/libcrypt.so* "$QTOX_APP_DIR/local/lib/" cp /usr/lib/x86_64-linux-gnu/libcrypto.so* "$QTOX_APP_DIR/local/lib" # Also bundle libjack.so* without which the AppImage does not work in Fedora Workstation cp /usr/lib/x86_64-linux-gnu/libjack.so* "$QTOX_APP_DIR/local/lib" # this is important , aitool automatically uses the same filename in .zsync meta file. # if this name does not match with the one we upload , the update always fails. if [ -n "$TRAVIS_TAG" ] then eval "$AITOOL_BIN -u \"$UPDATE_INFO\" $QTOX_APP_DIR qTox-$TRAVIS_TAG.x86_64.AppImage" else eval "$AITOOL_BIN -u \"$UPDATE_INFO\" $QTOX_APP_DIR qTox-$TRAVIS_COMMIT-x86_64.AppImage" fi # Chmod since everything is root:root chmod 755 -R "$OUTPUT_DIR" qTox/audio/000077500000000000000000000000001415623743500131245ustar00rootroot00000000000000qTox/audio/ToxEndCall.s16le.pcm000066400000000000000000001414201415623743500165550ustar00rootroot00000000000000qWO8Ze8V]tW6jl()v$Vfq@U1Bh)`&b8wa, he~)v/}c3AkO/V!\B Q / ; O  6 Q ` s ~  ) 3 4 0   h Q -  ~ 1 : U  ,dS(PRK/)D:N`#i n2v&9QcQIUWhivzv+`^$e :m25q"t _DJ7:-y9.7,qMd H  P  ; S s     - < V y   9 9 4 %  # 4 : B G 3 2   D ; 2 ; '7?]})1E)Yc'E\>~ MWv$d8~3`%hDGz *ja?l{ ? z : v  T r k b K ; .  + G x  1 C V ` n t 2B]bgeP6 ; V  I f V{-8grUSEsIX0ElXe1p-,=a>!2UrVkw.v<k+h@;?L\qx%Sd3:pfG%`9Ix*_1Z3Xf+ 5KS_m_E#oYTWiioS I v 6WYa^J6 e G , V 6 K } B  h H ZH<x AZNV* B  ; @ PMO=1)(9ClV; sH#@j%06M>cc*W|PKd?L`Gv'XK\],~C "Y3K">F X P C * iI  1 ? p ?  d  b  | \o h 6<2Q 2 g jN( mb5 =8c_)lP3}4HJQfYN5n[_HcwZ2^ wT'Bw>-}^D|pg^n3o  Z\-~ } w y }  w : " ;  u Vp^}|CHSN  3 9 8  pB~MA+ U/y*IByTV   88#J>R88^ x R VlTP+ ;~6(v7>kQp6W%T:ZuA6+-$`g9 nB~Eq$}ljn}&l(} h {T3W ` ' O  VQB r l b u Ahy&bMd4)j [eebl_Y?' +NvRt=7E0]|WF% 0EO&R5NiT_hjm~[K7& A5ZB^rukiUXYh>[' g u=& 8  \   4 = 1 : gc"^EPxK<iz Y % 1r b;\#Xc$}c+G`z}lY4Y(){ z.{V\SnKg[gb_:TFUbp`r,NmOE/ sJ-Frda # Y w p  G q  j(\*vKxx(>A4 a  3WJ.8N\[dj]mp/ m7@[<#w;XdzvG*(D\\/d%N Vl n[/L bW^ =HtGx7R JJT  ' % 2 J9r  I 5 z_1~L8hlssp2 ? g~Mo#1?Zz(}K49/ZiL(={U"0l5}bSVizI_a >_] v)r5lx=j78) t (RdxQw)Z 3 l ~  z|jJ* l  > fO)  @)  e _ tu.n jHpB .z-Q'k7]l$6Xjq2 DkTYwMV#j X7$nd?asNe i = *u&bd?N z^i Z . [B <#X*uF~) @ ?3,l=E5h!ntq(R0` Y,MQ iyayFP>aCXNZ~"F_S-IjS xDM{B\ u N67uDL g7T)zg :  sa`UG.  @m&5[1}#U M 4h&aF6x!m.tB)8Do%kXr,@)eE+)1P=} ?&VfQC/200SumHXJD]h"~% |_ P;,-Il &  nNsE(/ * )   +HoytTU-7%; D j-,7-foWUZG{oJ72&4g=N"GD*W$ Q>1W~; QoCaQkuz9Py ^0S   nZ= [ ' G a V % q`  0TsV.q * : ~ u*!d*;RB,8d$6< feB>a`}:lj 5t8H44Ye"@I=xao rL} cI2 E  1h 4 q:(H ^ } c 6 @(!Q W(jY 6f/c  YP/ZjJMw&hR,|$JShl?C".$+':?<0=Ii-Z&h`xLzRWA]seD \EDr_> Mk=0j ] 25W! = [ t A )1QHY d ULqg Ho S7u(u {s&Y3Y`o|L]V:U3%(|}gߺYjtyxvO)v./j%`s"j'Wpy VGi!-BN   ~>[em MD4 S v D3eF!(!f]_A#Tm { B[:2Vg~|;t>$}#J6uG UNc^fގܷ=i݃ެ߳,k!?S<|%"1XV-=- t !=':Q 0y SlyQ" ")" QG)Jl 5BUVWjnL&@ |(n$s+z IV7 ߈ݖ۷MqܰHuJ,LNT9%=3Pc(`y1{]F~[5  t;q.d <[`_M3  | M a2~ P kh1{4! ##!2 9<  2O0VifTcYG$sP!Gc[3_#:E2" (6FMggrspqV:'*WK`xF ',/Hk)k +BQiu{dO,g n 5 U9}p {a & ~^nf!$%-&$" Gi ItLx0{hK838Za].|] QQ\Zgv 5:B0!'GrݙJڭx:QtI9 v4SO1mI3,4=)l~iqi(%s$[ t0e X e7fX5H , )H "<%6'^'%#! 5Dqf? aB?{T.A~cgm`d &@֯ՙ`;ܱ߂u i<QMh)zQ$S7d )Ob [p@ } Vtas`kww{Oa c 3 7\ = @ 2by Xp!#0&:(e(&$" 0A0 = q AVtX8|~nhh{ H N B {naI az $8׼8ڽIHLp/A"tHsXN/@ fD dGZ ;{59!! u]]5W R 2 eQ M UTdd( |!#&3(g(&$" wWED@= u @ 1AZ|[' >lqOkD  f9lW*PsUZj׆%u 1qA/Pw.wU<7+a+s |=9mx5 vSZ ""! a(\3A | l <  3B]{ % Qv0YD ".$&J))'%~#I!kH | . ~C(@*s< p DlX?h2/oޯ`+ԕҗ[֣f*ݥ\fB5@d>+hYL\l.yO/ {.4|~TOBb:k jyK|!##"Z! qL _9 n kw p 8 H(Z2j !"$'k**@(&#!8<x1 5]:s'G: Hm/5WkC7M^  [${A{[C#߹w|oZ$ߜkD S`% hn4Uc>}H o+_L}n\uIeVC5 , o]ox"5%$9#" /Cr] a NPTCS w7\bg!"#&,),+)r'%"0 Z;& CDjAA_Ar(Q9& j s sjhaa"]޽ۧS϶pSRd$gް M>DD3qo;\QPE9>|zwXD!N0%DD  w 19 ,sa ?   yy/}E-H&D2 & M  ' F #~h5q_t-;`edJ4 j\N$ "U _#nJt+I%Q}YFIjvbO3QC39BTa}q@ (0Ko,Tz7sWn K @  ;j5$ -@Sb Ypws}oN'#CZ|o0 $&V&sT41?VO+t7rF4-" KP$s: \/ =3}E"+GZtYd}@z:-p7t= ,Z`\E;Fq'u *MNYROF3DatU~WDDMj Q 1GJ8~FpG?@ZmjF=^A" m34Uo5  {> #Jhut[41Ml+z .Kc* ( /V;|;OL91<pIA+QZq[ff]ZA/oO aI2&X+z6`>f.R|]D -JDK{wwwoE-%PreErg ?ef.Uo%13*M q); .Pr.ksQi!_S5!y: #Oh`E!c9gTk.s0QADYtA\dW[OVJNKLC4:Ko~P{:Y*Bc5T8q!T@dSSkx,6%HBr1R?Ml#3#Uy2_8f7bLEUq8MXTLK@@+"p#[]BLs?Q82AYt #./"?[hxF"s>cw2`|KpdV<,M Z?&r&8K#cJ.wAN.Mfwwsg`fZ>2%666@Edz`#R0W8Io0PM3DI>, (:Fp ?ua [+P-VthBt\M?-eS#EioO=3.cZ@f;vY.U-umcb$vfft}jaXfokdno b&Jx8_0W"Is{7n&1!Ho+\.4Q~tR:+.+i=$!1c" rn`K&/j 9FW8j/qD%Obwn`_gf`:?DF6 K_C@8)2EH@8@RqwJ?2g;Pc/Irr[KHi(\vBhFED{-6<Thytf_gdd< v;s*RxweN>e: }D0a @sJ{kP09Xu|jkX;L+ Z J 5F_YL(rHBQn>Y{>faD0 Kz 0QPmk}lkj`K n#|Hts{?xU'm$T l},2e; %aZ#N7S,**+7@Q~&Y|tKQxBGOpTlz-Ry]C%=GS~<Wcjv|rsmT*FJ%yncdO4+>;) #Jya $(~X@`-Xj/wGEpA#':` }NU{ VuO'9CUP`:v/?Ve&OSc`#qgdg\`$>v>bYu TC|><&## :NwU {oc@' @E,9AKBS r9 ue P3d.O2LfrN$d'[7%#7Uei{%csM^h*=yTD<8l/yYKD,M- U k z x \ 5 wUMg&gYB ,c(L{{>-'S&^ ^GtR8 6X6'6+ 5$g}TeCiv-]n_Vgo{vV/FDVQH|X".Tp$e(r9 *!?5 O"UeI2,3Ol]@X0: z  t b .;EX2 i   | {  t%k,(P vV |  O0z/KSYUWu"8<{F)UNG\o9 |i@gJ[[K .QU>wid3g3;3Q A #8^Av +MVA-  ] {  y Li s D  ;  t6-F  # ! 1 , $ & MdnXD&t/A@> n | l  C of1+'!>S8v:s*lcIe%r8[iue*GD-p&M_*$i0UO]/zVU;{mH <`"&^~5? T Jz /7{J @ d * _ ? &    * % } Q l A  ! , / 7 '   c \ pd +Y:Fe@k}?1F@YZ/3a' xH`P$c$RVw@m lV1afm`J@IWEy:iA- h 0  8b6%)9RZ0aPI t  ]  T : D U j u K  u b  . M K 3 ! _  ~ W>>8jxypu*j VV. g 3@rNe,uk| nM>(Z;L;y zZ/_h7bTF'`f`\OTd`Hc,1 t R "#I=}YNI`zd !z! }  % (  [    4 R j s = D  ? e@}.:sM4^*8sT _`PzFL"VA@EGRO]#HoUOU~OEuH/4Sa1JlHJ k a b !VyO|-g6aETZ% A  V 6 k  3Z c'1BHbca#}DLT) zIeyb4MFNSa5VIZ 6U}$A.(0I'XniiPPc  q  s D,,< Ci=z4V f:-(.+ # 0 W  % JoAh .- "F+)A.:f#i0>1;Yue/l)     E x\p:wI ,W{ xS`|V0JPw2/V8JpC*Jy|qDg) P4hIH<5V [ 7 D q<i#,(#,Fy^STVe*~1(CY_;"30eu9F  : w e!<4OX! p6DF$`$)'zlg]Qbt U NoT0~0&ބ$gP)[&zݤ|݄ݔ݈ݳޟO[#enD P/-+WIj(eM72 )  5[YfJ$E;,F[_-yjvr7 :sHK"O$n! t b%,0pe1sM0{514ߟnS5>ULaߢ(eQzD~2vOZ x^\tK.&'>7$(yL   #6L912 @Pk}/sG{  , ~ 4 c *7,swK,{  3 j T G %$&AjkSR!e&Iptj ?h߾$#8qk`h,OTewR79z33f_E63J}"Zev ]  t R " $( [8R=SeB@Lu \> !M!y!{!u!!!"O"Q"*"!!; Y] PSQ~QR5I y  X S M ? JN]/\^Rg8^e{eb00B)_ߛJ ,8K~ߦߪ}+oB6 n9; 7N98aOBP]G2# d u A B9*/Lh. 8sxXH= w%0 H!!!"."`"")####Y#"!/! ? Ah Dpe0sg #_^8LCeO`N9-(^p\*(eZ^C^ޡT 7=Rރިޯ0GS&0g?/e:RQ5S 1G,k-/{ Y   21&e Q-ci.+$jJF ;!! "I""""#s##h$$$$$J#""!!D! e F4ire]l ut{,EWn*EB (;JE)e ߊB5ވ{"ہgbnۢۼDܼi.fߞߵߥ߬PodRSA`_K^wcvUq\f 3l,u,e@ !""9##($$$?%%w&,'' (''A'&c& &%j%$Z$##2"2!, (<d?o j W _ Yo@P1+]8<;26=j {9\CbQhؤؚؼOُjܟAޜ޿7ߞ]"`"oAAHL :OP?Ln-2:>7 ( 0 $ %7[g1b"S EH) %!!B""#,$$9%%&y&''(J)))M)(u(('V'&~&%U%$#"! m.Rkx  -tIhm3]-Cv][i<wGkރi ܌#ۥڞ2ؗ؃؎ح +Z٫1۩G}݃݊ݎݰ*ޚvJ^olWr$-;Ni[NK+R-6<y x y uqdHnZ[ @i [!!U""#G$$v%%f&&7''()7***<*)d)((*('f'&h&%$#"! **/e  @/6RkOr8S^UECeڳ.٭+s+,Dtאׯw ٤t 4܎5ݗ ?~K/0Gn;9}?;K > #  !#^8MPf} !v"&##t$ %%&'_( ))B***++d,--f..b.-<-,+.+*)\)('&%j$9#" wtAYR  w 3Mi~;U!E` 4)M'ߜ܍_ٝ4׿יd+֔hK\֋?mכ*|eڑ۪rL@dۘ @[``ܖJݰ'ޣ q߳wHUivP5G"  m~U#KI< + ;!["H#$$%&g'[(U)$**U++Y,,?--b..~//,/.-,+1+c*)(('%$_#4"!i jfO0=` (  5Hgs v aX\Bi~ ^ߕ݃ ׈_P)ֱcR=a֪>`ׇIئ0٥]ۿۖVھFiۀۃjbێ>ܛv%\ލcW1JI4Q o@4=D o i H ]XhQ #I|&DfY z!i"##$%&'()*I++,!--m.///0000g/.--^,+D+[*Q)(&[%$"! =E}(c O R  @ vK6D..Qz[V=߃ھi֤r$ֳA&(Wչ&x֨\ײ%صvSۉۋc0ڱڗںHYL4gۢNܙܽM<ߙJ]tD5HK e.!>( kJLk38 F}4X !!"[#>$A%h&'()*`+,,]-../01c1P10l0/#/.!.- -C,:+ *(A' &$#"R! LH + 4  ? jmGn>Z~~KzvuFݮqyٲ ،י?յZQ\Ԝ-AQn՝Ղ._آؼظثآغ rٴق`قپmBۆ۵ܦwuޛ>u [U>5 T Z o [i3hV4Y%x} !g" ##$%&.(V)<**+E,,[--./?00000X0/X/..S.--,*)Q(C'N&\%[$V#<" se  &N}{/)y n nFx\6S?<6Pvg6٠;ت:֜LHkմոմէճ`״׾Yؘؙb'8ؤنW}ڨڏujݏ:=?$+8_=b  u>+`8,w> !e"##.%&')**+;,,m-%.//0<1u1{1c11070//I/.*.-+b*) ('&$#"P!u(ZX(pB W n i } ^%,@,2]X8`!7Y{esٞiEՅXԿ110@Yxq"VؒصبغٸFإؼ=ف$TNMgڥۊܓݵ ik,RY|h  A m4l4!g#( C!+""#$%j'(4*5+,,-../t0n1F22 3 322:21?1000\/K.-+p*r)('&%$/#!{7 )5f>VzY +   nM{Zuq#V,aۥڒ٪+כT՜xA:bqJң}`Ҋ ӑ_ԀԦԠԝԅf}Բ OsrO2@Օ֛'`م+IޠV(lA(e)Br!+  u P|2 V%tU D!P"7# $$q%S&Q'w()*+G,,-..s/v01Z22H333{3G322W22100.-,+*)('a&$#B!T$Sf ooO)9`, %[3 ܋3 @}ӫ~:9DS<q$i-{ջ2qְiףױׁuz-؛y@ڕsdޠ6%Vg4\L1 jIklXM !"{#A$%&'5(y)*a+,,c-..y/0123!4l4444e44333a21r0d/P.h-,+*)r(&% #ЀL:ώU=b0pӹ*kԦJqzՉո֠7׭SخذٔH&*FqߜUrR$dEuX 3Ct?` v!4" ##$`%,&'(1)H*G+ ,,--R../51234Z556>6O6/6655@5432100a/.-l,#+r)g''%&#`!&@%b&  iD]]OBaY%xZ N<ۆ׌@2QЛϚN-N̛̔M#%tPҩLӚMԡ!Diխ'֫F,W6 bٳڢܴۧ.L`6n%ZE9  (@uy+ U!""#$d%Q&+''()*+,k--T..C//01)3e4355 606<60655N54X43210//P.-,+`*(&$" #IJ T  8z.m`IKzI' _RڡՍsj҂ѭEϯ1Ͳ͉$'$7$j$$l%&&N''(f)5**+,,A----w---./G1234H555+6C68665F544)433s2=2211/J.J, *'%#!) v#!c%! u*P~mUC@V'ݐۯ؊աmrѬA͓+>-5ȊRǸnȑ̩MοsXѱҮo)ԁxֳ$tڈ*`Bېiے۞6ݙ޳^[&D2 eyi $E ? xr;iik 8"#Y$$$m$<$<$$$f%%&&e';())#**(+++++++,,./C1234255>666~6)65w5L54b43o3C3(32K2 1v/T-*O(%#!I3an L s X%'rJOD0Y:Vs>R8tL4^ל*Еύγ4ʞ'd`ƵFɭ͵αϡQюӶӇ3՝ֈUڷlݕLݾ ۮ\9ݵM|rQO\l- "5<J!#$.&&'&-&%g%W%%%%%%&&0''n((T))d****I*Q**+;-.024556n78j88X887t77Z65:55443208.e+}(%*# >Y8 Y M iHP15?%ROkPE{ݨ. ̀5*3BvǷ1~ũęó2;p ^@7՘Nؽ|tڛC7߬ߋ)ܪیۡ۶ۼ|H?N{z*"O7cfzN -@!U#;%.'()x*O*)(''&}&D&&%%%&&&X''D((()((()*,.0&34X6{789:E;;;;;L;:b:9y9,9887[6341[.*'$J"E47` 8 s{FMl9BRh%F 4VبIЌr̺8ǷƌšĽֿؾCOg]°rs ϙ % ׷ص3ܙW*e. ۥڐaD@ڛ6Gj}bmd -@ l Ul!#%')+,)-,(,*)(('h'&|&M&J&e&&&5'''' (''''y()+ .0"3;56;89:<<{===>>==p=P=+= =< <:}85R2.<+(J%" `ps x \%}d:9%sx3tchݛ\յ>0`GŃP ºZȼCf)y ̜FэMx״ ګR߶Ksܛ;epޥu+w (7B+PJ8+ 8 6#x!u#%g(*,.d//.-,+*L*)(^(''''Z'=','"'&&&o%$$$%g')>,.02P45789:;<<<=B=== >i>>+>=E;8y51R.+((.%" wR  18a}q"yml0xsOSt Rγ̥>Ş#™lۻXER~|òśǝɺ˹lѝԟ/צ2ۛudRAy+4ݓxߩAHk\t{K +$(4i!#X&(|+.N012220/-,++*)q('~''&n&&%H%$F$#"""#v%'*-02357C9o:;\<==C>> ?I???@E@?><0:62.z+5( %!vVk c?\RkN(=`utEu`$X 7ҢW%7SŏFdӸ^ӹл^ XÀvLjɬͦs4y*ٖH&5n*.=VޕރޝP߰}'q`iBXA r{Q! >#%(d+.v0G2o33 31R0!/.'-L,U+T*)((''x&%J%$##0"C! -!"$'+*x,.w0Q2!45J789:o;2<&??*@@c?=b;8640-)~&2#%J '*^nzxz-NI6:o'5ߋ܁hoӻθˉvqtêBgWζ򹩼e^đSnvk: ڭܓޟB' fKߐNFh߄߲O'{hfq" G"$'S*6-/o2S4p55493{1/l.2-#,#+*1)`(''&%P%$$h#"! O ! $ '),.024A6789:;><>h???.?= ;7v3z/+($e!  B   D]b`@]xcfXH o))&-҃̋iecąA^ٷhhvb+v0`΍ЄpXU5#)[T_Mh~R &ePkR1\h ( 2 #Q&1),/146j88)864=31u0/.-,+*)L)('/'`&p%Z$/#!!o !)$')w,.02*45I789:;u<==N>>?c@A6A@?=9j5j1-)[&#i e?6qnIB)7{HF;))}'+9:Uvɀ;  ζȴ ᱭuDٿ)j.ɝg|Ҡgx.P?4 )t{T**##PphH]Wiw42G V #'$*d-03$79;D<;2:o86N5#4320/J. -L,l+z*o)t(P'&$"?!x!X$;')-,./1Y356#899;;<=>?@ABBAX?;73/,(H%!, ^ pDt%Yj.Kkk1)RKogE',ɉ*Niϰ2ذ~{wT-÷RH҈jہ@Cfk$ 52rcF&kl={iG+ ?| $''{*-w1 5m8K;[=#>= <:/8S643820Y/-,+*)h(A'%$" 7kw!${'7*y,.{0X2746a7v8z9:;<=>?AAqBUCCBb@<8D4/+'E$ DZO! jN8.9^ N 1b9Z}ρҵMٯe,d&xYZ6w6B.7V7 |o3%!Q$L'Z*k-047;=?I@o?|= ;8I64v21/.v,#+*('&%$#!A Z\8"p%($,.D1A3:5'7 9:;<=>k?+@AB/C]DIEmEcDA=9_4y/*y&"!J{ & dWn+z1zY8yD'D}'i_dU59ՐK\w-!︠*aˬ .躢ğwnv<ӽD<ݚHb"w5-sG2ߕ]Xv (XNI #&)-d037;'>]@ A;@6>;J96431/F.,*)u(2'&%#" !"FM5?#&),.0257898;e>?@"BbCiDD DA=T94/O+O'#"K :$@4K=uskl|lw$Ӊ-@ʵ(‡= W㷈7Īm[LS;ځܡl$D'3*+-^03l7;7>@qA@^>;Z9642l1/].,;+*('&%$#S"h uX;!$N(+.134 79:;=K> ??n@=AIBCDE~FE|C?:50,'# Mi  M j 'e[+D~mFR DjoҽzwɿĘ?&,Ƕ'fڮ;/˵lLȦpѰG@|cGL29W|[b#"߷c݄Hf7^ ag#&),037;?ABB?a=:385$4291/ .,&+)M('&$#" 9\"&*,j/1|357m9:v<=>?@AB]DEcGRHGEA<83B.)%! 'Y >A3iS 9rDS9S8|u{*`7BdΞ˟ȥŹǽ9߳ʰCک.ó6 ٷT|H&;y"3i.fߺZߝ$W\  d#&**-1509a=gADE$EBh@=S;8643,1/3-w+)';&$h#! 4wjS'1"%(k+.U02578:<=?@,ABTD.F HqIuIoG`CP>r9h4/*&/"MN_ f xmm{ߙIcgxc`JgPC*pTrnQr?7MՒ̓I,Nֻ0{˦~G(ǼPHǷ1Cl׌r'~{~?Pq% )e@jYߥw$ܮلڕJZ ,Kp!%%),037;A@DHIHoFC2A>;9742W0I.m,z*}(&"%#"D BNt!&%z(+.[1368:<> @LA}BCDFHJLHMnKGA<72 .@)$ z (ޞj_}UUmLh?;Fm >NԴfbd޲9߽p v˝ν.2ݼ37UZ+^=7߫FDrmNOړGJ #l )"%))-049=BGIHE^C@e><9e7D5K3`1/-+)' &y$"-!*DMX T$'*-j02V579;=?KABCcE3GInL OPNJE[@;61B-(_$ }@ =u"Pߑx> Dk~yh"|#O~#Jg `΄*̾Z;/EȪ?x/Pih̔ϲPZ/%"ilK835WjޥQܕb؀^1Lޙ ,  )%#&*5.2s6b;@EVHGDB@@=;Q97*5Z31/.4,i*(&%X#}!CvU,qE"%(+-.02468:T<>?ABDFILENLHC8?p:51s,(#jmw _byr#Va!Z U"LۜԱU ĺԴVCըzFTL8e|͸!څ l]k2uE!NY|]/*$ۦi՘g#> 78%1#o'z+\/Q37fu z*#l&b),l.02578:<>@.BC0F&IJL NL6HB=9>4/*L& " wC8:%a{ߝnl\1c-KشE͘~`+ѽ˷޴ sLþ`Kjϩ7h+Ed[w`^;+F/2PۙԐ[i Gj6L -Oa $)O-X1O5t9c>5D|I_LfKuHEB@h=:85u3P1@/-*(&p$!\$H FB#!$'*<-/2g468:@ALCDXF;HrKiORPAKjE@:.5/X*2%H M h%"Fi;ۻݐ4zr`B$Z_ 9 Hq~J/*.pĹ[Wðꩳ:j˴XͽE[pP8 nl{/C؁`+f׶{F*#x #I(-1 69=SBHNHRPLIFsC@<96"370}-*'$!j JF q. ITl $(t+.147:b=%@BaEGGHJ6NRUTNTID>9[3-G(+#! 0~Ԥ7܊}I&X}"[etx 8aJcґ+ŮOT 'ħ졈:#Feƞ6ӒI>8\ t  *6eBvx Uѣ͘˛Ψԅ9N= H"A',1y6:V>KCAJmQUSP5MJGC@Y=U:O7H411.*'x$?!z kgN 0G5Wc?#&C*-1j47:= A9DG:IJMPUYXSMHjC>82-Z('#+ `pb  Њ+Դ<ٷN ]j5w?vm ( d+G1I&ԂO ɟWʟ S3͵M\tՋ޺D? jh]I\aܵqՓԏҤ5 )'M KR %O+05R:U>CKRWDVROELHnEAX>;7e41u-)&0#3  @xa 32#&*Y.,25L9<@CFsIqKMQWX\)\KWQLuF@:)5/*|$L \ f?͵̣\P _ o S>8j cL v)Xi5UƲz .hᖵH &̴e@ПV7NSu+ wl + *Jw-P1|ڙ)|ШʦƄȶrD'~Xl/G$*0'6: ?DsLTZtY2VROVL IE)B>H;7 4[0,5)%!8J 5M XrimD""%)-,14(8;?CFHlKOdV[H\WQ?  X3sT6*TʝźkIتɥj !"<ޭ+軩zlZ1`S\P { ]' (R5݊CnDPqCH5$ M%+ 27<AFNW:]m\XlUQNK~GCQ@<8-5O1y-)&"  7 O/= y " &*.1{59<AEEHJ MQX@_O_/YSNVHB<&7X1+%OOi2تU;B;l0~ : #IM؉dkɠĮRϴ92ᓓȔzRֿ&KP^޶\y 1 5i? yczZDٿQ7<~„˯3Mp &,^39>B^HPYZ`^ZWSLPLHEYA=951-)%!/Z xI6 ]"<&W*.2\6:>fBF\JLOS+[aFaQ[UO JD+>78:2I,>&8  2}|ނuUĴUʆ͊г.[JKy6 $s Z l;L|Ӣ́Ȉ( 3ơ퍜;٭ZiZę5VcB{j x Z <XRhw J?^)ݛJ+4N.f?( Q!$'**,2(6410-+@*&#! I* Z.uU\ 8J B x # /,+9&1] ~ 2 4m ?/c{A0;$                                                                qTox/audio/ToxIncomingCall.s16le.pcm000066400000000000000000004766521415623743500176340ustar00rootroot00000000000000O+ ;&j+./.*%[ Q7-J!(,/4W8985!0E)/!l:1  !Y 1Shc;t!ǿ ЉUݩ;@BڃϐRۅ-(+W//~- *$a6U#)E0589{84.'r ~8 X$HKoݪϛɗŬǜ?סB&7 .>Ыvӷ y&*:,/4/,)#?\CBg$3+c1L679973-[& u W U>DL Cȝ_*fY4 $Խ6ѴԖ o ?+,0.l,("8^W2%r,z2699U72<,$_a; V ~ S+E6.OȝͪfIS@y.ו>Ї1bp !!=,-N0S.+ '!*139C '-q379i961*^#E m =s#٬ѳ˚ŜƹɞԵ?s'ЅҠߣ "U#-|-i0-+& s[)1QL!P(.d4@89 95i0)o!G 0 }k }%vߠpжDž3c$LҌxE߆JZ#$--Y0?-]*$$4+c")/N58984:/0(z Y '\.#4*ɟō\{Kބyd6tktyb#T&b.2.20,)#4E$ $*16 99$84-&c6{ RG X(\RY؊ߌ?pسӛ\ϐԳK*m$'../+("~Cb}Z%(,2269972,/%Z Yl y#91tjEƽX_:ddAbBr"$R)/./H+'!}jIm&\-7379r961J+#ekT I + ;[wSň}iΘkk$܋$ӭϑٹ =%i+,.0-w+&!?r/ 2!'.(489!960)"n Y x4 `,t"8L~նu0ڼtқϹѵ<ۉڗXV'+./-*%J'~"L)/589875/w(}    e[֖g*ˣ1>IvuџљqܒJz(o,B// -)$PUJH#*0589:8F4H.'9b v ).\ r5hф0Z x޾S<9ҙӥV  M*,/;/,(#mjps%+16c997R3,%VE KT n hz8K #ۅQz'&ƙ yY6jwݴ״ӴtԴT!=!+/-/.+'"I:h&-3O799 7 2+#/(V '1@^l$frO$E۪O`^@hܸ"Gj,ָ* #r",y-60;._+& !% 'C.379:9L60a*/"t "Tyf?-FJ`8ϓ^5@+dՏCӮזb\ +$#--A0-*%  6n."(x/4}898z5/( Ph V m)zJE_ֲ[4|g&ҰaѲMo$%Q. .:0-)r$)$+<2v7:;: 60(l Ivi  3,OE/!t~iCͶM H|ҤgRSٮf$4%-,.*3&= `W%,38<<:M6/1(v0o s SGE5!*ʞy4ԶJEz|WEؙ!I*+'/,b*$~wp1P&-P4I9;<94-&gy v l ,Y-?!Vy@*ɻ,Շ TRrrٙgͻ0/, (*,)&!k*" # R ^.$+i2^79972*#x ^ *1`c! qڪq͉ ncs.<܊e3VFi$U!' )*'r$i| :&-3]8a:9n601*Y"K M  W 6~T*V}BCʷzprںQ>?g%]-qL"A'()+&"t   N @'f.*4 8]98q4.g'm,, L  [ /T/%Iؗ^=ˮ;ۍM Bܒ5h</ L}&G*++-(#{ e V r( (/k589W8740..&od  ^Tcيҝʁ חQ!ۙnϸ d@p$I'('@$m u k w nB '.K4{775e1*"^"  *D6\PЦ̒X  Q;F ѩKu%߄D!Q Q)*+* &@!a?* # +1368=8@5/(m 0? aA6 v"SΘʅ+|$ w`|ٵjӲI&$--.+'>![ ^C'e.4,897"4-%<@ em]<\@%ރ ʿN@>:3*a!I5-Nk y5)c`TªPP> c0#Nח~ /1":F96:r521(*#jO]'$+?39e?rB8C@<4o+!KU pp,kΥU@7œsF[pQ޿اӾΙ͍#c/32:9:4F0(V"'mf8}%-5mңqۼ_J$jm3jt*2Dž{Ögf٥ߨD).55R61,%E]%-55'<BEjFD@#9b0s'P= ' ,;hʶÙ1Ņ̺Ӱ%+gӞ͢AŮp{!'-73;33m/C+$P x7+X#@*18p>uC@FFD?8'0s'<W v 7 I9i;#XxøRǶ̀ө-Gԯϩʒa3jk(#*/v//,2("\m"(0>8?!E JLL;JmEe>>5D-%:fsq TJt8̸(澗<^Osv)kYT]ĿƋ2X@y&M))(t$U PEK '/G7=kCHgIsJEKJ"JHI*IHQF@@9?AQ-< :)f US/һ̍*ȑoO΅̽Zlh}$"/Gi f d{ 7"+1t4[3.&dD cjA"[Ӷڌ+S3 *BsK= G(+<6J8|66 6Q2*o" " %3)shr %L; ,5c=BGIH}FBG<5_.' LIk  $lF Tݨ"!Ťqy̢QE H׹A7t֗հ҇up֣w c_!q&.2787u5]1,'"O"'-t4\:>fAA@=7L0' ;+!glǍi]!"8݋Z[8[A!&C,*X4Y6:"<:"&)+,=+'9#^ Dv X  nE':anN;ݝݮ-#$1`t=#Vӓ\ܙ?p`#! i NuOpcJ&N.)48;%;L950=,'"|x=3z"7#(=)y.*,4 V.Yd'Sަط`Ϫ N&և#Wѫo߸}G%)4Δ.$+L1$55~4 /)"d"%.m7>EQJLMALTID:?91*V$b)  jI PMkᵏ=|[juڣbJx)ۡՔxy͠+1>`BHrHG4CL>70u)#%c" ',147A::]96]3.("R  z ZqoAלʢNC375YXyD:8@ٴ֠Mݒ~('/233V1-'!'] {R @1"D&'w('%#, M}S4k*"afPJwt ֫]{j;S733^ۚռ)Oɞ̯Ӎx> \p #k! d{I x<R "(&.O123j2/,)D&#>!AX L!*%$JڵY˥na/czm·ǓyٹĹrIŤM4p)l,//-)$P6 KZZC$,3U:?hC6FDGAGFB=b94.)$B x)Wךњ˿ؿaҳչ4׻߼kff@84ȌDH'c)>4|;B7DFDBj=h81H*$ !%")(- 2579: 951Y-y'w!I+% V&&F=[6Se_Q ^vRei Gל8X1.79X;9_73-&  x[K y"T##c#X! Mj Yeui5Of%hwي{Χ|!0'&@4ׇl̬ȋ ]UXԼp 'X_ ,Dl*{#&S*V-.12R33g444320E,(# S4T1Uۢ-*۽役s¨ƨֿ۴ߕ]X׉ʭTĺđQg9'&/,/1v//.+($" } "$&8)+N.1y59

???t?=:62,d%8-C P T$d\V2n\Qgϕ˳ <9o6qg>"1{iiΏ5/80g158<9963/,'W$ Jji #'+,043l4 5x30h-'!`  |F`;!xO=̋0ܸd1?hh)%>өQ-l" )8*-, ,($_ lFfRj f C%@)`,/%1,1z/-*%n `iKRh26d$ ke߆IV^"ٹ. 6Jϝ,.b"%%(&&F%s$#c!`nI@U!?%).385<>??=C:4-%z W=q']^jȻáľQÙz^,bT(K<d!O!G((5--//0&0o.-N,*)&$#$%a(,/2P69:;;&:73.' ` c  e1^Gq˿p=FXς =Nq5E-ߛݮ ۥ'>{ -p+357E863/o*$C L r I%@*-00/R-) $)>Y \/ 5;q%SzKGKI˄v8ۃ^R,D3w I,@& )125Q2/)$TY uG3 1")0539N:983,%Fu(L"_A 5 ]AM/nƳ̚p [b?G:ݧѳˎǻƈ32>"(+./%.+&G"E  XY9$n,n5=C I^LKID=G5,c%7 SoJ7IةЁJùR]؀@(7*ׄfԈ֟kk'*249997_4.(" %+'035776s3.)#/u JrEAI HaK Fƌ;½ ĘȮ%[p2-jw$S x5#3<*>A@@=`92+~$cB |vvS ho!&)*C+m*'$P}s exlYuְӘh׋xp5n}:>~ՑϘ˟ŹӶO ,#$j%" tP  9-K'07=ACDC ?93e,%{>a;L" n _ C @P\G058&̸ψ>ݞܛՉZJ'jʷS,1+3{6#;I>QACDEl?61,,/7@ C\EFbHGL!O!QRT#V+W;VP4E:%5+/izKϱpWƇsܿ;ս65 Ѵ` ձ񸽼 i#?(zLHJ^LLgPPESSSRGL>4B+%$i#. Jjuvfb ,a #4ѝT!p`U+۹>ȋQ=g!U#r0388|72-'"{ $/+03',19BuKR%WXXUOG=h3+V$ =X!*  ?U|c]4Íɞ5ИѬ|nɻp,ͮ  $z$/$ DL{!_"*D28;4??=B81+$ a}=!(i|~TיAg\ΤvG#ZhsrX}Ԉp],ۜ0w!+3/i43G4S1-Q);%#f#$''*S/;6<{ADbFEWB<4/,$$Y\W~̤`5OLEX/cɼ;ā;ޱ#..3f98,97-3 ./+g)T((*-C3`9>B_DEcD@;a66/(!~<Sxk K܈KŌD(Cȅ8XҭSoZܻ7ә8'02 5Q1.A(Q!qNg *l477c/'L#%WFotȝC=p۫3,ڦսz?U ݉ !"?#<m | ~2);29? BAz<5-##   Kw,>},γ\s6@ف$W j؄~Գ҂h; /'E++Y,(}$hl*l;9f`%+38n;:6:1)F"B >^$91~B~wЍXŦC$K|71A(X2$x(,._..+(%#"!Z!"f%(U,0H46w75q2,|&T H @I9< |UUݒvD]c||V#+/12]1|.+[)&##$&),F0&344u2./+I&M! "\/O y_\I+]oعR=DeHֹЋ5½R l̈MO$*M--*C& '  tY3#).>3J5q530,(%"L"P%8 M=5b"j׌/ gv݉\`' L : LZt& &+V.W.,s(L"C ]8]g8jO<4BwpߞܑJbK&&#M+)*'0"O5  '8R  s%'(p'O" nb,OH.@-7߽Ԡ:ңԜةmZy[I|d."3/50/f*K%g8%C!'i/:5%9::j6:0u'L R5Oސ_3ԧpѐxsےط5{C۴݊_o. `%%'A&[%$%&)]-15;)BFHTHE@t90T'$PV%""e ָ6ŸN1׆MDΘ (> JpYk/!#[)1.M2H566A52/+3))&'&,-3O5)]$zb Y?M@Ѵ^?o&Ur7sѣ=&Ʉͻ֮D'8OP R} m %*l./.+'"Lzdsh)+rH# D, P_GTm"T(/.6661,,#p ;w MU#),,A*$n  89?ޒR&ӷzߋ܅h$4զV6/29=!<92* 3 ]W%R0q:BtH.KgJuFk?F6,#}Y Zl WHMVVt8̦P)K ~9agۻWH#~(Gڴ.! 7 ^vU%0a;DLbSVUPH>3 'x 8KET ?   ߅i ̪~k/ۆ݂ݾþGL/#]$ 0d ^##N+2 7873k.'!fQ$U(9(.[j"5 pḷ+΂߄%? yrEǼ¬J!ZÛ߃ 6 {P3i#).08/+& ,y3p&1   ߳sOJ,@T!ÒɁ"!W  C!v&,)g)'"C  hU~2=vyJӣӢ}DZKExl%CdF-_..f89;72U*p 9c+xnc _)Y2n9e=]><71*" Fqv ɣ́s'0%?5ԴnDžħylɄGF#"#hN  2'2i>GiMPO)LE;U1B'x7s (H p@P% lu+6C)!΢r9E&T7&/ g} ,W"n,4:=>:4+i"i > x J$ Rz 3r։v}ʇ;dl=M{9@ψx c#x v L rB',0]1/+$!  .L"`^ ٗq4 ֩~"G SUX.o^P l&C)i.--v)-%& K-!'9. 48:95R0U(x .B  n$9ʳdǏ׽ 4޹!ǐÌÈҔQQ5+3>BDA>81)!/;-_$(,239={?T?;6/v'"*.Y~u9P4׎ɍ²« e~߅6#cĎ ɼ7%,27G6q4.w(4[ [% 7 +j6A2KRVjVQGJ@5N*$   +XfzȍiYτ'}"'nֹ˘PwǸx@p #),,z+& Khs 1 t"0.8 BlIMgNiKEb=3@'^E05/ H  _ǟ"ͮ %'ܫ1_ؚѨў@s!!!X'(9'% { 9 \  5&.6=fBDoDAb<4+g#uw  pIQ^a%8Yְ˧S:4Ƨ}nLL@ ͗,KՖW "" !@%&*05:q?%CD[C?:3)"J VU$!C/|<U K|TҀ˲u&$?( iФ ėҞV^#j&+)*!($ :}p? <#'0,/*368:T=e?$BDH%JKIDFE?F*0'),539G7}S(iΩÞŭȦxj}@!@;,b=۩z*{.!,z5!:;: 5l,W$w `@W>" spT`m_!"P#RH 0#/)a+((('#+xp*gvaWw8 4<1%418PRI0dUn,^W`ȠB|M$|*.00,/*\$y6 _d=6 !n*27;=>;7Y1* #\VVyMk8b:8؞Ҋ" ŭV5KƮЍא_xZd=ݑ3Ӌzoƚ| 4!)>27b<}AB,B?:3+!d bqa  Im1lT(ɕ'ؼiQαԃ5\ b.G0C=@pGIJIEE?@91);#. %_+N197;-=*<82* X ;7!.e/2 0'u2o[r `4ßxBD.R}&]prka?l} \d*377>\:5.$ vm /%.%6<@ATAW?(;`4,%o ~ a /gl/'|$ܓ֧Ȥw J ZV8N*׺|SѤ ',47N9$72,%Nz^ M O < '&,2V79:;q9 651*#cOq =E m'`@ٮQϵ˰EDłƕ?'T\X7ϣOP"V)/3303l/+|'!#s a  $"%(**:)%I!? S z ( s*K4.[2+nybqUݹYZM4ߺAF({t^-( 4yu Y& +/`210-))%: _~5h &]$G+e"IP ^=Rk~$_?kalyޙZ̎)NF! (.1d20+O&t a F.M"T+f3}:@EeHXItI9GC>9i2,`'c"x  E*Mm7C1ѹ`FR jԒXY $.;@GGFrB=~7/U(!'2!x%*T.1\57886H4e0G+?&[  tW,4 H ^ mp޾ўBJe=D3MF\ђ ʽ΋ڳ( +e-(-+T(U#v ^N UX$L),.b00.+)_%"LK7~,bcM9f#1Ws^бiNOC`ܭ|ϋu±.ĝX#ۄUr? x< ) GTQd{ 1#*d169;/<;r916W3/, +c),('g(c(b()`')U%,K~&(K};wLzETƖ%ƺZ ׄ}MV߿:ׁЧE=\VlѫG%L(++*'#:3: 8 ? v'1}9@EJM*NWNLjID?:4/*&"G *&?rw!zX<ջ:l9OwRG޿}s:9>"(29<;U=5;83i/("}].X %h+05l;:?ACsC0Bm>`:h5?/v)P#:2 o N [ %Q0& F2 𿮺WP"Ω׺+<#0AʹɧɵQљ #?">,6-/-+(o#S D ;A e;e#'E+-./0/,*(#&d#6!YTQ_$ <XS^ Tu3h݀vMq Mތ:ta6þKÀ r " t -%?+/4&78:;<< ;!}d<߿ueƴл$"F$--0#.,)&;# R!$N'*4.15:=lADEBF,FEXC?;:6b/'!Tx  0 c^Zȋeʳǵ9nnj^B@P?˳"ʘΎE7b /.[466z5 21.)H&(!V"M.Qg"'+0_58:;6:z73W-&[ %| W@9sp\̊bH*ѕ.+&QN/ o˟y~!ӵںgy x!**-+*;'" \} t5 r%(+05U8:gCJFGGEAr<4,#x~  iZ&WqLPϦR׻rP;œ7T+U QXͣ˿&HVi #9*,0<1=3:3 42/-o+)*'$<##m%';,15@99=@OB8CB@=842*!R\ /  CH ڝҙ˾W=58'{"^ܺUp=ePG-]+p569E97&4.a) #_-  }~}|#(s.s25552-'! 5y_ Z#`!Sg7yȫʴ,ԩsv=DpW޲ؙEйZJ%+357*5`2,%_+ )J\E'/(6j;>?%?u<6/'r-CC~o ;pl 30Ei ŐL@j Vn3۸յϑ5Dp$mռ6L!%,v.K1E1[/+&!7q "$,#62>rDILKIC< 4+# (x mgS~cҳrĚP_e12޳ێ؟ֆi׆ۋ!Ia $(/5=8U<;O;84c.d(!s0*%*/'3j57s75<3d.(V"kr &K8qApr4>]OVwAƷqƬjamک A!c9o( 695>?gC7A>93W,$ a2 ?) ~%';)X)e(%! uHo~VUvbbw17g=`bRA\p*AQ͏DL֥ӑ|=* $&:&#!N> I U%(07=;A`CCAB=71*$Kvl:/)  Bgړ*~\>¶yđlER-VܸٔL~NΏ -qo<N.S-(6m8=O@CEFSG?[71q--0 9@BDEGJ!MOPRSTSL@7f1)n,l;]0h{ŞCa˿de ݺǹsӶ<ܴC4} v TX(8" W*G>)RQ(IMMNQRTTTRJ<2 )$"3#+`f+t dvfEh̾ˮͯFԠ?μmidű̪T ! %1499#8H3.. (=# "').6`@HPVLZZ/XRIX@^5+:"J%77F.5އeS̱0CcK̊ϿsF^i:"'*(O(|%L#+!1z!$)B09oBkKRV9XW\T NRE;i1)"ty#(=9 (Aߞב%@Z8ͅO5Мˬǃ~KMĞrVv5 %%$ /4a#+27c;==:4z.'!ZQ'T8!#3"dJlpiS5 6݈t;ԙӼځݥ4 &/^3766F3/*&:%}%&),^299`?CFGHF@BL;3*+"*7S;;]C /ȵZm([W a)ΈȿGJ%%I^%=v:A3+G!in D D'24T6vzR޽_ke! צЉ͑˲α.+ڵ<)? z "3'z2=+GAOUXWQWI>I3 'y W"7 $Ye ޤG͖,oћݸE.җEnj:a+ j1G} #+i237862a-& ">v%t*8<)"" ! P`ӋY&P/QXÜW,A={ A:v7T[#)./- *% & Ov  yhoEۃܩTfU@:|@!`-;;N36#!?   d!4"7&(O(% - zmOr9?YU9b6ԳeCnM4~L#[// :0:`><7G1)X",QQ)сV0>Ҿ׈ZuLjî;( ~J#x"##Q }B x)4&@HFNPOK}DA;0&^r D ] ;#1 =wPtţZIžMljr V .B$-50; >=+:3*3!o 3:#D+  ްՙˎNI V!kj<'ڣ`v"ayf `9 7) M',/D0D.*" P uh;Ãǩ6H kN5@͂q4J&'G*.-,(F$Qy@:"s(.V4/8V:84.&Aq WVB]S|аɬÝȷ* ݦծ͙GǑW۔#+y6v?%CDA>/81)J!E v&-4\:>?L?;@5-&!z=/g`vzѶ.#]%ٖӥȍòî۪:),l4V7%6K3- 'p^ =  ,8FC|LRW,VPZI?4:)>5 c ( :/`، [UȦɥ̖uثܡquո'vK/Iܢ""g* ,n,*$;a / 4>$/U:{CYJ3NGNJD;N1C%eL ` ./:Q0=!و۲z6ojq$f# ((R'$ + AR'/T7">IBDC@:2)!5  zwFZ0߸9et Т׀Vu4ύ̧̭bR,^j!"N" f3"%+u1{6;;?FC@DB>U9K1D(E!aN! G!3ʶĒg4\ϹŽ 1B$]&))'%$ X/ $(-w04779;= @BmEHJnLLIFE@;F$y^@[^ "Xּ̽UƤ\Б2pX"u ;TLCE)"-26Z:;*:31+A#3YϛרhގU%:>X8h#tؕٱ g={ ,&/)0+#((f&"oN+ C"3Uxyݥk'"+jلԩ6NJIʆ %=+/00.Q*#: Pg; "+38z<>=;6z0)!mI-0rjgN* Zזѭmǜi1OzȂRodk͒X`ƸͶJ " +38<=6=:5h0)#)F"(-X4:'?BBDB?1:2L*} 5X i? |AIHIJHD?+91)"?!&,z2R8S<==94,T#KG MDI&q/d7m=?A#BPA?m:3,$ 7 s FrJX:t7B}ոwhl&ˆϧԌNas&%l 90ƪ}=(-57z96'2+$58 ] 6 ' kMD '. 48: ;7;;95r0)y"|| cG (*A/)@I:ٵӛ'30ūɛΌԸUi_"Bl"T$)03)42.K+& o m r C#U&:)**(u% :  1^ +)nO\8 IT *w֧٘R*^҃c̀it5> HkO \ '+0210(-0)$@c] &$,fvi YԹϵ̊˫iBՕٕo݊5ˠt6G"(/120*%G B_]#,4;AFHIyIFC>@81M,&!k+MpBE9ۡ<ƀ]޴}ߞp5oˇĖ"4%r1p<@BGG=FAJ=6.'1!  W"I&*/25)888j63/*%yrJe d9~  Grl)sG~ݝׯ2ǫ~ŕ/'OB>0![̢ɔ1Yیzd)"+-,+'"  y U / hq J%0*b-`/0/-+(%!^rwue?9ht|9ee۵dϾЀ~>ەԋΠċ͙Xt N8 K<{ Btd >%),2y7u:D<<;H963Y/h,+H))('(>((')')s%&-~^TypdcK=[ʭJɒb2@ؕu&$_"3ψoZ͵>>OυQ&(I,)'"k^  ~ )2:AFJM{NNLH DI?93B/!*i&"k1 4j#z [۾nrױȰ!: k*oؾ,g;Z%f)4F92 :4.("=m kEMJ] 92ݞՙ͡ƶ(@128ΰɐɤ͝ѝq##--0-+ ("5! X f( x| o$(+-G//P0.,*V(%#!GD  C"9{S?ǠɌ͌ҕc3H)GċHO9& ]k&7,047w9P;BDgFwF~FE.Cp?:5d.' ? 3 " Nk_#&c]ߝ,ֺ*Ȼ\LK"ڽiαr*Ȩ7j"1.55651-)% k-s#',1(6b9 ;;:<72,%V xF Mm Ws.Sn[9ӀˁA)2 ^%[CgsmK2˔˰WIӡE#O* +-+*& " Ve Q; ^u !J&,158V;<;;84D0)" `5M ?I x 7Ot2˚Қ,gQ ZşC%bu5eS >&'))('%"F^>e"d&*/4:P?.DFGGEQA;3k+"AH q dPa}w#ΟT鵄 IJ7lhڴcӮXL̤%֫Z7D%S*,01j3z341r/-1+('$S##%X(%-{269=8ABmCB@=7A1( ~ $ ?ybكњ|Bt{X/Y֝V*a 9-0,f666:87k38.(8"  n}N$),/3N5641\- '  T&g# r}IYfȫǶ [\Q8,I/}eѮh.Մօ!$-3I6741M+$/wlqS v(*07$;5.&'C 1cw a  <4/VϑT&Q\hp 2Ă:,J_Z"]&A-.1 1!/ +%V   l %a.j7F?VEkJLKHC;2*" Sper nb4шɦ).9(ȣB/l)sw08e֚z֜QݯS'_(05-9p<;;H84-'! 1z %n+J0357K752-'F!(8yVF,631wҕq} ""+/ۍ>{~-&92W+$DX v^B %!&R(a)b)'($% j _uPc#nBwLec֠!ӋIܳX|h&Ѽϒ̓"׈Ӂ'aM!$&%`# pn ; >[Z )%28n>ACC A<60);#ZV)4~G t  *Av>—ʭҋֻܘݹړ&Ҏ}ÀMP}m.b.6 9^>@hDENGFx>61S-V.1:AB/EEGVKM^OPRST^RK>L7/*)xq@wɤ]{~NҿE~##}g$@˴ 崚#ԷYh1 x! VMg*I n1RHNMOQ!STTT RI:1'$!2%/*] R;POGڗxag̞H΃zӳqخaѸBWΰ>z '42259Y97g2R-'" $#M((/ 8AIQVW|ZTZWQH>3* tC& 6 Ee%gDZW3{k4`dZũˆGq$׷ #2(#((%" \!%*P1U:CLmSV-X\WSLC&:0(!^(!G%%f k~IބPƽ X p8-ռ ӦDN%H5 !&&%$U $,3t8;">=!:3-&Z  gl#nfz[vLLcIxNMk؍NwԪGݮM (0M476w62/*~&M%%P'*-j3A:2@tD*GGEfAf:N2f)!|: 3[ Jд"޿Qre.9}&QGĿİџ"o'/d59#9(96H2-+*)* -07=jBDEFsG7E@K;>5-' ^!0j/8ً3SZKJҶ~I]ДǪպ_;rЄҝ%*/2K3/,%$.p  ",<6=C,GRHFA<5- '""mZ! "A\fPIMiϳ ɈR9$/ڗXD݇ٳ;O1Vglʽʝ ""t6 ULN qu!+3:*@A?\:3=+G!hw k F2[ 5E+!4H ] Y }2[<⻟%(̱94خy͍]xѴ;۸ %! Rl`o(&49?THZP&VXVPG@=1[%) /#*  h`+ݷթЖ͈g;4ՌXHݞݞۏA| ~ W:D Oc.e $,137|8632y,%\p%,<$"  K"Ϧ Aa Y% ҆iesDztL  O${+ u$*//s-h)/$j;%_  %3dMکAݒ:GVzlG8nݡ;BȿTy# !TU ti J"&('(a%H/ CG6 ]Bm5 ns^Ԕ# b0/քlAb/h1~:U:E<7@2(W)$#|H #-k5;>>%<6@0d(U!y>= hqY˖Ȥvz=m9k2F)  0A $P~3 mNݧΘhvozVbBСgϗݫ@G"-1 +^ MH!7(F-100-)j! " PbtB,:ֵ#Ǧ WϏٚ> .fۃf͐Ҝ Y'6+.-&,#(#6B+#Q)/48i:>84-%8DQx efɿTyr~bPc܋uƿ=YȆ+'3,8?CGDhA=67/'G fC u'.y5);>?>E:^4,%  sbԁEn=7-/҇װQq\bĥb2΋݉+,56-6V2k,%.J )^ ^["[.9DMSZWU PH>2'U t " FbH,siU еcٯnFtȰ.|0b$"++,;)($x |%1;DKuNMJCf:/v#" B w R _[&xz^TΚӝ7ڻۻ܏Iڮi?34d0$-!S)'g'$%Y' E $(0q8>B%DaC @91( .> B) |>dg}f;$NCщFބD_ؖ6ύ͓͡Ψ`"؝\""R"x `"w&+D27;r@C4D$B4>_8/#'I !57pCnz׽lF >I0mJɻƽ_ȦR1Av%&*l)'r#b  1<#!$L)-047{9;,>s@CE.IJLlHGCABD!U vkowFSd9bԝ@u`>ǿؤ>P؛`"Z}iڢ(\s Q$5/7:;92*!w)H0|xB"gN U]qtluyw { *.**'z(%"`,wUY^_];I"4wtt *B#}/ǜ81Β@ '+000j.z)u"G N!$-3B9<>=;5/w( }#)D4Ds>jB)ע\QEÔYعܲ x&\@4u3V p#,39<;=[BHIJ4HQD>%8/(!I~"'f-]39<{=;7 1Y(% el./<I5)ޮص{ʙǪĶD8Zĭ\>ݬLipZI-Q *NFҷB .39L=>R=d93+! Y>I'0\8*>A"BA>9d2+#?y A X KE7ڟM"xƣ0ɤ7u_) S5>6 cǮ;j% (#/o6@8`96_1*#X #  cIL!(.4k8O:";;85/(C!L9 L ~$ TήǞC {[ρ+2A6JpUْ;l֗h%*i13S4#2h.*"&M T  0 \8P#&)+*($.I ?J KVp ?.aq޶*9% 5 ˊQ5ҍ 5%Z8 }OW v!',121-0,(#:V}# !&L$d-W&6N] dp)x_˪ʲ˃Ѿ)'r9$tܱ8ʬ7_4ھQ\@$)[012^/>*$I' C2%1.5D VvqNDlxܳ֬,l$yaR+ ޿2;QHδCmR Q*L+.,:+&! O : x v,$!&*-/0/-D+L(l$!;Zm\;7mE#It ӥ#){ݘaz/ 1ڪӼͼ%Eĝ}U} $y ha$&$-t37:`(Ϻ،"r'X)A,|)B'!"p P K7!*38@3.)%"r}R.t@mӃZհsY U3UݳqѾ# EʬTܵ'#*6B9<<;u72-'R!.RW"'V-[27=O@BDCAy=]93-'!(K \ 7 H rKzpẋ-ƶؾ%ǖDYAV-FweɩɍҏaP"Z%g-./?-+c' "Ix ' JH` \3x%k)%,-m/0R0|.p,*'%" p0d A@m{noXӻ͠ǂ$kzߖa5}2ŀ\޼8=zI):3jzr<m ugh R',01579;<]94,-.&e{6 1 n ]yeWVa֗Ͱ ѳ볉Hq#7V5>O=_ˈZ߉;1r.X65/7y41,7)$1 ^mO$z({-8269R;;96"2+$4+ $s +(ۡgʲ81ܷL6}lt݌Ѯˎ'X{ $H*+u-+U*&d! ,` 26I! ',[2-6F9;{<:7U4z/(!a  ?A 4JufeV¿{ӍeFkLpP[ĠÅԖ1pMM!&'M*)(P'i%]"uV"'+/q5:@DFGhGE@:2'*!9Z 1 M4}N;bw̮~û^+ͲӣFc2'τ8ˀHԹ ]&@*-02h333P1A/j-*(&Q$?#$ &(.36y:|>AB~C~B,@< 760'@ #W ,n\ otзQRWL还  >u12ޝz6qAV--6>7m:8L72-'Y!A *, Kr~%*/h355f451z,.&x y*j db^f=Oߜ׈Їˇȋ%ЧD#>xcb_ЄЍ`KN$$I/36740s*#m4  } !)317;4-v%B ]w HgGf J#ԙs5G<C>ȸƥC=i ^z#c'-/10.X*%] 8 '&/8B@FKLTKG B`:1w)]!tS 3   : O_nMRiYIsulԷ>ֱلr7BCC@;26/{(j"#JXr~m=" a ׄvjz(uy@y@݊݅ܝ<؇Չ̊ĸ ÷zpv:. )-12321-($e!_% -3:BSHsLNPOaKE>6G.%o + u;^''Mϸ&Ǽ] aܹUEo6m~FRR^620;i=)C\ERFkEB >7,2v,/& %5o)J $'*,-[.#-(!?T' \KXyCѕ@qLKA_ `  ]\$2 !lf ! my(/DA-X߷jwXW]7Rxq5,) y{34Y2=0aQ$xL'$d. w81nH)\j PBG}"\G`N r;77+ 'YH F ~FvDAP:^[RgQ1jX M t;fq n k C Z  ,0A nU7G6`U8q|gJ w <G8U+W S 29I  3np{D6(k]rMTD sWJI0E>#b+l 8 ?$fH $ ]&~!St|21yuoE}2,8Sg%1 _^  s 7dv +\&;0Xi   A, @PI "5#0!1{S T >OWtI1" Cb-j85>a*$:>q!OsLaclxU_@ z6n   ->Xb K CA3#:@k9wzKb8$zu sC$> I$/#&6('P'&e$ I> ) .   a ;  q NJ0?e!NvH^Jv5tCz.t , w# p>!$P'&S%#$"yA tib + 5 )6A>;pp _H""S9" E mfH "-A" >U."!l/M $ P G 4QL)6vSTB2x]$[iQ[eef5$vz = p EuMm5Ib `+Dq-m 3QBP=7$O#mo}ZZII=_w "6!"";##! *JZ _ \Fdt,m5_]A`߻h[)55;.ZEoz1 |f F   # % '5 i/58' V  [8!wIF2 D0+y}o_mH \.|' CD5 ]d_yc# }Z} @ .@L P -  1 H 0g}=,w +Jc<)`Lq}VFB[ 1 )xb C) Q;{WO+7aa!W2R-M]p!6] ^ RKy[ !"!iFs iQ[2fD5<EEX LHBo*ERQB9#hXHcuwb_{._diUz= izA! uD, &% 1 i(j _ zq`WBF <   c k |[z rQu:\GyaSDE ?shV [dv8 M]. t)x1B lGb(' 7 giIORz.\uW 60'Y J 4 kGnL @qo mJJ H yD<3Nc4U H  @ P oIY3> V+_^i6@_ SOjE`-T[~ d x g x GiB).^p`Q~NCK:K& p j " ]`5HOxUu ) ' F _y^|=GORi NL1 ").0`0-)$xii#S)}. 35_53 0* % )  My(>6ԨͦwKDǚ+ږ+ڳέfȁf= K)i3'=@ELDvBg<5[-%8(!M(/:7=BpDyB>8=1(S!^e f np4 ӤtM+Y8ƀV^xݚ^9n&m ?i(՜K/%,/2/+m%:v!T+5?rHAORSQK-?\><6/n'IT _ 2j&6`p>55;k$}齤+hh*/44k2 /e)~#"`gI3x 'I/m59;R<:60)"Qq#Z(ϛ.ʝ^m$hb&ךD_B'{(3?5]7x51,n%'v;a(1b8^>BB@F;4 ,#3 n2 ""99]vԫ2ƌIVx՜L\ְs#( 6'%>/_/J1o/,x'z!*+#3+I3:V@B`B??92)!X  ! ?= צ3̍ɟOq.FցۛLZw݋ٗ}4rb ;)!)*(#%+ e& b'c&,)2Q5U65P2&- & B?d.CNH\ČX̨ҍ;o1'G؃3u >LB# $'&b$$ Dmq ZL^ V&+03\41,U&o ;w"U >amSL{%?z<޺ w~qՈڎކ`",.21Q0+3'N"ikZP!%*N/_23D30+% w- *_8݉  ľǡ͸M\d2{tInzؙfS&+5r7;972-("s &+0 577509+$Kj j 1n3&^imWѶPȡTƄJж,)k{(aMׁAVAAj?f:3,,# a  -ޓ  YVžL פm Xڅ\e&Ε؊SD y&\, .0H.+6&r!0$+*3:?ICyDiC?92*{#YE  REfJ1ۖ}˝jJ@ޤ˵x/ګ4!%%3&"vKP!5$+268i9 8+4.'6 Y { U Me|M\ϦhV4n^SO v`J<(^7eCg & ***&"/8B**")q.[2A431+v%/T4 y4(0 ˑ`΢Ӧex_w3vߔT߃ 6+*T2343=0,%h iJ-"',0E331-y' `z8x hq լ%&[ ^Ԯ۠ ߦۮqՑڳXO-$,5@6'7n5s1,&R"Q8""(I/6Y=BEuFD@%:r2J)  S V-s ,ξkx_׆A'݋W(Ȋ!(Ƭfl!2$(('$ |8!U( 07)?DFH@IhG5C5;@,CB8?r91)E!}Z :d ʱ"%ƕ\NԆ d? ۟10œE#,%$q&"$E %/6c=ACA=6J.F%B r v|,9(܊ԧε"ɲb@ދ(A,bt.EŐȑɃ՜:&>"!z"g7b s V"*Y27;@<:l5R.%~  V~ AX<ݧծ\˽h G$hDۓ!%$'$HK &Gi 3'Q-1 4%3/*Q"I Q? FRss+e@̬o>׭5x,b(G8 \'*064 4?38.)"").3g67 50O*"  Rb `m)M\*ǼHш__'ϚN-/a79j972*-%# u &,27_;<0;s705(4m'~֐ͨ45RJѴGff.H>ҿGԚֻ*+l69;9 61)!#]KC#1+29?B!CV@O;3|* _ ]Md[V<>t۳qʜA{Ҕ)3X4'׸Ѭͩ, .A.889O6h1E+#Zo'/7=wBDCk?9)0L&kz ?ou=vۂX*iƿ|S<PX$ߟІ`2+2;;gBBCA_=6.V% P|d!}wɗmE£ɵۇWٚu`@-+R/a(&'4103"0,'#r N! #(^/6=CHLM+LHA=9J0D)  eTWR޿ԋ!Ĭy)å@pۦ۬٦ձϦuõWЧK!)M)N+'v$q4&} &.5 dfJEA2M:># 57634&52'4.&b 9T!*&b7;ZR4yٚq DcQ5DTڦ֙бlH2 Jz >& .@ e$,4~%CWc*͐IVfb'<pBI'׃ֵkڠ!b!)+,112K10.)#t +O: C (^07 =?q@=>v:i5.&=JN )[ Zum *[,7u<` OrϮӹ׌93Vtߟܩa&LS̟ԥ!(C/47973-'p!z57!'O. 4c8(;;9F73.("  NHpݖg;9*Ka|bϞVǶŞc|ϊӭו'En,-Bߜۂ&ߎ^7)-]4n565}10,m'"t  j!%X)*++_+ )$?OM |5 ub* k\\.sU(6l 5=ۅY"ԳWߑہK* "@#!.= [ aek!b)Z1%7:2=;z84-'!Xb$(*3 V ,\?#TλϤхݬwwVjO Ӽ@j (l-g4O5v63/[(!0s \]#j(o1:BHuMQOaNEL=HDBo;(4n,$&- F 63 ;}bOu_.d0Mū-x񶯶t 4(ϩյ܋v&SLJܳҎ2ϣUբi/(8+9M@PGJJ~HC=6d/'!S$-*.479 ;:83,/)"@)->QSS"7ܤT؃խҪVl?Uʌ|m{=nm^cOA #0+/]568l53-(+!Ml) s}r ZWg $'-(W'e%!U^ z B S 6Jwt h> NTcڀk*?UU΄9.+||"#&h&[% q}n }#%+045}4U2.)$} td!>#ovkK<]U^aS˩tՔٛ;Ge2*G/Xÿb<~Gj%@-A1{42}0*% #,4_80(" K"k'+l/37 99%96 3V-'i!? "x|k݌|`#P%ΣyOɁz_&=6^߇ڠٰ٫jSo-/6g:o<=:82+-`%; [ "#{#"!5  L l> ۹Zd~3Y.h޿ؤ[0ѫ^DI;s܉=$H^*آ=u #"7 $ ~ Z X!'+?/23X4k43}2 1/|.,*(%%"aH`!7JМ24#nɰUYKNЂɝ,#µd,ѬC%l&I024w5|2/I+,'"5ct!$|(L,/36o:=?A@C?= ;7&2-['  fL9" \ZW"}-Is˰ܼĶ@SlfzԢjڒ|v'&02642-' y} kf: %+L1l57N86v3G.'R ?C D-V B&, a # ;_PXiZϡ'dȧ n N%О3W˦o՜?)s $)*,+0*&"9J* r5,H!-(/y7>CG*JH)E?8/' #  " 'd%(p*Vz{}ԭ)ӷIc[k%%qj6~ΟΦU>N'"r#%#M H@@v d*hr_q oU!)209">hAICC?:4-&y zE E6  nMC2 o]װЃg=ęÌ2ȈԮd=k_θɂ3Ta?pe+-r17z:=@BJDDr>A5#/+{+063@ DEG?ILOQS9VW`W9XNB60]'l'wG[ZC;V7پg·ݴŲwְ=ѱNЦCe= |(n| ;\JFZLKNuO]RRSSRKB?H3+$&I$.`) "Z%-# VYG:WʫDZH׏KniË;L {*b1p575A3-(#  z$*1e:ABJQTxVUPHi@=6+P"B/ =5̿;e"`J)ϣE¯5ʶw 2Q"(I+5,,)(%$c%U'+07?HOyTwV}WUOCH?c6-''!7 ,$ ~qM-lÙǂ˘ϥvҡϰI bK+ l"$k$"e'N.49=?>V:S5/) %!>9!+> g0m:MĻ͖D>zݕ؅ԎЪjϨ5D\(-F37595N3/*%#"["#a&4+&28a>BE FD>8z/' ,.|uOА#Yiuߤ۵ՐϪyǸ?- 39:@;8'4/+('n&'*d/5i;?C*EE,CN?:2+# _^ Kyzdgϰ 6H[̶I8԰ҕψˤƏJ:ĕ|F #F/03{1[.()!z|) 3AVA]=7(/$8$ (F de oСʸ_vA6/ގ؞ӋG׷{% )0)+o(%o +vo`")D1699D61 +D#2  y %Dm]FـA]ö2l6 kjj~B&)- /0.H,*)g&#"W!!##%~(,(13E5P5B2.n(R"9 dg !ũ0;SKT#m-uY)'٤ $r*01_3530-r)$"! "#G%(|,q0233l1.j*% !0%pkQr܈θ{"(-aЋڟޯߩ֌.gg˰  6%F*/.,'B"v^ 7"e*06898~51,("G=X{`݆ٯڡKB vք[М+WuT . PH5 oP&+&/p0.*$>]] ^mM63 |Ts"{OM"! &('T&D!J{<  #`')W(,#1 F[ܰZ; 4ՁлÝΟёHcJS#t '0+/.S-*$ 9~ 7&#-2S6=87b5~/'. NK`J{O#*s4 {x$ GQ9',g"#'**W+))(g)*,.y27=ACDB>8J1$)!I%q9 IϽa8ưL֬٣6s:rf¯Nc;ɶ K! < Kl$=*0v47^99.851@.+)*,.-8  5h"ԃqC!zzʜŽO?ºAV|" W| #).u11/+'"l2MgaqwS#X8lOڙ*k+[545y1,$+ &; "(,'.+'}"B w` 5]EޗJ.ܷ"t( Q4ރܶUzٝ$1!{3-3:78@70) r b #.x8AGCKtKZG@a8.A$   >S13Rtlű O{b] ύڣ  2,S]%.@8hAIP}TSOH>c3'B  5$ 6׎ˏɦ g/j^̵#gn A9 P | Bsg&-?3553/*%${*59   }]h<8TTf  v. <#ˌ m w < 2iaAO N[ q(.00@.D*$`V" s ! ) ymQPCo;.ƼA mO@ [ "#6 G7&<@#()($?, L27b?= /2_ڰ ֫E~7?N2J)-49981*O D g:&d07(=?Y>:4,$A J ^piסц̙&̥{5!7MksivC)غ {"}!@G (  $0<'E\KrOPL=F0=11'  J f [ xdx]_Ir{V.$v{ɱ y?"Oϳ#xr s o )17;=X:4- $NS " &*}BoGՆ͐_[nީWn%i 2[Tuݿ9 } A g\ 16 ,%+/;21X.'P   j 7ZҜ*řHzD  F^a}ˬVӝd'+00 0A-'"g#*0I699g72,#)W ;c0n߮jlKlY;2bߓYЍGbȕϬ.1@\BEC@:I2l) q!)I29?KCHC@;4f,%z] GXv\CKE#ĥŘͬܵ5Ҷ:ģ+̝հ(lm..4744/)Z ]\ - ])5PAXKSCXWS!M1C88+!  l wMRp֝Tep0ȑjA۔ؖI,̆ ##-+,(#4  E~ ,y6?GLMK G??:4( `( ?  ҰÛx"ٴbTxlܓ}ѣK+׌r4,!z!'!&' ".)D #+3: @CDC>7o/%o\ U|vͳY)B(ŗzTX۫oюʧ`ɖ%;., -"&/&%H"} Z "Q&L+06;C@BUCXA+I0479 =>gADFHK%MKLDlC;*>4uPd HOn{5usN"WdzdvH.'?\X.*ظוOD$09>AA?u8.S% 1uh2 Yڟ߯ n KJjd)XeZݛ6ߛ[rg m/++J*#(e)#v c!| RM_ҍ@d3UaVݾ97@Qo%C*A---+&e \ #+"3j83;<;83,%2[:< m*G%& <ښԽ zĪĄŏǃj;21N)Mبxоa2˟Μ "#,1?7:;962,&!a%p*/6<A"CPC$B>80'7< 3Hk?`+:%+ɜàȼ#ָHɝպߟX ߣ9aD56+-)9>.D|HII2G(C<6.b'!I#m).f4:=8><82(g}Us]q7!"σ2Ɲ)XdX-LCS<ܒ֜K>" ۖm$0u5;<><`93+"G2: F '+08>1BACA>$9=1)!jg \Ug^TZNLدSbL^Ȑctv0v@E PÓ̰ʙ '/Y4764]06)""P?  ~g 1"o)`05:;c;:7;3v-&r CuES 92>lO 6h"͇ʐjjȆޚA~6cBvӒGՈ$R%m+12 41V.(#6W " ? ;n+!%(*n+*9(#= ]S O` 4c9YWxۦٗ.w_uIUβ `"Y<Ga/ &!K).2:5X41-(i"w\Tn"$(-v [{BVކϘ )~ձ04X@V[`úms@÷+֦w{#).0a1Q2/*$Y ' jO W#,*6=}DI9LnLyKH`C3=6/E)#8)%#\ZG_oYOߥجъa׽j񵿶ռä{ Gj?˖Ƹ|ʏ֥$Q5_3:TչЛƘVnӌ hlxWjEggnهC%j+./-,'H"g$ # H?#K(,E0110,)(% /IG\"- 2EmdT7Ot܆حKӒԭ߄5%g}/};ǐ&);;0i j" -_>G n&/5Q;>?ٰǘ@b޺  '+X-8+y(#f 2 G)m3f=8Z4.')!E-v"(/P5:m?bCDEDIB=71?+$K?F  2 ( Q$vZLT`IԎʿ[ͿƄk9/ڃԿν,ʉ8-#ލ N!*~-01.#,7'"2; 0 o "(,/'1>10M/^-K)&R#c *mzH:o&d_2-fbDs"̃З79{ 234jlf)Viue!G* Rmv  !d(/4 9s<=$>=ʬÍ=;uY,#!,[3YTC>W֑U!c )2+.-s,)c%- |@@ 3\N R%=*.Y36\8N979X73l/*%%~ 5 p/26Ŀq0ΎQ3q4D=Cy٢ӺĽýDƠ&Jc v!T$''&)'%# Px ]@["&+)/36";>?BCTC%B?;5/'t c EvuW!3ڍo,ĥ?tIcgϴKۊߊܵUԤС6dHm0W#\"+,1Z1k210/\-J*(&F%$#Y$%o(q, 1_6:=@CCBAL>94T-_% v  `d A}V5[Ljf`iɟא47BܷW&ԶׇوF+?-738q:;8661L+$,v .  X /"([-$2N56_6410* $.O~ MVioN,ǵm.`*':ϯҝa۾ K%_'b24z9o75J0)g"um !A5. "+17<>>Q<8e2+#Y ,"0:'KV 5Dُz͊0Ǽ=U$GҦfCS (Rع҆iɅ|Ƣk-"R(L,2-.K-*&!  ev!(08?DHJI%Ee?8.&- p&MiR1cg/ܩ͐+ʼl8-ōҖIAHQg9Տ%֏ړU*+5|6:::|74 0 */$:s$)/3X789}8a5 1*$ Di:\q>GV>o٠9ɲ FEě.̿ѠN߰l? (a:2j)Ye=65>@ChD B=70(!ABA8> 92,[%Ht'J n 2 { D Xpü~ʼҢֹc-ށڅ״Іk7e@e˼- yu-2/3'9<@BDEGE>l5/,C,07@MCTD_FGJMpOEQTUTTCJ=3-L$+&?ng*]$C-˜^euζճ-߳P嶌(^ cV iY*c*]DKJNM5PPSSVTDT=REI<1("$8"." 2!s"i d^ ]FlO/V.YZYYSKCn8 .V$E:ObS dRYo̴żӿ T &C(0)Z(g&$8"!"%9)Z/6?HO2T VWTcNF=V44,r%  ! -{ \ ? <VȣoPĠǑogԆգn͘ȑíּ#\4 $%%k">: ] '.4k9q<> BREFFCo?92* #/%!62A`_?{kB4 ^˽3j˭Ԉ #/#073q0-C'6$ 73*A4_tHdm!(b+/0u1.>,(%}#!!!"%(,u145G52-'Q!d]A J ~th"޺ע Ƕ$v^,zҙDZJ 4&H-k357y52T/+&$#$%J(+/35H6P5j30+_&!Me4N^zRܳ̓bP`u$6DUܾɸ!P $9)-,g*%f $,3]8=;<:73.*$! .#W*=icY-R@ݢtZ*,|mrY  Z;m2o 4%* . /G-("bq i`)1X+vi_h$TuVK*SVO%!+(((&!V' V--gX!0%"'$0j ؼԋjҳקA #)S+,,++G+,-/25;AoDFFFxD=@9%2 *!MmPIi' ~ BKr'"ր̖*o߻|MF1"wLļ׶kvuD & 2&+1589:X;9a730o--- 0y12;Zua  m<խجyH ?EE#<3.Q^%Πsob y!b(j-//.9*a&  3 rz3Vwx+҇NB+-d6o5G631+$#; S^ST!'+B,h)q%ub=|)Z=W^d٣3ߠWS xږ(486{<9T81 *!U = &0w:LCLITLK~G@8V.#(~ t  @[ˉ-lĺQ{ɉ` C4۰پHΓ˚Mϟ׊prtOUR'O1:DLoS`V?UPH8?k3'^m [# Q n>VXˤʠ-r2mڝՃFպ` }LVn qj/0 gH'v.35533/*d$QP&+8^7)1!| qG5XHV{"c?ޙՌ̪轆ÓX n9u^c  (W..0/-(#H l_$4@   tzH,\ԼPtngJ ^## i9@R8}t#W'(&"K rXv_@ &xؒ0] pގMpL}K.mSgЦՈ?;,-.q7:;+9_2<*  U )S2n9M>Y@l>[:Z4+f$C 7  Vf+WAuթϴȘMۓUtHMš:gʱثGE2!!!uU5? D  /&3K>FL`PPLE<31&Xc l l `z~ 6}3SS1VvG2V֔ǪįaIKЦ>u B J!*28J<=94 , #4 8 x o(  IZgԋȂH zHә̼c~F+!e -A 0[A) .X%J+/T1u0,%4y ] .tqXDP#H<  I]$f=γM԰R(+)10/F,&! C>H#*O1k6J9 9P6b1I*!*:d lL %nTcor'bkNGJ&| ު֍Tęrɮe/I4ACEFC@%:1() Z O"T+4=;@CC@ :2<+$bD ~TMǏË0ܻm љi~ȗ./67464.m(/ B   +7 CLTXWXSALB6W*a jM W1 JyΐAA5D+yۺڭןlrc xm f"$,[++&w"a ;"-83AHDMMKF=}2 '>2fE oWC  ?۵o3?ОߛhIոѸt0%ߦ(FF "' &|&[!F;,7 Qu$,|4%;*@oC&DB%=36-@$ y ,"*q:y+)si„p[V@xS7|:D3qӵ T :# 'c&%"i  K "&+b17IB`EFIKM"KC9C:?#b۲g'# "Y&2:=?A>67-$$t''ki֓qbe@ Y^K Lݳt9 )0*[+s)#((("e$x O~ f?5BHl(Ԇq܃PD y4 @׆Λlʵ/A&+s-:.-*&#% . $,48;<;B82+$$v Us!C}:.YvȰVĔN\5ݝ߬,B-f#c2~˺P._Z '%-279::s9C61 ,%X!S:v# %3+07i=AkCyCAS>7/M&vi TW, g,.ny*6[`} Щ2^ kXBF)Vs --:S?QEHJIFB.<5-&]!.OG$q*/m5;t>f><_8%1|'R> 3(\yFhuh9"ΘcEÀ˹Ҏz0 }z=i6FUv'1{6N<82y* -g ^ FO A)1:h?BNCA%>380)( _ h 5 Zy! v~E]YQy]˷_nߘv&*MW/YDŽĸR̦$$"(04{764/o(N! 8 B 3 8'#*16:EJLL[KHB_<5.y(#'k[e#S o,-XeRx׏c]ߵٶ>zP:PpBiQƎ̍ '6>7FHkIZFB"<4,$VA%)7.;36K9C;U;~9]62t-m'}!KU  j XjFaoո`ȗơƅ$TyiWzs\Gl'+.|/-5,&x!3 ~sX c 7 N$5)-011/,($j b1ki)P;F"%Mup;-FӰ֛g2ҰTԕN .~%!Aj? ,QTT(t07R<.??i?~=94/+'$#!"F$$'n'O+'.#Bi1?97`љ1Ȅz){חݧ h؟Ӟ͛ƣ'OA˞"',!-4+'"65 %B  +%5]>E[K`OQQ|PMHyB0*%!z="eMhO iܮViO&ָʴyг9Йy ѽsgԣWj$Z-8;?U>3=n83+.' a|s}#*0Q6#;y@ DkE/FDA8=71*# - ( $' WAF\,1ˑߺ긂:sdz%S ٜӿ̀uϱ*R" "&,-j10.+&f!n KFn D#)-01x10[/-(%# <j,Er'>5l9pqr?s΀]wɃ+.d[BX֯>X>$h!{ ( Zfd")05:=>t>=<6;975F420.+G)%p# "O&O]<B2iiv+ЭyJ%16!xҿև٘Qڂv 7ܵƻv֩u "./32:0,d($c,G;_$d(-27AkCCBA=873c,;$ e jndUw/ܭ[ MnD#ʢٳT@; P/YI=.,48; ;8b6c0*$d9  j>#i).256U614p/)/#88* EfWOt Tş!֛2>_6ECENnhӻ^ >U%)259$775S/(G!gi uB 8$,,28<>>;7k1)!' Uazg G  [eخƋŀɵٶpU4$cҺ-ƬɃՔS#n)_,-.-u*&&!R $ z_"*19@jEOIJHD>6-%PZ 1is^kEx̑uVYZb޶ֶՌ eyݬ{&*=-5#7:::7.4b/T)_#gS$j%*04789,84F0)" 4o(:tzYM{ػg?F%NX+̥'=.ydA;u:75R?@D DA*w)(M&T! gt ^yKti_ԧ*b%_HaXwaXϿs @u>$q%;'# # )a U#9,`4U:>ABA=L81+l$>bo ( U  b)ݷX²ǛE=(ۭ%߿$u!yϡă?*6Ìr6. 7./49L=~@.C!E8FDk=4/+,1^9AvCDF$HKMOQTTUScH1< 2,r#y&imc3&Ԭ· YF2߼z6&gM3yc{1 )90H_JEKM^NVPQSTrTeTQG;/'"$""0 k" "T z bqաli5ƕʮͼҩݛǩ9θ}H5·2ٖ {!a.2708653-(#!!_#l( /6?GPV\YZXRJA6,"' fE#2سYa>NBFL:ͭKȓG“&ks]ʒA H &5(Q)'1&O$!!"o%)707@IUPoT)VVSPM[E<2%+j$ T "-   9ew~T ?+'!զThbzvȕi J^m%%%!?=g!(/T59<>y;6G1+:%7!plD) <3f1'Ν.c ܂y}fئՓѺkd܍Q W".2777T40*&$#c$D&=).5;_ADzFoFB<;5,$,>#^~J^6AǾŶoHh-ڟqi@$Ҥۣ |#~0?53;2;;L83J/+})'')M-29>BEGrF=C>80)4"~B1wvܳ6ŨE!zǿ̔И(ѐ4QĕvѻÉ͉D%w/0#3/, &!v C)\!+5 >]DDIJ3IEI@720(4$|<=:vs,65 3y/*%s!*x 5۲ iÃ&Ք F ܥ؅!'6һ־xJd$?*,,)O$5' i%-38|;R 5X;Ӹٺs3}!#)]+,,++A+[,-d/2~6o`T?Ic1٫Մ<.j *e/p655[0*!sN f"-(+,($R  =; 9aqL5gCӛԲ݋qvioެCڥRۡ6-37$<970(  '*2;gDILKF?6,r"{  m U(vz܇yʙœ7غUVcJg؃jW׊њRϞ#},"* {pv(2 4 s 'Cp{e").40Z/x,(+"E`Y&Ex Y /` ]j}yZS]ҙ9!bʩ !#"  }x@ )$'({&H"zH8*vZ'܃M1ְ]Q"m^D f ..8h:;$8q1(3t N.a YD o*~3@:>P@=9U3*# \KP9&ۛʡ)ƌ3ћ4Bpuźϊٻ*"!~!D { '7 :9~(4?GOMP*PKD;/Q%F ' 1 h P حї]Ԑݡz\R=DՏNJ;hOĪǞO{c_ Mm  N",3Y9<<$93*!{a \ E ,^)s M KN%݋Ӯ̜!RVrCx̊գޯx!4 vA o3BrV&,0Z1-0+$. ! J #4c%CƈFЄٍ: F 9+B-k)Y,~1J0/u+(&1!,z 4N<$+.26~985}0*) 7o Q200O%̅Ł~{6>tZ  Vݩp![o{$/m6+B}C9F"C@8\0:'X?NOT$,75<+ACBo? 914*#z %~5fCF tѕ˜QÌ`ʻΣؐڸIŷ'_K͕]"-g1743-B'fBI  f!f-9D/NUX#WRJ@;5(K} U{XLԜ ɄlƧ`ճHj|`jG&?͊٪bl"&,+A+&!QSL:  &$/u9^BIMMJ$EO<0i%qiI Z J K #=iKL´: R֥0}חԀѶb֊ר^$0'k&% y xP%-5;@CDA=<58,#b Y  x|/2Շ[J2lT2ZFD۾xe̡whʓ*! $'n&%!, K #',<27<@BB?:3*#\X =$u0H #Aˣb7ڗhX)B>,c`&Ygx$),5-)&f"|i,#p(,1b6:=@?BE7GILMICBG:A@&) f - lnjZ`t]LJƖ5sIu- @TqׅrV *(3g;?A=5,," 0-׺ + 85,tKtGh Vݍ44 1*+((/( I a0Hz!(\ҁҖCu h& ܣָˋз˗ \~!!&+-Y.m-@**%/n r %-4s9; &+1}8.>BC^CA=6N.$  L=p #·g{aoоFApGevޤ7o-/X>><7/&?|B [ <dcpi@̥ӱ H?ߐ҈,ۼNC)F279;+81#)Jl  Y!"^*2:@B#CAAi=07.'a?  -"Voi܉uƑJZO%VnQjvPqvƭ-Bˌb|$)11576I4.v'2 b  h?}$+^2~7);؛M '-i2a331,'"` j & I"')K+Y+(*5'5"'$ =  /=OVW~ ݧQ6IoPiu\͆=0\<@e( Qv m1^%&$ =b$H+-03530,& x6<@$$,&$_My@PTk |+Hkϒnjf yډl$8>xTpcĈ* ]x$F,021.)="Y' F G|&/8?FKL~L KfGAu;4-'b"0wMY"}{ktq֚Pȁ ۵(Yo\ٟ&;[EУʥŢƕ&ӳ4 )A8B?FH?IEaA;3+#cVM` %:*.3&79k;'; 951,& tG |BIs LO#;QNlqιQ0yEEW_eLnЎڣٰ(+q/ /-+3& V c m D%)#. 111`/+(#$5x/sm= oW1P=o Z(w gpԈRܟ(4WĻOĔ-ηs-!UM|k =UB )i17/b*&$"!h"m$I%''m+'/(VIbQ3r>RldǬ}ɠ̝ӉGޱgҘ̩Kd@Կʲ*,$(]-,*,'!c  N",6?F LOQQ?PjMHAM;Q5.)+%0!$W5 j, p{0iQܼՀϏfW+P;"ּBZז܇ ݚ45P\D,z$/8<7437-&[($+17;3ACDE)FVDAGv>=<:H975 4h20-+(%"!N"& sO/Wx.ϻȹ0򼫿K׿٣+غ9 8L4޵2E̯ހ !$C/0M4!2/,'Z#9I $()e.3N8<@DnGHtIH1FC?|:4/ (C!jN( @ [ c ,hm-4-S™ҵײ䲻"a5bހ8SߔחJznj9>ʼ.ރ]0Z068*984^17+&!!a$p3%).26:P<<;}83~.L'-D 0UN p2@nG#\؏˿2  },X@_ZGtόzM-C׼<W5aùy~BB! &'c()V'C%R"(  6&} #@(I,h0*4M8R0:g4-%&8* vhQ׃/K#Gx7}-V5ׂ_,Tuӄϖ9O݇d"a%,$.#2w12s10W/,)((F&=%$$$x&)-27;>zACCBA=<8F2V+", ^* RzX" gHx{LŽ"μʷYFڔՃӔԟ/2r|/,K68u;:85/)8#' 1T S;$*.R356363.(*"L DuDN  vl;1}?<vȭ˷ו~xQF8GvUxY.ٷ4>$+M3E6x964\.'. :]+ k> ;e%9-3G9<>C>y;7a0( + o{L h iB3~*̧ȥƂQ[lڌ  #"c:tYȹƂǔ$Z#X*c,-j.,)W%c 246 T H#+3:]AFIJHC=5V,$V }'e R{*i@~˹JٻAH$.a$ߑ`ٵwլ"<fO&$).57;:2:63.y("jc %&+<1588#97H4y/( " dsH%4UGoݱѴ̉ñxȆ͘wo<hyIa#Q f7r6@@DCpA;5h.|&ICb  >q{ geO!j%8)2*])(%  MoVWs],0=٭ԙEZ֥ڑ A#((#./0/;-)%!axh")0}7N>[EJMO5PMHiB:2*3"MI =2pdlmbܿٻyֶpOΉ3ڑߏNUQh5ݲ?ޭ\1//7=@CE|EB?w:4.k)#K!N"%)+-././+% 4i)Qe8T8B@f 7 % x  &K\97; #t  nzm=Yw+O:gxj!`]2UAr;d$K  Tm! M W7GS }fA lNAO futU-7AzfCNm+MV,% 7@Iby  1PeE%HFRjEOHoH Yc{  Y U  d b OGOc(Z;E8wu {]%L & p~_Hbuy* ( l;++C5 muN7%DIrFCb`)?'FN8H]u6 Bt0~{pu fqrxX$+SMTOXFbV~n"|aIToh,_{UG 3`^ ydPeL5O 7M3-~h @S7M5iN T$$ \~\=mC pvA/# # % N]tK0D8D !G7n=2~:!=KfqZ:FR_: 4&  o<j}Zp f l4bo &_zU'#guvq 7+Ni~%W{&"78(qKNb I  r=SR 0=:x \|)$KL,znF`1k&eK!X57z#'**(T'%"D0P {` / h A 7 Pn'QK_Wx2c/X4 1 I .\n. /> #&%#!N| >2YnbUii iU;+ Gc@JPy (d@bpWfjI2`seL09 b= 4f g\[ 0X8q$iD~ nLS"!?5xk C{ !z %'''&%u"  A N { "{3x O HI19Dm,(1EWqHXq5l & e ? >P] ;tl#&O'g&v%# ' d"b n WEV 8Q;>:ew-7Y-d<K o :  0 c*n@.f 2~td iV4  <2:80U.EqwS5g09TSs=O0 9G M6x v WnD8 h {v+*ZvZU:YW@8aD~5] b=3)ckEgh<BS;`;` l3 @"" P(J~ 7je _)4X+xb. djvJ^<[x8dap o Q S ^+@ F-&*4 x j[%uCAQ^{AX[+'bn=fk~T ir|x>v uQ  ^ ( ?4N h {yip+ @O-<e!;qyO!n/P)/ EJeHkpA >YOQ6O/+ 4^[ "x,o ma?J;G7._F)D<: 6s[~ S %7 ${@u v L+'-:>w%&nh VRF_X2G4 ;%m \ DWbFDT s_}z% { .vZ}rSY P E % i~k1{su+'oZ.SHRiO{}I Aai >jx^n3.9b& /EmapvCrA"h'k)hY h8}40  sR b - p. `g@( X/K~jN^6!obrRyNISU M*U&h H^+ x N  s $hL!%<@Md1QKyFum_lzZ  hhIL +"7,yzA@i>H5, +\RiX{~4M B@skkO 13mvcNh GdH b 9I+ ^ ` X 4 #[d :N/]),{iPT*a\Tj9u %4 u2IBynSN,*(ktx$$fE:Go3?U@ < dd<B w{Ekl)~>P vyh#J~72 ? S G J =H:Nqa;!2\WO6fh[: [FfM2o -u*d.&oP<^NjH{H($;xa EuuOD^)t.!^n V IHU_VAKFfHhJIldoGiFHxtI_VzYwLQnVF7[]mQHRevt( f||7 $N c } 8 O g Z 2+-Kasv)0^Wi b="Y(oC,, .CI n W[.R  _ } ?P0 ]d'cd9{m(%/"7CO\J[m}E  i 5 6 " l Y 0 SrIMd: knWUBj2ANR/!uz=wk+%4#{X{2p*6SRW_?Gzr N}*R: e"wN}zeO#  n Y }K>8U%j CYq2f7Ox#Brfh\o@! > U P C ' (  K ] U  [  S G 7?/Zfn5ATNAM|xlMbyWH V    z   F d | L o T].y&`gO t=,e,i.`   ( ( O & S &  H ?D]C&H5Lua-.Hu6` 9J BY'QA F[~*R,h=Grx,g6Tv=d4NlP/=dd_ t r<\N(]}i $& 5R~FDQi#lR NVY:i'j yF! u. _   A BK:Es?5gOw`<X ~$WM6F^!D"MKGa4Zj3" $ C  * lkSwN~M<7 TgM($`)&I &q;"d}T\E Hum r  ~Fk]]mlgLECp4`Uh?t^*oC'U!Tn5uv2be`YA{-`-F"JDP,&Y=Z]  Bz |zy(6+W`9`*)NWQlx,C_jqk9 XO(5`[}!-F!gVi5*y{}vFeAE 'B4{>T@8D;?1g[*65dko" 'xNy$/ :Xs("gWQ',70{ /`%wWV=Sq+N6#: &<=MN?GDE?5a/:\m'D;gYvBg] J[4nMKSg(fR"mwnC-x$ $EYS`IW]Jni0u&]O  i;8  09^xU%{w< Y1ct(J!uJ[ 6vu? #T0eLpc&uJ"]4 IO 'VhaXO`T /}|0L(IDtm_h;tZ#  m]E*'K}$yG r+#(fcPVKCMvKh T3$CH |S :U{ejzy1VZ#h9jAT(mB5tX~*]F=SIs._yu_XA1 LlgXHj' i:ufD>0PM^+qQ K4z ;VWk`Z7 ApNp~ 'Cr`7tV=C@To{Xy8]6O[2C>1ubcr'ctoehfn~*Y$2QPc]J pN 9UzK^S*.XiLLp  )'5M~%<LoiJ3,+8W{cl$b^~1C Q{P([bs7QQL^^}] VKIuWC/1-If4e")2445C`v#U@go$M%Od|o>f0m {qfgo]mq;j%^_'k+9=ELEbf{/d}eH6 IcuWAHF |yrocQ<(>O~Kr=eUVo$x 1??==(90FHM_IL'wZu9=n6`4 eV0;X*`w.U R5y$[guaPG#nKs:Si1r8-   D hCIa (a[#DVn|sr_H& qDd2'i9f#yK{gWH\acwotzs}v~u~9jSx &3F^tK EuyY&tU/ |y|`h:. ^iD,'37PMWPV=7#,Mj#>bk '@\y!6Zi~vaV43! ")*,8!q5 ~L> }lEstn{G\/Rn5fKff\:*$"(17=NNTVT?<g4vXOSQda^dRJ8nI;85ETc!=KK[QQKTNRYVu *T %9HELD1yI-@WN$](Xlq~R4(  tdM?0(  %3Gco -<Onntn{gh8zhrim{q}vsoe^WA2 $=Rq ?GZgm|{}ti\TMPIVYeu}z[9(~xyvfgdYZMWTRh_u  ";DQglvy{qZU.*sgRQBCBMRgu"!29%=1$!3';AR_fzypnYN=-{lbHG6533JLi ,:86-"5JLhbeukrrlnfa`VTNCA<1) ~kvcmvnwoePXMTbi$  1"100+,!    wl][PZdi|}rwwx  4*:K;T;73gXYGOQ[kxvyvrvy !&)3,9&4!# xz|hyu  ")x{mmnrv*53HEAL<=<21/*#2$50,8%2#yyuuwxy|  "%3(16*6)('"     %&2*50%<.)/ &!  {v}{ .2:<DC?J9=6*2) {~$/1;5@;5:((  ',")      %"'.'6,++#'    )%044D7C:<72,*# wy|tzy{y#"&'''"$'#/+-94B<FIDJFDB;40'{$$%#!$.&)2+4096.<*/. ! !(#%/)"!"'"$4%460>=;?792++zwrzmzx -&)/"-!##& $$)'-,,+3, '         qTox/audio/ToxOutgoingCall.s16le.pcm000066400000000000000000005726201415623743500176540ustar00rootroot00000000000000 Hb.eWcl D w-T5  "| " K B3'z6{I nS "$(*,c.G25 5/+ )!&#:  H mR$fח-Nܴ!ߩ "FMx XhAeݤvHYzɥ̢1Ù{99͔Ȥw4ТtUn O A  dKP ޷ۂsA=(ÚTѵ4 G "(.4:@FLoRX]yb_7WPMKHD@;78r40o,M('$J id= 7)I#'g+f/w3o7Z;.?BFdJNQ_UXT\K`Q^U;LYE@D<6$0)# {U;P Ľ~ځ7c-f ldI:/31̡ǘ¡ֳ4xҠȗxHΗHۦ<*Xێ g. '?Lq f|wߖܘ٣ְ >C:oA9 "_(0.39m?4EJPU \/`H\TNKIF9C>:973/ ,( $ k 41 {7#'b+/26:C>AmEHLOcSV3ZO][?SoJC>c:5.("- "JV}sܕкʳ')ևBo߾o2(/B@EK]u )j-Z49ZÉι|׫HTH͔sG #,ǥvpքۏVQ iVyr3Q!\K{ݭXҍu[ _,kT V "'Q-2}8 >CINSY\}Y SRMIG2EA=9X62`/+'$: bzyy_ Sm8 Y pt#4'*N.15G9<.@C GgJMP4TWZ+XPdHA<83-'%"n f;|݄{Fuٴ۹޹}TD0>Ehl  &S1pӣ'y^Dz*ŠY̩ ýǩkf]]QKVk , '1C~%wߤܱ*M=!\%A 1i!&x,27=KCHlNSY\WZTO`LXJ!H.EAF>:74=0u,($+!*}' 77\D!$(z+.|259fA/ELH`KONQWU:TMF@Z<834B/)$ZV EMѢ"̟ϻm֢׿dڒW [pOgjwjUC hS,Tٗ<̻ȍ{0^)?­xEnM̽Ւwf -Q) hi"K{T' (D޷ٍ9 D8:3*4 [f`c r%*+05;@`FKQUTdP LkIGJFDpA>;9630-)e&"lW\ c 5/[UTD !#&(+.j1b4I7:=ADGJNQPKYE?;73/*%;!JYf ?;]a NϏz{הb} V"&k ?{ f)#=`У̛ȔRֻW߲z+F]*}մQ 6۶߆8 P %xQ;`wQI߃ְٓOܩ!A 6"o','27ADGXK7NrMIC=v951-G)$W N,} I/BםђNڀMRݰVIt|#gnSv%ݕ~w G*과[ۡ| íU:@AȬ:ҫ֡ڹrwKVp7O{GE m_sߡ@! w $(-27;97o5230w.+-)7&"[7|@ b[ "%')+-0*2b46%9;x>;ACFILDKFEA;P7K3Y/2+&"^K  ( Z Jܮݵ޶UPi1.{CL޴ڒv4ʖP#e ƤWįdaMF4̎qPBuT[eK}vaBf+_I3.v w~#?(,1P6";@!EhJMMMJ|GDB>A?=;97631/.-*'$!<  o![#%'c)L+>-+/ 13%5`79\<>wADFZIHD!?9Z5K1n-y)Z%(!4H8  y4Kg܌ݬެ 3h |#PvDi}2s۲أՀIőPd"{٥Ȥ n(vZEK™ʼnȯ^arH2@8&>EF7; BS4Y!\K.LZ D 8GWTs!%)u.37<3AEJMM-K)HEZCA8@>=U;97531L/,B*F',$ BE K" $%'\) +,.q0<2457;:<>=AsCFJH0GTC>8040],( %!6%  v |Y4ی-Rׄـ޺LF1-r,|O5Nz݂^V&Qɬ)xZL UOiGZÍ7͗)ף}3O#|%m2wߘ?Io6 0\!@&*V/48=lB3GFLOOMJ7HFD3CAf@><:8^641/D,0)%"Odo!{##%&s(k*q,p.=0%246N8:eD|ߺٽրZ8ʦ]J6 ܤ -o(XtwVq=uo4hZVHR9G3oQr߀߃>8zx "&]++05:>C&IpLMKIGEDBA@;?~=;97652/- *&q#YtHs!"B$%h'K)K+'-/61z358:=?/BDGWIIiFmB>951I.*j'#fM K;lTlߚc G~sgnߣ]" N4 +;${(,16;@UEuHIHFEQCAx@i?[>.=;9763r1/,* '#h EHY "#U%&(d*&,-/13579X<>bACEWECf?A; 72A/+(%%!Z  f ;3-TNMmG7-_;N2\~di$ރۦخռϨuyzf+޵1[{e_fxe0#,͑yڦޙc>Cf&V$T"=he R#'+W0V5?:?CF'HGFEkCB@ @D?P>=;:86p4j2s0+.+`($1!}1Z "-$Z%e&'(S*+!-.L01+346>9;^>@BtBz@V=951V.'+ (%!.$ 7(`ݫZ%%2:+{iK03v:E`6x<ڹ"Ջ7x#pcһ:s!˭-Ҳ::8nrFdԯ)߱KqS.3Byqjh!iE/78s 0G*!o%).37=`=<;:?97631/-?+@($!)3IW!+#W$i%u&'(!*+5-.0/23k57R:<?@@O? ^"&_m=]zcG#Y Vt =$N(,1o6:;?CDNEDCwBUAq@?>C>K=E<;9 86&4620-*'#U KlH) "$&%%&'2(K)i*+-.0134E79?@>loA CeCBAq@?>< < ;9876533T1t/t-U+(%#>`!#%z&'(*++-o./[123Q578:<==\<96>3/&,(%e"".L jUt;Pv5\;W/O9( UPՇZ&ϔ3Ǟ%Ͽ"2:>Ա|~ʵYκ8} \ƻʆ~ЖӬ:cH~_wgh[fe J Y  $-L#&*-159=c@ B~BBIA7@?>=J@@A@?/?z>=I=<;;?:<97s64K31/- +n(%"!] S!q"#$%&a'(()+**+--./0G23o5=7t8875L3z0-*'$2"^~b |4J1w{:e`dL!9jK~HWBChtׇ՗ӳѣjF g¯ t)Ÿd5zlr|ɵ8И{Krߏ}|}E)_z+=>P)H C >"&)J-01>5"97n???>m>=]=<[<;:>:Y9+86W531/-9+~(%"  !"#L$$x%&&9''(z)q*|+,-.0"23*554c301.+(?&# +^M ]6'q$).S 2ߥ߂cMt?~w%sg>Q߹>xԙҘЈcǪSBvhǶe? {{ƅȧeiI Z CL@?f"}L  g. a!$'2+.259<>? @?R?>>==j<;::97{64%3P1u/z-H+('&#! !"l#<$$t% &&-''H(()*+,m-o./0G2K332_1I/,)<'$!G ($qr\ [ߏ5ޕޔ޺߮]b:EC4Oބ(l׾{ҫʣȥơg$ n2{q *B8#l&),04M8g;=6??@??;??>X>=3=<;:9b8642 1.7,H)&}$#{""##q$$X%%&&&)''((f)k*d+=,-@./*1P22O210/,)*'%" 5 & [`%lGn݀3gH~\1ۅ ؄}dйʿl޽%ӹ'Kݿsw)ǺɭХ҅xWYޘE+V)SW~8Zw4Y  \!Y_ t#|&f)q,/p369<=>>X>>==6==<<,<;;H:g9886 5i31/-*k(t&7%$$%%(&&&'')'P'k''''(/))p**+,?.'/l/.-+)'$7"KuW 96w\K0s8My1ݨEw1C|xEޖڀ*ՇK ҽu'l:Rý ڻer¾s<ŜȜJ(%pԬ;(ߴ r:o(aFP\YX>CPP ]T!ae X PZroE !#u&g),G036k99;K<<<=9=O========v~-;-ʼn$ɵA[(G NN5MB|N`zZU`*=b[  Kg;~!:$&),I/l134444G5567q778P8Y8$87636l54S31/.a,'+d*$*A***z**)(( (j'&B&%%%%R%:%j%%U&&&&%#" qn R TeQWc<_"wNVޖ ݣ7ۿt݉ݜ@i!۸@L֓9r]ҺKMYw͒q%ʢ%;~Xhx]ɛjaV,5|үԄB;k L-S jn8bS% 7A]*!N N  ~`R "$&_)+=.-01223-3n33{44>5j55.6u66L65R544731a0.[-O,+e+++++U+*A*)U)('&D&%z%%$C$$3$s$$y$#"S!b_m>Q( M > m .to2j1>ޔ_ۆ}'gbه$3Ք(oў>9vϺn͚̆R%ȕrǼʞS̸ͪΛ_ ^UؗڕJ|66UljC:1q} BCV9v- "j$o&(*%-.]0N112%2233=444X555 6655S54+4&310>/<.-X-_---W-,B,++x*)(2('&H&% %$1$$$($#i#j" 'Gzq cI  !!@].)tb'uA*%7Xۛڻ0"ڸ֦չԝ)Ӥ wѨ)Sh- Bu53̙ ͦMΙ_Zb҈֝N>ۋ ZH7d|Pp`]7[?t 6 IJ9\5 "$&(*:,K-&..j/0012_334P556Y6x66l6:6554210//2/..:.-,+**%),(V'}&%$#?#"!!!!_! `[TGA 2cpQ: m{/3ߒuWc}U"Ljc\ؚ׺8ԀKԬ^x+y*ѷ7ЎϘΓ͟(̘|ʹ}ϫZ#6wtKۇAAW&ps 4+]N1V(  R\_jP#tA{!c#%'n)*,--.W/B0E1V2.33p44Z5556655_543h2<1=0n/.f. .-,+,%+3*6)U(}'&%$#1#l"! N IJa"""6d Q 0L;S:PT2^F[@9:hھ=+]Z٠kכՙeJԒ/Ӈ9ҍ6LѨ'̣͡/P΃YЮѓҤӯ8׳^4ri;xtJ& /y= , fl``z[o@\ "#&K()*+,../M01152<344]55556$6 65r54)431w0T/.-E-,:,+*)(y'&%$+$]#"! @  qwSV.& sO +  >'CpOr/Gݬܫ^ظد؅kժՔՋՓmLԕ`&ӥjӲ5rьЫM@TtеѣaB"Qح-@,4/Cku[?RLQ -  5ebB !#%'()*+,-./01253334Z4y44c4+433=21/.+.-,L,+*)('k&r%$#","N!n ( @INXKQ| N [ F_P} K<lQ#FۚLx اׄ׈פו\֞f'կiյ7ԆӭF !=`cOJa]ӮpwיBZ_- ^94j_|  $ > +Z@Y<h08 !#%&'( *++ -).O/W0 11 2W222A3b3l3D32210/.H.-"-u,++*('&%$I$#"! 'ch2^^m^Pa ' u . wYb:Ie&o@jߺ݌܍ۨWخ)؉qwAPrՈxCԣԂԦԯԶԪԃUg}|Ң:rӆbGjӺTշ\ւC#Cۚ?hWvbui#?{SMg ` s9.BIY2  O"#$%&'()**,-.//000V11U22222y210//N.--S,+*})(&8%$F#~"! ]@4Mnu4d 2m+A j ` D B~YsXaSm5)P۪cZeQڿl/طِRطض؉9ؔ1֭x]I@G8բ8NԼՆ,׾N ܁@*DV}66\y N}Ir<~s   T12#ksU:H[b !(#?$Z%[&e'`()*?,-i..D///R00"1P1X1?10B0Y/w.--q,++\*)j('%t$h#""X!h V4,9JB^ %fe=^' j @ B 7 Tw6@H9@rbCNڜڂ@W؅" U،رؒ:% S׿5ռվբdVxը\y؛VGQܟ89{ms' YT; `](8}nk z!"#$%&'()B+k,T--T...s//a0000g0//C.-,y,+5+*)(y';&%$I#"!$! B6.f*I _r4>LyTIlۭ{ۯUٯ|w٬ٮRظشؿؚ6؝HՔrWJ6ԆecԊ՟Ybc o%ߠ76Bzr\e5u xD s ? [ h (U?VO; !"0$Q%|&'-)*+,L--:..O//01@1,100//W.-,W,+*&*K)*(&%$#"5"v! m8w&B'@[w   2 3 E8P J_OHj"q߶޵(ܽ܈5ېU Sai4ڲګXۄۥn#~טL֧g֛H7;ՀZ֟J׵\Tڥ_:jsL]U_i[& c  } ; +k;x 4"_#$%H'()*}++,H-../1000<0z/.-N-,',+*&*V)C()'&%*$Y#"! pE!}:&@%BGS] > G@:^8{"VTqDa|bރO޺Oz$ہqۃ۔ۅh=G~ۯTڞ؊6׹ׁc4֘XVh֒ MiׯqXٽIO߰ Y+.?Bf !"#&%Z&l'S(3))*m+Q,&-..@/{//.-`-,x,,}+*[*)('&%0%`$#"! `*E@+:te5jI y \ 4in+T*Y7sDߤNwݐ'܄gn^7ی]PhۚNۏ/ٷhךf2{dd֌־ Z׎׼nGiLsL]Z:^ YU+a Z @ 2 - 1-9'~G g!"#$-&N'(()&**X+,,m-.g.p..Z-,+R+*_*)<)(' '&T%$$p#""$!. !  ZE%|2*gf\b m $ z,j?l#]wf$߹j4yo% %J{VײוmH4W׬qz\ڼ/۶w܍q s> Bk5f(iV. M  ! & 7 BQI*|Fe|='MNJ O!T"I#@$5%%& '''3(()))G**\*)($(k'&&&%%}$#4#"!!!  } 6PwX?RMYjUbgM3   .C6@'EH+Td@|'Lqݩݕݘ݌}[܃ٍ۠N. خؕؕsf]Aܱ:u0u[[+9Sb*}x8&nAVS W R ` x tR!]W ! "# $$%&& ']''(((V))d)(&(Z'& &%[%$`$#(#"!O! W d{ZQ:,z  W  { vE{kNR6Q O9ds&޽މPd|@ٸفCضتJٝڣ+ۦ܆;[ ;^+}$[y&oFY|OT   zPA[qq !"O##$%%%&{&&R''''o'& &g%$$?$#h#"T"!! : a:#nzjlqCV0u.QcktP% , E Q  <#kGt5@N5w\0eX5,58߯߹ߺߖ;ߖݎ$۽ۢۀxn}۫VܾB݊6iP>c ?UL?BUlxP#{,|nc)  2 Z a)D _6  S!!!)""#($$$v$$#'#"""^""!e! m  ^ t"~-?I_1e :  wJnJ Q}{s}>rvQ"|ޅ-ߦ bifo uG|MZ;/|E{cT C H F g\E4 *6N t!B""r###J#"u"'""!!a!! @ G<6Aa!c+i   ~ ?>}j2{=(A)R t?^f(0bg{J߯ߧmIB(<{& HF3O) |]]OAJ[Q5h 9h6*b < v3%1XW'tb P!!+";""!!e![!c!N!! Y g)~8u`Rx0Fr  6 e xN0)`n/Sfi).A.4H5 " -Ox( # ( E CwblBKPJMoo?>dUVlzs~  4A'|.|:ui" Y K Cf.~>*lr>mXU "+1@CfV0 [ݤݛݚ݊ݔM޻E};;_mFJI)zezl"6n $^HHIFr}Q? M/4Cf 9BNmyj&p7/[|L(C2{kUCds ? a=+;ro$!0!7XD:9<9<86Sd)&([_ޮ{Z 1`.wNA@%n1I/us/YITbM | C "8pZ ~1`~}lUCxj -'M*>HRgGL5Uxl9=:Sux^`  O r bU*q =$ a=ey'7"z"I O]u!DM߆5N\eCg[\6)J?wA^B'JReK! !5X8 ` (  Q5_*O5R@ON.S10;;FuX2|"EOM~}e0/  [ I}v ^=Xc9ecq.Gh(fewM35XZiD:sVPWE4pCoWLH5t1|d p Y X h<u Y5^_DDa4|=q!yV q(o~Y'  u ) Q sqZ@GuOeH6'Si->]N6WF6{_bf2.7jEf>YD;~LuoNj0# WM[jL l ` f SQ1i>Hrzuc; H 7R;% @1w | |uO!Q{!UdWK, Q 3OMB7K~~_Je#stj^ se  5fW6)yInUHJd8%YREL w@h  p { p i @IAbqxo[:#!HLq!{-fW\g_Q&mw z J  ]%/x8U_jq$ fVy ^rQ &:&ROao Y%!2um8\ J 7KQ*r4 m J 8:XbujVZcSE`^[MKrNfM=KXC~(@*LLc    F}$%x: KNkly#T3Wv!(})L=<f*.Qw\T_ W"'"*<{dY1x?A:mLLFGWP==1 ^  } ^ I #9{!c A]!a^Lj&e U . 5 s=  $3d>|__[&k+ xE,f/i &"s*kKA58` "4Q$[^_ [ f&RTG , U  g  S 0e v Z$Y+~<{ 7B{ C   CvIg7j*un{Y&Sg2J$rU*0rVVTHICJnTy+^ +2t> ]?~7N k)E@ G  V  M l x m P 7  " u    < 9E7E(ot]A8@Titg/| 6 U 7iEC{3lSGQV^_DoNkn R FnFh A".LZJ&48 U$W+[5?i[J~n}*m 1 m   0 F .  h X  T q z j R A . 2 m # s 6 r 9J5!7fNl^0  / T h "*v'['dFA7Q]tsAM>x'_W:Uf/Ra<?d4#TW0ZE`IB? f0-LqXeaWcxw7#m    $ )    [  ~ K Q     % < y J  q ~;/ :* J  5 C _j^ @j`{G&.Sr\l4L5/m8^~9zAOO C uUsF#4[qA||kaI) M~QM?JqI }8)fh 8 x z f Q  , ZIk+ _ G , 1 fq wc  r  3U\L,TL246/0Jx.`v>_G5vtq F[|%Ldu#!^LC3{\7ljRo e% )]M]:e' 5CG   $    p P  @?  \ K  #}_ 2 ' ^ BW\/r mD' Z {#\rxM&(RZ !k>%*k/ S%%S|BYteP1x:*ZYNz i B?<w r9 z  8l'2B0 N H .  8G+* & A   =dZ],{W9,Z+R^7h*ccdf uE9lE?iT .Y s Cbzsh^Qei=ahq$_8 $CH%!+d]?hTT  E7"W2p:    r 4 7  v  < z  y ]<ztpT?m3NX/z\#\B9z-Td>LzBkO#[KqHt<A`wzaY_cZXy?%m={/|07gFR: | k   # - I t z q K  l F  Iy`XWjm t]8=2W{{W*OM^ft -u0\gH-FY"3 N@s0_6XSS5X*th`2uD .7Uw^aj~vfj})N%UyI   J k   `  n f ]6pz`dR=h-;+Ed!btD/lE)YgTOrD;:5DA[obqxOq=4/>C5=F-u ghHtD-flalBw :?;LSQPy0z}%U)ep\ W  F z x f F  f W $\ckp/xtpbZ&)JVTJRS`O}Hw#D 8k6\nR=)+Y+Q}b;?v|9^C Iy$g9gB3'p)pZ$yM,v8{BBwT  Q | g n [ B  c  dZ:2T_]qdYXODf] KszU`T)0f[mWtA!Di{/fO|0^3*fv ^)w$x7d<@_9o:~vN/(("7f"sE;rU ' , ) U lw`sDG?#x)GaF}vP>Jp,mac'08c  4F]D2 e!U W+t6ve/qDQR[R@LUh<~ z B > ] w p i U 9  jgE6,OM_bm-W[6cF3>pc^F'#)DmN:B+ 4X|}pR@{Z =} U K[a? J$j'A=5(fe+Gcg&f> z^)3#Zs#NO'!vI O(iU[`]cw9vlPN7tLvtnT$zNUE@'5_%y; x )bs@_ $)S%ck/?r~:|B^3V.(8,6UY^j| +[lE9Qjeg|c\RAa-|{blu2{b9gr)jSD?.c3[27=?}8bB9/-#;f ^o4nvDNm TP560$kA3bUmiu$V6 RWd^ggdTD<~\'e)zK_G"{zVE7Vz5OG*uQ2w<6Wq zcC6$ ]nu%:TXYDO]S?i5 4OVcb 4cLWH)6d>a ;JB(`3*  43<:G_aGy\INJ5|O~TIPe`L6" g>`CV'tN<% 'a-x1`!E^fhcD$8[j'\| h+^%SHk$<Pc~|`2lv}| OF^ZYX`qhU?-*ZktpO&}dBoR=L]{V3!tPD%yIkn!uqaB89@G6&/8>Io+];A>2I`a_X`xc[3 $W F@g ]c5]-O^go mA'Io'#&<Xsv&_%)i:}R/cOFqZ1~k?yE]42ExceilXXbp}uk\lwA{7xoh};b @a}Gyf&s9u'0;SysM0 ,Xe-=JZ!Fyq_7b5x2~:Q%  Q7..@>:a0c<U4` }1   "8f ;mmO;702*$3D]_[ioujZ7",_$W5Tk1`UB%Ea/_*>V_w)Yo@oiJ"Fb"h\F,bP&gFK mq' Fx0gkXYZMO>EE83%*`K3.?]R)^](AC/9:DEQJ=~[B?IzIj#?k J'j  *yQ='d+Z5re'*,wmUKPkpc8Qi,g/i8t V*cV>04EEJDNLQTW`K,}gE@@`P~ E*iAi*}-GiytV1 T !8Sa=x(ZuB|W6_QCe/&+'u`77}L"pQ%SR L =D6-%!.b 8{4G\rvfP33!(D*f*c 6g hy[7@`)$[%Nq9CTV.eN-~`>o4{QF[]S1 e>VT!P$X%<]94Twnwt{|)f-H8*LfuBxHm NI(m#2Mk  ,Tr{z~|| 0CTU][9~YO6[i'K++:J=%\e?!TjT9SwX<EhwnmX1W I3fRNx*FnZ$Ue_D3:P{[:C3%dnR>7D[u$}udI03AINHI6CR56JW[?]SxS[^mV]gK8+Y"9) BgP -qf,0T{ K3QE-"<X4hEcF*/Umxomk_:tOB- `yK! `g#qd2M~d> W8(%WwzgXG813Bd>TcpT@Fg/c,\K$Utz8u+j 2PXj5N_\G$-;h_Js4syr I  M  V Y v&;XlBwz,H3i: " w i3vW9 h +`(3'X$'lD~Y~WMc1 mXw8אf5۞ݻ7n+o!  Z f oT~bBVJX>H<:o\ 5GbG!#&Y(*N,*x'$#! ./|V bW# a " 6RqK"!~"#/%0$ fAt ~ +;}A:Mw6`F%~XL5VSsF $?q7Zهשѝ2ܒ|-F]H6 u T  ]_woX?h2l]~E 4zB< y"$&N)*/)&#"  MVv  W 2y2 * ^.EMA !#$"K, >o@H)!d'V{9@G6y,t_KKgK+bۢ>֠s؞ۗHHu=PeGo 9`QB96#-BL]O=-b%# 4  :Ts 7"e$h&()8(%"]!6 rf> Q 8> )"zp@o ""!KK e T\,\ ve:=3OhO+~+:?kzZ߽tK׹Xv԰ٵܿw,7[w 6f qsr|$#21EQc4> s   $?]m!u#%'}(?'$^" rDQ  $}   B /Q1Les O!] Sg ' x-K.V[QK/b 'Dt^xk/t!c6!ޜۗ؝IFw׌Tx4*N\eu| K N QdZ7 9m#LLwU? !#%'|(P'${" J#w=&r  m4:ln > $.e 3 RFoK&a;xXvJ:Jy'CVO?J    $=fa,S*D@ & 58&,Ms'hS;xX(m$q`&e~{SB1a~߸c%l$i(L3Ss/6y ^ KZ;9tzk;?wf W F !R{ 3! ]F')a/  _(Q% ; @ 2 3CXxx^5\9 -? ; j ,p dXnyn3W@^(Nng%s[/\>ksk yD& ;{gR`^g#f t 8B V P5}I5EV. %,{S&-iT2bOY0 |O0!ex[5 2|C*!Oyq 2 B H=a}+bbe:'-"}| v  y uU;wg | QA&Hq 7 _ . OwKXsYYU5Ru;XHh7A0_rF;2=LZSg[OwvC pM_u ^5KK .)  S  PTo`Qh2 Xp8?i g ? iHVOe;;n} SK.  F6u;u I~azt%j*+Yz3\?>a<sGzACCoR<1LZ z`om.Xd x _>w ={* M | E!  U3F$R0 #>` / xPAfO8dNf>/Gt qQ6O-Fj]KLl2{ ~#2 \N>zJw4-Q565PDP u.d35Tnh [ X/Px.RcF/2Mnz  J ) Eds   ^{~<Ad0 U R6,b:E(Y# :_T5IkS!#Qulo nt q]VXK'Mbx MAc`IGDi[-D ?@z8' \ $HddrkwNznos+e  g P B : @T3\4Q acf7_4t*px$>IO&7xmEf^6!XM&A}jaU @mE(~pOfH3( #a - w 9 x  &>sV $ P  X% ^ ' @wp[ Bvq $ 4{0{yL>=i9=>$}'k9, nM_*$d85!?UK*Rire\ZoUd[-7Q7ta[\PF D ema4 0 hpT@=0#K: T  Z NT D M b(PnO\.L A \sBsQ4z{ ** r(qFr:kIP' QSMW@vD@^2gV-<\&4 V1g p _?#[Y,f5xQw f w u : H a  y TFG|^Y G rBX'5nG#fa(BMf(d\U2&h {(bI_w!NS^XP@\=#0@kB\; ,IWe`mVHjUC' V j v |nL"wIEL qn3G   $ ' m<dR , ] 5o1Yt|F}vF s %}U3\=MKWG'fc+cFMZ( (EddfOF+ }H" ;J[V#W.a] C[qRye0!{S < P n \ 1 2 I _ *r ",  H 7{w~gW4ELm'D#@Vw Q tdT% s_\Zmxuc=y1k>H5Ox-|.CX= Fn3k n P5V\vW 1*^ 0 e  I p i  2 ( ( + 86czY s h L R-Xq%Sj>`U/]5j3 & - l  3 b b F _} eG1&>`+gc] An&C#U"pW|"DjFLB{V:R` JA%0'xR* -Y @k: c ( 0`1GD<cr ^  3 _ T / 1 d H n ( { 2DWLL; 3  ) B JC8-(**Ny1zR&zZVPbz 4QdjPR=(f^c;G'` }L_DEjB|L?u /'!wzzyP$ JqIKjICv&JFK} 7\:$DXfwopicss D=n!'Cl? H {  &<Xql[:Pq } O % I  a 7 ! 7 P w  7 v  "  c ? /   ? &OTe6}2TzwnibYTG*{W'Cblkhw_a8t l>FelZ]!7p 6PkL4 ?u-y%\@|  ~ l a e?VTM=h4 b M )  I   3 U y b 9  ` :  c K \9 WeIj-PkD& Uq<tB'Nh/gfH(j%vfd>^."g>h-Xkw&m7<|o\ m C  j 8JZTV?/ k f W P ? ,  R +    ' 3 I X j w v V . K  W 4 H_sVZXM%yzKoK]BdK->-ll\;!lpvlp4t4/C-8S` DAWy%lM4Ff.H- N  g 7  W { j e c X R > 0 x A "    S  e " S  PTP<ZETcgG/ITG h8}2vK ]>n;@srx Kaa!~eqW;a4yh>V eNS>+laK Q J  \  Z  ' + < / (    & 1   [ 6 *    k Y 9  n & b % ~2-Edsgm Bbtvj6HW c U#F? }C~}~{nm][PKYt7oZKI/Y&"pb`LbCr rxY@|E I j  O G  1 L k  & & "  w k ^ J 9  \ 8  O i " V Q"Y/h`%E[PL4D]6UzD FuX/ Bn)X@z0pGYG9\$8a-A^p|8G I  ] ' J s . P f  ( ? E I K 8 ,   Q  ] ( K E Z~*oMW:{30j1v"p=soSxp#`)g.mR5! 0HQfm{ 0Gfy3kBw9eN}P~OR`R3i6 g ZQD ;i I }  H l F j  ; e   0 , 2 '    _ ( _ ' : v  fsvi.7*o6ID6$T0slMQ MpR7 6COmw$A\!3AZzMeMn.\!&{Z"G^K-| >bLz$ [  S  F j 2 c d 9 { @  _  D G^t':d'v7@R:~bQ#|#au5Q$kZQ;$.FQalu{}{ 6YBx(|pu? sGbSQ1VVO1yH5|0aW f Q  Z q Q l u f O 8  N  M # 6 ;f3e*ZFh j7t$zT'Y&P<*Q?xCe@&| /APc&?gD)p >qAW?5w/~-"oGP7kY] H  B w  ? [ t  C U k p o e T H : /  u Q  y U # G ]d#NQKU}Dpg>F OU6o=p6vbPB.    +4DUs (GnhKV k"GV;{1 r Pj1mXQS&o= 3 _ ! R      _ D  c 6  q  H\*Ti,r(~>I[<q:iGx1(^Ma,t_8xw~wmcWXO>??IXch|&<`*t \cq$6. |==TT/i8Ev W2U/ m  7 f ~  2 Z q | q [ L 3  l D  ^  W%N/mJ] Z q3s,*a9\<w41y"Y#V;&z^@}qss (ANkL.}(|4-.wK[7y>t(w<| kGL.v^+ V / B ^ {  . @ M D C 5  e A &  q S ' {;yG&_>b.P~Gx%rNu,lG\ZWY*ybO-zptqmkgYP?,# %0Kq7p9CVB<` d7v5pQhQ,q7({X = i  < m  P <   j U > EW/V2X#D[(R p^4EG}-!BBi<kD!o`\Vdjir{ @_X0u=!_ u*-_QJ K-n5jY#k<lKm Q   d L 5 "  nA _+ }_0b.a"UK mZIbr*KGw5fDb?)}ficRS4;9ekh(w5gI* mTIMHL?/ zh]JM<44Co1pL,fJ% tgE*|P+xbQG>NE_m+b5_%_/{7e._!Rx%9[t0l-e72n7w-o$/278B=+!  `> u^E$pH`"R v;y;f ja-lO7( fH-$tP0pfZCA0$se^]SF,xX<uAE S Vel!p>ycG(r_UQVOXQG>#iI)rW5 "8Vk 2W;zR@v =\~ 1Q|%=Sy3k J'[+['m!<Ubn{cBhf]WG;61+ ~lM,sG Z(zD ;Dm3vJ}X>tf<'iVG)!pT4)%6FWa+Hp(iA)V(NeBe,[} :f$O&O R&f2H^mnbnippjnd_=%nW='p<Yl2C^Z TuL5 n]VPdanxkmUL5}W.m9dI=.107GT^ 5T~;[2[.6Yj*Rn"KO;s5w c Ojl< g;Me&_`x2m?m[@,lT({V:M#tdgbgtt .PsJ[:^z%RgrlkimkqdnaWE#|kxkjmpgBf8U$Y`_2aD2&wz^N0h7y\@Z( 5No*[L}.Qf 7NQ^WVY\p3e=Q*z ^+Uq {nf_`\d`_gm  pFvJ^2_/mJtT5vT5d<R#fK.'/Ab|IuRl"6MQZcm  :Qu;=2-15?AD9+$  7>Kbq*<VwtY0 fG"k<wma_ajmm_WIAKILKSKTB/lBfA*_< {w#=Tov}4Py 0Q}Q{:Sh{ 9_z,EWu{m\G|bGX+ uq~{u~yiG:\(nG[5 '7FQWkw"& (%%-&)uu{(S~ 0PwEo (<G`gwvvb^][e_^TPKH:3*'<DVekAWo'DX\_UQMF>4#lS5 zI,dP<b2e9r_VR@>5011GXj%:EF>IJO[Zabaf`dfqxzq]L?40$v_C+r_TKA:&z[SD=??RT``_fey} 3@EGC<8:;@EAC=9<8?>?HDI@87.=?Spz&9K\l|& $     !%.8ADTQV`Yb_glzyiNI.'! ~ga>,qcWG9) zmc\`iw 08DIC=8()'$+  $/-/1#)+(@Jf#+HWcs|   $5<DT_iyu`L=1 wmZE+   uaU=+}t_LG05PYk~xcL=,tfS=)  !'&%-.4401' q^N9& vaSJEDN]q{"2BHRWQJ=( %G`!AYi}   &3;O`qcJ. mfWPB0# .6:A71.&)(-,)% ycR?)se^LUKWqu -36.&0DPV^XU[Yds~wd^KCB;?7/ %.3C:C2.3,ANbz*<UYckizzu}#07:7435;P]lkibTE,%no_eWO9   #! seTA,%6786:>Kgr{l_RMJGB7,')-4>87)$0>Yn,FPZaipmmom.779467@R`mqkmWN7vgh_YK;   '&'% '-$qeO9*%6?ACAFFTkxxoWZFJF;<#+&*27:3$ 3HDQSWULE;71DAOLE<.l[H62)' !'7<:;85@J[nvz{xlfPA7!)  %=Vn|%3Ldwoikdjm\aYQ^[ijke]OEOMWgr $".))31CSello`]I4'vmS<7+,.4>KWflmnoy}uumdok}z|d[D,u_PO@A:) (/39APS_^b_Xa]n}unYB4-Gby .E^n~sm_PF=?:72)'&*35=?36"!%,?Kfv/+/4/?BZelpf`LD+ |s`HJ=GHO[gs{{wmosv~ujZI2kiWQOKE<2&09D@GHRWX^RUMESJ_jds^\U@5*  +@Vf /;MXV]WRKA>351,6)(,'09@JKLHABFSZn}%(,#&..FHRPIB:' uiSRPOO\`gztyzsu||yk\N8# uhVNEGC9510?HFSZbhnijd[adm|yzuhgPH9&44UZnB__RUc },Q:UHJt-QHO p p D u  " u p\br~M@% y;1Fyf D}&yVH=l~5Lr~ W|ߞ$ VI:/Ls:k?n_Qk8bw#Sl ;[x ! <h-G*uUE y [ 6\y'Wag29 ) E h  ,7Sdg!-MZ ! 2x!rJ.qod8 eC(rbY\HeO&| "EwG{9VfGKf*Ra2YPSn.RA\"!B9wja  NCV j X A &x>$h\ % + M [w|*k<g . TipJnAG1p<T;G!YYe9y"w%v~!iAY8HaLo4,\H)DLt K5g&M~i2eNv7 U QhRr_[`m{ r t k c ]F2y_@X    $ 66B  z iK(j%-mUI*s@xAKhV4'(8' <\ hy1\<-(Z8}rSC=_x/X%b-|>xOe Pw~   9Mb_"8 K Q [ ] _^PC,qDRZ " )   o[F7ip-P! s~|nQ. r{?Rs'c/G3O#bg9d':v$hDCw8D Y1#V:%}zH8j::, eJ=0[iiA " 4 H Q dlmn\I7#|R  pD&qK'Y? / 0Wn f>gf?MDDP X#"lKD}/U%[3 `3_<!FE}*qVt2\e VS j'>@x-<9b$[Y:n 5 f/qM20A^x  9 R mn3\'   sKWJ) H p :x @dy`VB/*`:'?~n.y8iB*oWCm&#LhUa3MA&Oa5l>_uCr<M V bjYi="Gs 4 ] ~q I#  z >an*|ngA" A {)w f?_xQ>oHo,fEQ PvBsgTCA&#Q=3Vm Bj]Xt5dB#tT,kDFtk,~p T ;4R8VD> { # O #36 $Q F Jy0]^0 } ) |)CG,v :$Pu>dOVY6o\5"+?Wht"Jv*4F}^WB'm]RB1B6kWL# f 2uI}{^ 4 o 1 r 4^tPa:P  D r%8GhW s 1 ]Cktbc &3GmKQ\-nN:2 #(%28AKWd7pntqDeH\zk]_lty~vI$o q\-j L '\V(0V D i 1_ L;4rY Y " b  r$rYHMvc V H ) G d,n(VskBT0&5gaquqbZXU]er" Qr\/+ 0.^jr2NpN>rM|  * T } s#r % u n  QDnZs|(t  0 /o2fh|- g? \+Z#z>Q]7bJ;;91s/jBDPnu|7Zvd.zKuiOw*P#TQ3CbxGa .Vn:m7H 8 e & } /b+ >p  X  A m%Gc6, d@sHWk6*{:o>hZO)+>\u<^/]*:<-|I}k[RHCNE]<aEE1M h ^ _ g XOA'bMEqZ>AUu:_ae) 0Nw0c\`I{NQ*zZE0S9-'rnnqYgW   ( % 43;-#y', F C /   Xv(p?Ej  ; M RQE0a1     "k]h$/+p ^ W ^u@jrL' /Y)Q(g*lj,n&j<`9\Y=/q!'3No"*46l iJ2 Q j t q ` L,V)y 6P c s v r bG& G<-    ?(|)q5zeSHaE8%%'TtMrh;iwE l$YHxc=-e@_J ~>?9h|slZO > ,  EV] p  8 [ i i h[9v[6K J C : * k@ nT '(5EJ[_kkrqsQYHZ"oT>,@t'c*lR\aCR[n*m+Q)LkUF >^h6GtcZ55 n \)y 4 m = ^ q y[A1@ 3  n @ e!(O_ \ f v 2LVwL JB.eKK]q*Y W,wt-A1Sr-A13WM| eTFI Vx<x=(oQ>u Y (  1 _ B  T   #788SX#'  ~ V ! B IGd/   9 S t /Qq.>_n:xI|?sM X-l7}$q'DwIG}$d F/,Cx6fx5P+c?c9&0 tO0l> W ' Xi, w  i  b Q ( X} *>,  v @ V  O 5= 7 Y 8o 9^z" LGo@mV*,tU7>[0 v`g@c>h 9a;xgg$&jx] VX(iA(uW.qKe' Q  Y | * < T Q ; sC]hRO. d  s  m  N ^kF 1 y A q Ir7c&Ppu<\ 4XIp^2~S=XdKFC(!7Qw2iH/1p=}]6qZ>-^jAj9f'y? h ) F ~ z d $ X  @ \ ] F%n[q|^2 O N 4 z 7 q b * } 5 k(e J~+i z"C_} AZ K^JRLZ%fK;>JtUhmo{Iu;>JVmjxiYT==-*(&&%#*T09Rb e[7 u i p L  ` - ~ =  FzfnO3 p ; ~  O }  + K F  q o G  _x)09@xB, +*4<*$0;N{QZ;5Tx5 vRB2#Q|~li[TQGHAFIOZcmr`nKF8"f"O a  5 B J ; &  | W . c 8 _'&g/ p ' ^  F d m  )  yLq4UM3vmfX\RVWPSQQXMOIAB@F7'ECBEQc$j d &kt: kJ. sZF'G`_bXPM>B1:58<:HNeo>%`@&Z)HVg G m I , b C  `2 6f T / ^  - H R d w { r J V e P/ wL_>g+|oa`TN?4& A 3Ji(iu*VD,Eaf eyA r9 y@'"(//AAQZjR]_Bn-K`geT / Z j r o f V H 6  p M +  mTABVn$ a  7 b Q   xdL11h:oeT>5 ~MY;g3vs)Y1nB5AWo8:s)3GNeu=b3 /Jizp; |    ~ i J 0  xjns ) Y   3 T r  ~ K  V z unhhg@jC& qaJ4]6R!\,m`9P0! 4K~$cA67:U+`$6E^m'Ij^&(17:*_ J ~  k Y ? 0   6 W z  & @ P [ d h f g f [ M ' q  Rt%*.<Bc[gAmO4g5 5#gUL_^#"_T0kXZ7^s $8RnBg9@ sm`A? , F a q x n e P J : ,    0 G d |  * 0 . 6 & * "   { : 3l(@Vf| #;qO]. a9eEbw{w!2c"R##Aj*k0tM(u N5]{5Ps 7l4|xI%zgUC'[!"g % > L _ i g o h h e Z Y M H C 4 &      * 0 P ] r T ! G9{9\7d%qa$T$W5c, o'St!>lvIshos.[9{2r9~?p!Dp5T 6oIl!KuW3g,>A8j  ( 1 7 D E M O R V [ X S T K M H P S b m y  m K 8  u9P_@T3jZi_%t>vCuAw9xA3(tkj>r1{< ^$w<b'm1s7AvI)rdYNTWbs} 6Y7k9l r-n0l$b%f3:i0j:hPC+&&-7IXtGc*Y?n CxN OjA tdRP@a.v2h0^<Tw  , ? F V ` d u v } z z y p m ] Q = ) nD!X't9mHm"IjBl+]^YZd)x= s7yQ,rf[NIFGNMT\f}9Ws/`9n:s @;vDiO1pgO+l2u$XMu7Qj " ) 0 E @ R Q M U C P D D G 1 1  Z9Tx:v4Z ~7a=m%T IUW^$n3P LjO% }zstpxyy5OpLlKvFr8k!]f1{X7'y(kR,r8t!ZIr4Oj|        $   gGy>j.\=g!Im(\CVc%m)u?PtB^1`@"#J^y$Er=m)^ A~.h,m<dA5-z].k.jEs0Hm+CVn   vR<p@ l8h#SDl'ZF^1$w Qf#[ >o3a'Ei:J_r~hN4U'f6|8;}HC EGUi1t/u8}E^)SbFofTNC?=554>N[n 'BWIq Bo(`NL['oH.vG OBwRF_-Nm!4DWdry~oWG.vH$uD^h9z;}D? CRd%m*vAGHC=6+z\5O An4Ib0y6Gg1BX c1O"I]3 oT=1 "8HUo*MrIfA}KE?>b.j+hQ'TFp6a{.Li&%dM"qIvJn<d+Xl<O(J _#p6{El5 c(Q.tmOI;(*&"7;EU\gv "?[z:b~*](`T EE^$aR3g6e=` -Ks2I]n~sV9uS'\.]/W"Mp,f(Zr7Qn6h1[/hG,heZKQPN`_f}t 9Pl&5Y~Aq4mU?GQI<uAv<m*Hr5Ql 0>TWm}qRB#rO'j>KBGp6sB j4W(u8j1`0hEeK3 !/?Tl~ *=_x1kM&eB|:>y-_MxAx0Y~ ,Rs*Gj 0@OabtnsqfiZYO<1cDpAb6nALyL_$Q~H c/e/Z#{R/ saI7",7EP\p(GXt)Qw&[2dC}9}0cNTF{!?fB\1Md| %-31.1(( bP,jG~K%i<JV)j5[0Q$}IQV+tN+o\Q:7+)/(0(+*'.,:BL[]nq2G^w#NsLGxK>wM(aRAq)Mr'Ic)EMez}h\>0 [?Z2 ]7xKd6t< sB j<oF}HjEl[=&w|wttjrguuz '6Qas0Kt 9k#T~S,m0l :j'X=b<j)Gd /AUiuodUB=!`B sF#W,tCg8 vFVU$kBxS&cDrUD" ,;Qgu9W~:e8_$T%WJz<h0a3ZFc$Bdr&6<EHNGMKKGB>.+lI-|T5 |K)`A d.l8 {Ka2a4]<oP6_T=-( ,APlw6Wy&Hn.W.iKz1`En5^Lj (Kj,CMbrl[>& jL-Y3~Z+`3yE_9`7 _0vT5vcI0{lg\OI@46-/0.90?@F\d{3Rl2Lr#InBwFsJr Q0Qy 5[-Ah +4J^h{vxujfSL1 pU8a:kEV)rG%iGX2f>y[=gD1y~ ,H]w!>a#?i-UBj ;[ 7c&Hn(Gj.;_m08>DHONSRPTJH>9;10+wbO4z\<zZ: N.}T'yR/rJ _?!kP9 r^V@<-)3GYn'AcA\Ir+Ps1Su%Fn<]1Rt4>[k}  ucP>+jQ3 gH# wO#Z9iI'c<bE'dS;|rc\IB9,- )07@HM\bq>Py =]9] /Ts @l(Li4Vz=\x(HSm|wgZI>,!ycP.}[;%jLY1 vY4uU3dK4u^D7" {xnadXPVLZ^cmowy0Hb|6Qn5Wp*Mj+Su@U+Ek!FZp"1?DRX[iclnhrgfi_ee_aTIF--  |s]I3qY>!{W4 R/ eGlU6ZK:rcRH8)  &1H]o :Ed}6Up'A`|;]|4Mo +Dg %1E[k  &"*%!#!# ndUA1! uXF&}T:nG*jO+ pU;waP: niUD>-) $0?J^i}(8P`x9Se3Di7bq,B`/Gd)3N]lm_V?8"rbE,mN0xW5qY6~]E.{^I/ ~mgZRN?@23,&*)259>AGKTYdr{2GZf}5Ke{5Ul?az%D`/Lev%2DIS_enxxql_VH=.ubG9_M+eD-tX=%u_J.pdJ?-z|iohmroxz| 4FYo 5I^t4Ob|!9Vr0Jc{"@T_z )-2?>EEGDHGJQMTOMJEB9;-2%pXD0t\;) iJ/q]?*~fU@3 vg[G:/,7DXez "6H]o/A^i8Sk%4Sg| 58NYe|   xdN?)kN:& |T?. {eJ>#scXB5 zl^MC80&  *.;GG^bp+2M[k+>Tgy 9Va (<Qg~3=IYduxxmaX?5 tdK5$ |gP8'c[@/ leJ;+ skgXUG>8,+"$%")%)+*04:AJPZeiu )8F[iz .4 gZD4 p`R;6 noc[\TYVZW\Z\a]of{y,5AXas,9GZeu);V_,JPt} -6:GIW[`k`qcmjkqrxr|rwsnojhg]`XLN>>91-$paL9*n[G:ylYF9$wnWUB6/  !&8?MZgw )7BQ[mz ):N`v ;N]w *11:5766685?8<@5=424+0,)+   rhOC0{oPE1u^[B;'sicPM=., (5FOXgl$4AJUdlz*=R`x 0HQbrw  so\J?* q\L7) zp`VH;.'!pi\OC91'!   '1DH\en{ 17BPXant#;GZn~.;FSXij~ylaWE:+ |cWB3-ul]RG?4,&{ri^SSAA5**  )&-:9CJM[eq{ #1>CSXbmu%0CWaw&,;AHVWflpz}ohXND5( scPD1* pk]WLH=5,yseiVTJ?;60+5'703769@CCRN[a_nqw *3;IO\`kt| &;BZbv&*5<BOQ[_`mjqu{z}~{slaXPA=4"rbWC9+  zlmbbVQL=8- tpfd_\^[_\Y_[adcmmx{| %+5;@NQ^djou} $7?R^hz!)28?FGJRQYX^\[\U[WVZX]Zb^^b\``\`c\baWbZ]a^_X[NS@C3,, ~l`S?8, {xsglY\LF>.0~~} #)44E>NPQc]plt|x33HS\ou ((-1-849<0<,24*5-345978=8<=;@?A>A?>D@FCAB7:/(# u`XF9/ {stfdUQG=9.(  $'/4;DFPTTb]ikqy{ (6EQ]jy $!&)%'$!"!#!(%()))(.+04/3.,'"zkVQ93#|oob\VLH=90)!'&54>CGQT]`finwv"&??UZiw           ucYI81}zshiYYPIE;2,$ /0:D@UK^^allvy} *0AFV^m|  {hZT96$vvoge_XNO@?6/+%"/0CIPY_flrv%-5CHZensk\NA0({wthjaWUII>=2,$ *0;GPXcepsx$38CNXhqpqaZM<5#ywnkg^[TMIC>6/$    $)-4@89362493:8>@>HAINKUWZ`jnv} "+/:<DNP_hlxz|xpmf`ZRNIB:1&! vsqnhkaaXPRGIGCFBEEGIJNLRQSZU_^`mju}{ #"+73?FJX_cory~{wskffZWRHH<7.'~wwtkpdcZXORLEPBLJHOKUTW[Y`agfnpq~!(-5<@JSWgip{xrveg]UTGF>50% ~|vspdfU]OUQJRIPPTS\\^ealhotqz$%.5;CLS\ekrs{~~~yxxvtysyz}~vukf`[UKL?:6)' ~xvkjaaY[TUXUXZZ]echnivrw~z # !Db9pw^&Cr]j-)J&~\1 XB Q bADz^ ]G:J'dD.@xqnlf]_ekxnFRqqtdjw|(9g02jBdWkgo|z &Jqp"_1 \'vW;)|p)p:l0X j  ]  c 4x-d )lCp*c{; V l  f N *} ' C G  hG(~K4 +,|/BMi2U#o\&O~rihx/L{ S|kV|pb_ddcW Q|  / ; J ] dqzXJ  S / 's>8nP't3 3 / o Ey* k_H*~_M&Pns(~8Td_er1tTqmgr<\Ax2kA.%xo_YPJEF-,H:^! {y} *4<` NM $@] , E  _  _ lU+k$Ji/Jl#n  Q $ Nv#L X k xiYYF=-v&}voie_]i@)acw2VL6hzhW5dR<(  .Qu 7Qr)vC?ePZm ~ W r 3 L lh[4yFsRmcS  J f y b   ? i rk]RONOGBA02)! -N`jL ."4/  3 j 7 i#60vb; ` y   q  4@<;<AHPPVYUa[d\fYfM]I%pH(}cPpMuX CZLK =u|Q f>jW14ROt+c.mC}m NqfSF5    < N g @  c0Yl,-Z! 6 < C D 8)t "'5;O[gy~),%w=vC|Q)^TxExD G,xmoal2W+rQ# Dn14q:~eUAf'& {kX<(  u Z 6 o+t i  , " hIM fgJCSu   | q _ @ q C A ] (?Sh *^^Ru2WJ*(}h52R3xgB*0^St NBMo7^h?aeFABG?/)UY; uU< n @  Q [ }  { riX;KG"\UU^_ S = ! i A4I # " \#;[t .=etiT"jz$7M m*1=(s>,y.AhZ:5X1T>~@bq<_\h|+J65Y~~`0c=!uF  N H  ^ n  P _ hng\I-G"sfSG8  k 8  ^  "_@g(Jq`VF;@%zle ot`&nuc ^v? {`]I/Y c w'P E uJa"Yi&Jt+[P~Q$f2 R  X  2 ~ I akwoi_u?rG) S  @ Y w 3 y  .xBu)[ @Vq/a+xW9.~\s'7`1xYK>k'qoz&H]y3$JL 7m1u2|}q(uC k-p 0 m ! t _ f X D ) Ro#,&^}AX  w 0 1   2 7 V {L GyW4`D)b;(Y>}H_BVPzC p=th|lj#!8Q'k_Yp!;}ESb,EU @q-t3e ?   Z  [  :j#FX]7j z9\ 2 * |  h N  N$vJC|<}1 qQE?u1yNgM^7zg\WW4u [hv/i@&u$Kv#o7e*]&h+9X5"Mg"Hh!| '  } n + B r ! 3 +c4dI5~!8BO E " n ! S "d^Jd8MO?)U4dG?+-tEjP?.+6Fb_&c6["S/LubU_1rIc:rH'a dz(+$ p   m  @ Z  sfK'S%5"7+ r J  J k < w 7?D4$nZ{M^.m>`KOI=tlp| %@>LB5E_v+W*=N1i5{V3\pDrcZO1p  9 k ` v 4 ]jd R'SE#)}_9 z 7 w # 1  8 }l.@OVUSV +Nl6bI{D${2&?\dOdmqaRMP\j9ga\mu+@M\Dg;nM=>XFBNh!O\!Qtwl[F1&oyxlU7    {V- i6 w9 aM\vW)R5Xi}"IYcdn}5 ! DsG|&usBt-L'wlkr2L|01=,$  /n% ykX? h8vgK)^<\--b?j2FZl^&z4Lgz ,?QcQ geJ@D`Pl`^{"^>!rmm"|6".8>QuX2bC&g: i 9WX^KA)q]2zQ$Lp1Po!.5( 9r5^6W| -MWA/ v9LKVm*`k6+i!^1z\HD>n !3?N]i|5S)vMV$DU|\M-wL$  Bc$<Up~BA$V&XH{S|bQD2% mE5K n'Yf/bC,0>YPAU.|e)]s,;Q_u-mv:q6g%@I3ds`R9.oQ;)*:Cb}*AL_lry^/_`L0lL$_g5s_M5#tj^=i$S<KwGVzY OX*y0Odo"9Qg (h?K h%:IKE!:MX]dba_WTE;/ xe]Vdl //ECJUMWNQGB0 t'8B=+u^XS1yYB.h]H6QM[}Ei?# Cd~'aA(T"T-Wt:Sm  &$+*'(}kH4gOyDWyPo7V"~D h3^1c9U-oE$ zlR@2 &?N]|5Qy 6`FH{]7pM)[4`.V9g .Uy@Un#&,.+0$#  lP;uT*_= T'^+\&On=["OL"mCf@+xcWC:,  %1>L_o3Vw *Z ` CZn *66CBAI9F46.  lZ:&lH)iAi=R&\6h?vOj@oL!y];!taH=* +>T`/Vr(UvJr(N7i!L| 2eEwGo/Nu)FUt"  u_H1lQ'tV#U(tES)e7~M"vF$]6u]? }u\PE2- $&;FSjw8^t$Js:\ 6_AtQ{0c=g 6X8Yz!5NasrbN9% hO0 ^5gD h2Mb<Z,[1vT1|[G)||c]RCA55%0'&%.09@LUdq,Ad~#Cg"Ll;rJqPz0X2Z"Hj<Z{&:K]ny{m]O<*hO1iEX'U+uGl9c< yF'sP1cO1 |hf\UTKPHPNU\akt 9Qh"?]9]!KtHpIr"NwIt 0Uy;[r2@M[fryysg^SB9&cM2zK. rBuO vCqDuL(cAvS4mcD. w}{tz 1HXy;[w Li*Vy GuAlAj5\6^|3L_26EMRZ[f`iddcbZZRBF/,! wZI(wX0{_+jEjNxO+\:|Z6tT:iZA0 9FTs8Yx7]y4^%JuAe 2^!Cm;Zz!8Qf} )#04.=257-3)!zdU8! {Z?kF'f<tI#^8 vT+xS8zb@,ncHA3 !/EFdn #>[u ,Ln"?h(Ir 6^$Gt,Is3Tr!:J_sxkRB+|]D#~W9]6 vK+jIkHuV8mM;}mcOI;//$,3?IV^sx2Ddx%=a-Kp#Sl 1X{=a1Vy/Gi} *BJ`q{pdQA0 w[;(]E${L/tR.}S6d=|YBzaG2soaZUKIECBH@MIPZ[knz'>Uh7Vp2Tu!Il$Kn$Mm8Sx 9Sj 05KVXpm|xlfSH:( s[7)oQ1lK)yV:q@+}a8 jI0xjG;$~xv}{+>S`}6NlAX'Gh?_5Ru4Rn<Sb|%28CIPW[caffghdba]RPA84"whH7x]7%`C"}ZB{X?z]:"s[=# mWJ7) )8I[g :Ok 1Hh#Hi/Vu9\x+N_~ 5A[jz!"&-(0-+/$$ {hT;+ w]?# qY3}]BoQ,}^D& jQ; }o^PB4'  ",5BRZl|-AXo ;[q *Dk #MgD_y&;Vk4IPmqtaN?(rXA'bM'eE*`G' gI3{iL:!}kcYJJ>;64,1*0./84=BFLTYepx *>Hdx9Hi+MhEZ|0>e*@Zf ,7JQ\isxsq[QF+% ~iQ9#rV9"gH2vY?! jS> ~fP@* u{onmijlhqltwx0HVrv -*<8?EEMFNKEH@@;32(! v\N>!|lR8( kT;% wcI4{bM6" mbSC<)" 30CNSgl}%8N^x*IZ{ ";Rl'<Tdy.@Hbgz  xj[F@# |gX<-kPA$s]G4o_@7 sk`ROB;6-'+# "()4::JJU`dst !/BScy :Jg}$,Ifp8OUr 2;IW_kw~~wg\NB-&{_W>*xfO>#iZD,eXB4& ~wpje_YXRUMMOINNKRTT]]gjrw &01%kXI5&w`R@* wl^QE7- 16BSXlt(:O[t&8J\n/:ISdn"%-.86;>=>A=?C5B39.+(   yr_UE6' r_VA2"{rZN>) }rgXSA>2'#  "(46?KP]jl #09VYt%9IWjw -:BS]dwy   |phXMD1)zf\F?.wh`G>/oecUML;?4*1)#$,+388FBOM]^cvr **JLbl"5HQcr| /0CHS^fpw~xr`_OD?*& ~i^U>;# rkWKB*) ytleeXYRMQCLEBC@A@>@>DG>NDNQOYUbcfpo{}%+>FTcr"8=P[fy|-&=?ESUagow|zwnb_RKB<-(xlcRJ93sd[K?4)||vvsmpjjleifgjepjlwjzwz!.8DQYht"0=GTcix -)6>DJQV`dkso{yxumgaYRNE=7.&  {jdWJF2)! |peWQB:0"  "35FJZek{ #-7BMYbmx  *-4?<EJOSW][g_lglniqiomklief`_ZWSOGCA16'## xum[ZI?9*#}ikXSL9<+% &)8>ERZbmt%/7CNRbgq~ ",,278@@FDJMESDNOFRGLEDE=A864*($ ~xihZTKA6.$ ylf_SKE<50$ "&+79ENR^fkv "$1<>MSXhfvz$!)#.)..-/1-1-,+'*!#  zuod`WPG<7.& }{sfbXRNB@63*"#    ,.1;?AMQY^inv$*0:CHRX]hmuz    |ytmcdSSM@B0/&|sndcWUKI@;;/1+%$      $"/*35=>GJLTYaepo|  .*>:GLO\Zeiqw{qtl`eYRSE@?/0% }urldaXYNMHBD;:73300,**)&)$"& %"#$$( )&&0#1-*8,869=@FCVIX[Yicpqx~ !./4=EEQQVb`jmp{}yxole`_TRPDD>54&* ~wtngf_[ZQULLKBK?ED9I6C<9@5@:=<<=D8D=BE@K>QCIRITWS]^^jeltp| ')17;@GIPTT_^emkwv{|zumjh`bVWNMGC?69). }zsslnfce]^^W[XVWTTSTLVMOQNRNUNSPUQVUUYT]X^b_ejjosuy~~ &'026>=CHJQSX]afhoquy{}wutilf`^ZUOPGGA=94/+%  zxtuppohogkhdj^haacbabf_edagghfjhjjnnowr{y{"*&055;=BGIROW^[efimpss|y}|~{ruokiacX_SQUDKC?@831,&& }~{yxzwtwrpwmttmwqrstuszszzx~{|  '(-2288>CELLQWWZ`]dhgnlrswx{{~w~vxuptinfdbZ_TXOLMFDA<;920-&% ~  '$0+0859A@EMHQSRV[Z^baffkkjsnwtxwz|ww~{~}|{}xywtrrnkniifb^_[UWQNMGFB?>966//+&% "''',0-76<;CBDKGRIVNWXW]Xc_cddkeokithpqlsosqsqqrltkmqfnfddbb^^\YXVSNMKGGC@>;853//+'*#   "#,%+010;5;A<BBFFKLJSNTQYTY_YdWgYde[g`cedcce_de_e]b\\`Q_RXUSQQNLMDKAC>@989/32)+-"&!&",+,1167::?;BB@L@IKHONLUNTUPZOXVRXTVYVUWSVXRXSSTORKNMJOFHHFDBA<>;8843//+()&#" !")%--,7.7698=<=D;FBCIGHKLLKOIMOFTFNNHRGLLHMHHHEECBC?A?>B9>9;577/7,//(-")$"%     !$$ +')0+-7.:25=3@9:D>CCC@H?AH?ECCAFA@H=BB>B??;=9;7;67:3914320.,-+)'&$!%%    ##'((,*///30264797<;9>::;8<7<87:6>1=3992927112/5)30.4*.-+-,%*&$$$!%     "',,&(/&2)00/5/6549445336/611404/4.02-0,-/+).()0#-()'(("*'"!      #"###($&,$-**--,.0,+/*+/'1+*+,).+'1%,)&(''#'%')%%%% &! # !  5A^j,Sp``zX, ~I!`+[C0cao2 H n Y/$k@[*eu tQ2l'q+XJHx-FSZ&I]`nu.FdGv=q#\1a8 | D  ; + H $ i_F!d,X4!n4NZ ] ^ ^ d   d3`-O{=a|-jT; rW8rb M 2Ar@l` g(]iPCL$Uk4Wd,MC{;V)\-p?  e 9 `  E e A@)hCN$Z8x"+- *  Y d bJ~/nMu6b{XX(mL. {.bC9=L k2ho}0z6Mi0d2LEW0b7vS[L7P%d)Rl % t m J  / z`@}J,Mw    v f R 3 E E  $ x._;m I{O8c)a*m"TM iZ\GSd*U&:B(KMAHVy,w&2S9z`H1*Sll=e!o2i- a  {  J a yy wcMQL"h{|laL '  r 3 17r*c] G,`O}.Jl,^4Z{6 TMm0`7bn}$nOFEQPMYz#QB+~*v*rBU _T |9 [  J n * OYt xj2$oT; W fO^+n3r5xG >3DFW k*V`hLY W+{`F5;!N$`;"yw 6XMHIOZa=ml-]~8Ud  m | 3 +>]| #).."H= |`:}I  y : ; d [ q'/} X6zV0u,CL^r.F<7Xjy"x" m  z * ~ R b$Kx%;LUdTfWvNN q / #  $ E_eTB,eg !j^T>*wtst/nT9nE2|xR*-Vy-^)d+V$CXy-U>W/+ _DZ[O9 #  > % /|7m(Ks~ x.qCe&~2 : `4NMTMHBFC3w[3zZL=@Q4/s`SD?DIi`JgIJ`wHt3TYc9:ZJ E~8)eS*QR? b9 l o FS2l=i=4;b7HJJ G x : /v<H_x&<$ufY`+qAg:#utsv%*" vlw 6Mh Lkw7j&i-s5|iyO 9`M&a-[  a 0 4 a3}dB~Dmi/F[v&vk \ - P ~eR)Kl3Tvme*F{$f%g*g4P- '<[uV=  *A\%P n-r7Pm1"nNt?s 6[5 }  +al_ P1lI><KR_N7 W  GB0~NU!QT@g1l T IL|"z""-B^AtuX~e:! &m7a%~Go@l? a8dx/` 3O_r : _u6Z` [N7tBJ `OD)sK T t A auI kE#^&(^ ?pM{*eFIXw} "8Lm#T!$&`5 zkkkXs9y$}M#S!e1{OR7R}3Qas [ C 0LI] ilc[VhG+sJJ z4qpfP5sV8G;aG}#Gy7K#Di)Ow$OZfSg7mI(soW6l}ApDdJo+|R-}`7oT%\]# qgZF#`^5]sA T J|$WMo}*o(qT-d6 :&$2?Obielw!:u8VrAc!Smd*MPlH&i W ] h*&Gd*>L g1g9axGoJ# pP2iU[2*v`P:m t1Z$TURu20u&X3i&[q">Th|(8iMLr7Kh0Mq<0-BkM:8>Lx3SPP ^#yA s)MB!a;~iH38+;3iL,sJ!~|El?Gi-q1O1yRHq Dgw.Xas.Ss!;Uu 0J_{%8Ysz6dTU` q)Pnyl \[ l!CyQ6xhO<+ hNN9V/m; r,dsh@}O#sGxD^+c9|Cs"Gw(DL1(Ce)W?h#L)M\w'@YoT%b Zm7eS'0bG9~aUQYodI$yrYP?(' Ik[@xJxCk(0 V3 m@T!|J&CBq:^$Di\)U&\*_)WD 5Kdv&AQk{p*}ek;f ^qh|'lP(nP?79xkS9{rf\PCAR)iEq;Ww.?d6e7f: vK>Pk Fw*Nx;Tsi(k B_>zU/ZECMZn=B\kuhCicr/\LdY#i/{K&vaEjwzeS=&{zuNuCe'~:GLF{wU7yY6yN#wjxN{ =a "BPixz_0J0{d O<&dv(6CK;P_>r5}D e7*UC=}EW58FKD4"/t/H]lpi_,LPO?'lN1cB2L2Jm/95"m#tomg] YU !=bMSz>\QUp0b O:FW Y?ISOD9!k qXH%xY.8[!7Qhy>Ufo$|+/6R!^/ea#wD yI S&V^{$DYid<( }L] u4g5 V4.Nz8b5kL?t  .8Sx&t X? mIZPlrdZI:+eL4.C\s)0CN[cqv{}kBj'T |4Yz4U ~]I<+% ~d.\|Ob- jN6$-,H`Kq0e5jDj ")A@VdHZ1 Ob b "CLR[QULD<3'ymN=;/>KXq%37>IGMQLF/ M E ?r,[F~lXK;,% b@S"^, \8zmkr7a3[N7Xl{+8GXex(k!a.l6s/q%Z"$$ mZZP^co  !$ Y&z>KF AOu^S:0 z_I'N#oJbG'3Vv/V3] 'ASgs)8O\qN|.j(aX?wLt|tzt`0m.S`%o8W6 tYJ3 znVI( rJ!W=r\:) %CWo6RyEh.ARf| 2JZu=l<tU7nC{ 3UssX4S#O{? ])fK}eJ6e[A/bJhO1 u\K91,+66MWg6Px%Lr 3MYu (3Xl2_J{KQKq2K`uwdI'jAX"_)Y*|Y8hN1u]H3`< eG-xlgkfty!"}d4z_- _6 _7 |R1cG$nT7oR7sbS8./AJet4Glz,9gy>_ 0Wx<j 3[ Em=U{&4Fad~teS?-cC%{X6vT,gA_;]>sR7hQ4zi]MD30% &-9CP[jz.Fbw ,Cp6ZC]Im $Sp Md#GZ{$4K[n|whSF*rP7{Y?aHb;Y7 {V5 dG,{cE5zdaNID65+,'$( ))-57AGS\gx.G_v!Cb.Qo*To 2Xz3^{ ,Pl %D]x3?Tfo|oZQ;'pS;eC"tP+tP(oJ'mO*^L {fO1 ygdSSIC@:<87:<;HEQX_it0I^v;YxG`@eAdFa4Qr  EXu!:HTkqmlPH8 rW9"pL, ]Bd@`FgJ$bC' hR@"vvdeYVRPLMMLVQ]^awp 5H`y3Xj9Qy+Oq'Qo(Fo5Sv!?Os);KVfuztg\IA("uV>$vV8tL,U2 Y;fC bH$ sXF.vvlhgcaadcljn{{!;K`{/Hk$Hj8`6T -Pn6Up4Pe~ .;GXbjzzwrhXU>9!rXA% {cA$\=lK"rW- cBdI-{cP:*{ty{s{/5Qd| )E^6[{$InC_2Qu4Nx1I]t*9EP[bjvo{~urhcWND6+ r[A- nJ.mQ) ^?mL*aCpL4u[K6$ 1?We| !?[t-Li4R|Jb 4Su+Qf #:Sh{  48EMT^]hiisfpn_j[XSDE2, mZF(vV=b?"xX5jG*^I p\8&m\F8( '6HUn|5Tm>V{BY.Bt,Tq(G]~3?Yr}&.5?BLNPTTWOTLIG;8.%nWF)|dE'vU2tO1 hH(iH+ |fE2fbJ82",CM]t4Fl} *Kh'El )Qh/Jn7[k3EYp  0)684?5:34.*!zlU?-fU.dJ*jK+eJ.oO7p\:)saUD3. %0;JYgz/G`x8Xt 0Ol -Mn)Dm-Ggy 1FZj| tcT<+rY<&z\>hG) nI0yY@"hO:" wlYPD6/$  )6=KVeq,DWr)Ef}8Qu-Lk%?]|#5Sl-BTdt{vSU4*sfC/nQ;~hG# rT3bS)yeO7# rm^RN::1)$"#,+5@>YPjq}'AWi2XhXo&:JXhzubXC7!~lM; cN.dH%zYAx\>)}cP<( {ok[XQHE?;8682676?=FJSXblr->Sj~*>`v;_t%F_| )A_z(FXq !+@IZgq|{tc[I;-mWC&x^F&aH)dF(kQ8{iTA/ ~xojeaY[UWUSZU__aloy *CUf7Ik}#CZy!:\u8Km(CZl ,<HQ`hr}zsm]XH:2n]H.pW@ aL/nQ7|gF9l`J8, }vywtxvz{"4B\e} 37ap)?aw4Tf#<\n(?Qg{ (0@CUUecqpty~||wrrgc^QN@6.vZL7 }lO6!hL2w_@+ {_I2 xlWH:( !.9RWn !9Kg|,F]}.Hcy'D\q !5L[n&,5A@NJTT[Y[`U_WTPME@=./ |sUM5!y^I1fS3hR9w_J5! wi]J?0&  ",:JSip1G[t1Edu%>Xl1A`q-<Paq"(..5359082,2"&wfUG6 lY@)jU= u`G.z`T6*}pbUK<6)! !(4Ym3;[k|*>FZcqwk[UA8( |jXA5tWG,dT8(raO>/|li`TXEJA;<8380351<2C;HEQTYib|{ !+>O_s%:Jfx $5Kcs.:Udt %1DG[ef|~ykcZMB6*jbH9*n`D2rdJ9$fcG?1xzgpbcbZ_X\[Z`]cejlut!5BR`u+DVVenkw~{rnd_TQ?:0! q_T</s_I6 lbI7'znZQ>5% ~ 0;IXdv.HPl{ '1KYn}!-APap!,3=CJMX\]hblmlqnqlrigk[cZRSCF;10!q`S@4!p]L:%|qZI;geRFA-'  $/Q\w~*5JPhn $'/42C7FADJDGHDED9C28.'+ wh^JB0m^K7,f]K8/loYQJ=3,! ",;>NX^rw(0BT_t$3HTcy ,=GVdm{  " ! ~keSJ:/" {lUM<#!xi[K=-"xvecSKK760"$  "0/?AMT]jo~!.>L[jw #.:M[ky"/<JPcix}xiaYGF/& siQL5+ ul]JE-( tjlXZOHD<AA@C?GDJNLVVZdfkux~ %34LS[uq49OTdq} #(7<INUcdpu}zqihYUO@@/- }vhVU;8+{kbVJ=4)zxvmpijegcbe^fccigljup{{ (3>IU^nt !-2GOZkp "):6FKL^Vgihzp}~z~rtlj_aRQI@>1/"zjbXE?4#zob]ME=/'  '-=@NX`jx%1?HT^kr "(06>=HLMWY[acdhkmlpmormpnlkjjbg][]RSKGB:8/*# wnfXKI5.&|rh^VJC<.)"  &15DEQa]tt"06DLVans}#&*409=;FCILHPOOTNTQOSNMOJFH?C;97-.&" ttb`OHA2*# w|ge[ONA:4*( #-19BDTZ_lpy *88HPVifw  "&&,+.12319328247000,*)&  xjkZUJ@;+( |{qgdTZHF>35(%"#,34?GEZTckh}z$#0<?NRYgjr   "!  wum_aPKF73*${vog__OPF@?33*($ !'&308B?JPR]cert|&/3CBOZUkiq~{~vll][VCK853# zvnhb\VRNFA<<12.",!     #"%,+25;135)2'&-!'%#%"& %$'",*%6*2818>9HBHNMWT`[khlwt "+/8<DKMZVdjjvt|~tmp^bZOSDC<41$( zxordfbY_RRTHMHBE?@?:>97;85;66>2@8<@=ADDCPEPSLbR`c^ogrr|{ #**6;=IGPS]_clkvx{v}plo__]PTK@B:42)!!|{ttqgl_gZbYV\M[NQOKPKLLJMILKJOLRLUSR\T]_]dfekoovv{ !'*33<ACMKUYZdcmopyuyvulkh``WTSGJ??46/&( |syqpokjifdeb_a`]^[^YaZ\cVf]_f_iefodptlxxx "$*/38:BBKNPXTc\hkhunzx{~~}vvonhg``YXPMOBE=;51-'#  |~}vzuusqpqnmpjnnhpjolmrltqpuvt~v}}#,+.90B>@PESSS]\adgilosuv|y}|~vytntfkf_dVYVNNKED>:73/*( " }}{||{{|y{z|{|{#('-339><FEHPNTR]Z]f]lhgrktrwvx}z~~|~}u}urxlrljicc`\]VSSKLIBA?8:1/.''    '+),40:8?AEDJPIYNYW]\ab^lcmmkrmtqsutvwyt{uwyuyvxuuvpumqolikfdg]a\YYTSPNLGCD>>=2:*1+#(  #%')1.493B;@H?NGNRLYP\WY`\d`dehckfkkjlmkkpfsgmofoihiefcbb`Z`WXZOVRLMIFFA?><7440,,'$$  #&)(2+617:;>@CCKGJOJRSQUXXZ[X`]Zd\`f\bd_dbabbb__^_[]ZW]TUSRRMOKFIEBB?;;9452..+($"    !"'()./-3578<<<C@CGEKJNMOQOUSVVVYU]VX]U[[V\XXZTYVSURRPPMKMGJFEDA@<<:8621/.,*&'#"  $!($+*/,36.=1<;9B=BCBFIGKIKNIRKQRJVJUNQTLTONSMMOIMJIEJE?H;BA7B49527,1,)+)%#%"    "!%'&,*+2,551:76@6A><H;HCEDHFFMEIKGKKGJKFIKCJDCG>G<B=><<<6;1712/.-'+'%'#    #)((&.),-101626:2=7<>7C:A@>C?BACB@D@CC@C?@>?<=?9;9;497142./.*/%*#&%#     !!%!*#()(-*/+2/2332575:68:9;7?5><8@7<<;:;7:8894636304,00).)'*#$&!   !$&"&$+%(/$2+-0//15/35/921<.97/>068/817/63+7+/2*-**),!*###""     !""##&$((',%--(1)./-00/-2-11/1.4,11)4*-.+.'-%)*#*'#$       %$%(%"*#*%,$-*$0&,0'--)+.'-**+','()$&)"'$!%"    %+BHr:MsoL3uO(H2k:k"QxCsS;D7=8s:o$7bQ RaE/> hk1(i w4KG=Iat+<Rg30LX t)E]jF :o)Ry# A kL,i6i)IeT<>#T8O^ h 6 _x[Wb3 oB}J'=x#gbB&rePjorq|#8O1I`iaUZ_fs'@x'Jk=k$c0W;f,La\ Jh1,r_4m 6fw\T.Q l{+|)w H$8r_:W2 xKrF+vAmH-nO~lgy8Xz v-ibYX_fjmZ)rDq+\R [Dd 4Pn & =t VL6sQ$#uc/LXZQEo[J0gN5>Bk>I^4{WHt1gkl !=bJV b-!zhXKKJKFL`@ }=y=z:9yi!7GSaotxvdLEE7#i@H$w4G?5'yUj@bdE<-|lWCf>hQJTa/USdq?aM!>AT<q`D2)!%+V FQT"q$b4',9???A?{C3j,-930w"bjA2C5xgIg/3E3/0'w# ^ `U F FUa;7]t'UM"aPottU-rR:$ ^i~IF a&vF XDY6(++*#Lt> j.8>7,'T54gF\Lt,%%++//+&=P'1lV.vrz'6R3pw(9Pp7\yJY-h=vYR6M%jA{I v M\*HMCw3(ONB'].cVHfAv 4a#LvPH|:f/d*Wx1OW %@715=P s)XB%~%0@SXi=j9_6Z99H1V([(WFw,h=v;Q_QDUT6l Ap-b{'+I5p:o:m6|#Q8a9k2" */@IX4a,!\t[9.(x&19=#b%cAtW8]\C_'P |4S_}LJ$c8V n3t84d f#R'Y#M},TjH 3x_>yQ&];h Wgw OFe.U/W+J_] j ]Qu1T1]`[E#h H0%Q 'D_6^{Q"w/On/WW ` N[2dLBrAhM0x[I(J\k!x1?HL}FxKa+_',v%[ ;c 2]{;hRVK7'pb$ !Bn1Fey*@`6wu,ZA|=G2n+g8|X<$ve3mS;!lSAIv.gpu{#{"tg`(e<c8qE ;)V(Sw-WwmS?MQMH=NAFb ,8Kgx&H_}ens+b!]f#{E0h?>x6N rQxN"lUL)xqxD q#ule WE3}i? xQ4 mA\(J.U};f&Meo=6.FY^cgon.FOhx,FWrh@a t,p+rir_DgD"qGrL,S)Wx&Ml4PS? vs)H^j~2>/6PVkw$=LH"Sr0x:U~H8t$^Dv3x59HRA tcZC;+1/-v_C!b= -<4nU3sK-e4@m&Wu7Ul"Vj@cy5Vj! )/' SC.v X(sC Y|XE  xX1Q</<Y,Op :Wf|3V {9g!Jo%N -:HO\fkzx8t:V)W-rL#FJyGz W; wljZUOA@3,*!ErM0xJ[ b$exdH-|a6&wXQYr.Jn ":HgsPEu5l*]I|I) ':=IYWjgq{c-w7i3 xL#}R814Up1VF}K}azrmh_]XOMIC?=8GnX/xB>Cy0gsaJ2~bD2trs1Gi3BOhnT1v3w1p6t1n+xVL8DHHXYchltr~~Pv; zDlEmfl|@p"G~ 6l>o|vvmqjdicba^\al,i3w3w*gR2UithgS>8hN5,N^ 5;NTb]M1c$i/;MNnidajgorqxw|{~k=qAY0hF 4V$Ss-XOu{~u}uw|vz}y~|C{2z.cM,b6>C83! lW@&4H_v$-51  }Ha,Gc!rB|~mR+pFkBiI( -Ju.Rv Lu+Vk|y"W8t!Z3k7oi^K2$ 2I]m f/d)Us? d+|zywyvt}ryjR<pFz[2oS9 6Gn4Q~>a,P]kstyt{ss>j@sM~L}?m{u\V>3 &9J[iztCW*]*U+T1 |wxipich_aQ;%qC lM&`N;/44FZk 6X/Qx*?LV^afinst{ /VDt;o/\Ai|tc]F@+#-=MXhtzpU(~Q$g6vDZ6!tqhd]YQPG:( iH!|c@& zk`afp| #DZAb .:. |hC(r[C/,=Zj!>_w!-:BV]i| /A]x'Sw@n-Iy!4?OT^edmomtmorhkd]YTK@A6:=9FDNQX^_hfhpjoodle_^VQF7$ yZ7}W3 oE|eN7'p`SH60|aB-r`L9, -?Nes3Id &6FRdp .DUv1X{4Z} )Qn 78CITR`[bddgdf]bYVUKNJKNOSW\Zbaceceb]]YSPGB:1&kK, _>c@wYD4w_W?7"vXI*ujXL74  %-8FSar =Og!8D[ix&CQn=Z&Kk )Kb -2@CNPV[[aabc`^_[Z]W`]`baebdab^^UWLGE74'"o\:!iF+c; fO8! |iSF/ n\C0 sj_PK@<:89;?DOQ`ip)>We'7Kbp5Nc{#E_;Wz"=]l$)9=GKUX[_bagfdhfjijkkhmfeeZ_RPM<A,) sbJ-oS4~^E! mT<& nZH."iYD1!spfba\a]cfjrt +BUc|*=Tj#;Yk)Lb0Eg1H]t*1AALXTebkiornvsurupsgkb[]MKB6-$ {_N;xVA(f@6 q[>#{`K7 hSI3%",ENex4Aft'DZy3Jg:Np~#9N`w&9=HOY`amjpttuwuusjndb[QMA;-# |qUE/zaI/{YG' pS; o\B.uiNE5  $2>U_v.>Zr7Ri&9Zu &AUu#:P[z$,9<JNP^XdaiifkgbfaXYQHA@)0 j[F0"gQ8!~nQ2 eL2g[<,veZF7-#7EPgu">Qh.D^|*J`{+E_r '8N_p#/5;DHMPTX[X[ZUZRPLEA85&#~j]F2#nZA(tbC(rc@.jT@'g]O86 #(9HSgv "7Jhw >Ok7Ni+O[z 9KXo -.3A=FGFNIKKHFD?:5.)pYK5"ydD8mS:rV>&iT@(obVD8, qTox/audio/format.md000066400000000000000000000007651415623743500147460ustar00rootroot00000000000000To keep sounds consistent a sound file should follow the parameters listed below. # Format Internally qTox needs PCM with signed 16Bit integers and a samplerate of 48kHz and one channel (mono). You can use ffmpeg to create those files as follows: ``` ffmpeg -i notification.wav -f s16le -acodec pcm_s16le -ac 1 -ar 48000 notification.s16le.pcm ``` # Normalization All sound files should have their maximum at -1.0 dB. To normalize them correctly you can use Audacity with the "Normalize" plugin. qTox/audio/notification.s16le.pcm000066400000000000000000001100761415623743500172510ustar00rootroot00000000000000                                      $$"!/7 ,&$)#     $"'!  ' +&/ -=WL6+XplHEn4s"} M;kBCAH$h_v ^*_)o?-N\>"\F&z^rvd %e-oe)Ce\T *Z&-m j9t 9! AmOy;uD w`Ag.f  o1C.lfT{V^ V aec'1ZUߦ!޽߸pY#h: <9  d} !""#T####I#X" Ei+rN S .)iܐ۬sڡۊ<ܻސ31~4.a1y|U Ms "5#D$K%9&'z'}''=&M%0$#!S p z ) @[?/6{$ݼەٹR.>ؤ؛K&fEK7CC,-T N!`#$&&e''''8'&%%#!F>MN 9{'V,"Aܙjڔ؞ئ؊WTۚޠߒ Tg &;;# 6DYyE u!"#$&%%%4%$#"!Z IZ1e 3Y}=6`a@ݭޖ: O|[,>fPp^|7{ aMzK N V=BCds }]d | ;Wt[Q o4 :'o 1 z0\9bynY  .Q1#i\:d8-Nx21 e @)tp4 _  -{x_u e8Q>ni|PjgP6WJh M E8 #  #u/Nb?m^Z sS؄٪@ݫ`v@ (}f  Gu !"#$n%%&_'''I'F''&&$ #  sFx= Zy}k-'iޞtbٞ&Uܫݦx,TxЭΎͧ9̑Fgәܒ߿t0%Yw c peC $(w+b-E/|1A3l45u6<788 9K987163?0,,S($!4(1 Qr XXѽjA_ N*=4yˇ(]Еӗ&A 2 2 $'*,.02E5L789:::"97[41T/-n+(&"p&Y U{2[Vi|XךLf:jƢVh9vuώ'׍DmdFT ^M#'!*8-0`4u7:=@sC EEEE!DPCSB@><2;89o62.)$y 82HenŹY)Ugg>ڶ`]gƝ!2Tߊ= &9 ?!L',S27<.?WBLE, ^ J  # c D m ( m ZKguV$QT|:kT   \{TY"&8)+./01h2,34c6655543:2./+(%"= UifܯO ιawY}|bQ2[36ɔ{ֆCޛyN, T"&9,159J>OBnE!HJKMNOQPOM>LIH0F;C*@=84/)$$3o M [F+b WW8ƖVǽYG`ßſT@ܾ> !j n#&(+0./61g34E6i78q876z42/,* )/&5# ;Zu t)"l,(݄ٮ2ѨΣFSAuΠ@Ӵd)oPF )S "$'*,d/612567?878776420r.,)''%!(^C GPTݣ)֋ӺqEͰ1ȷDžXm2~ԇ@T9UK" A 40vQ| H"#%k'F(c('&&u&'&%z%#.!n) /ov%)Hy 3C[<Q@eK   f  ~F>H {k4'zvRs/-h_ } #+!$A'*-M1\47V9R;Z<:8}5;2/+M($ )e8v WVpиfɯ{WVCǺƷȶxCsƋɔ@;Pد?RD n #%(+./32345x67v99::;;9'86C5c3j1=/,)%"2| %I8MN߯ݔORܞ+su&cb.OL<Y_( "[ !x:f`~$ ڠ; _ȥpx¯ȸʺ)ϒ=c,hlQ~'[ D5#(-S39=AFKNSVXZ\T^_U`_^^[YiWTPMHC=?82-'E"j `%c^ª8*9ݖ} wEC((Y˩̮ ¹ۿy% (/Z6a{ F MQ5fza*\՘6Уΰ__x̜q[{ԥּޕ0^18k Z4)"`&j*r.25g79 <>?hABDDDFDCGB A@>2в?GIϋ0#\37Nwރv(N4P ) 9G #l'*-l0A35]8:<>o??@_A[A@@&@8?=$<\:8>5z2/,(g$ |; ^VSNH RƟȾ4)=VɹKº̣πӽ  x#~'^+.O25b8L;#>@gBCDE]FWF4FEAEID9CA?<:87 4z0,d)%!Js X+}a(&r5# ȀƏWNþ!ǥrxͫj]ע"X{Jk= ?)S'!#%')9+,.V//f000A0/I/.-R,*Y)y'O%_#M!@< -cfB1\(ܮ?ڱU(݁Uߐhl$Ug!~JTfY e V =7x] Q !!  `I0qR ^W82f_vqGjdݛK'0pޖ?oZG^@|s o \}AjQ |"#$%&'(((6(''\'&%$"4!N-Q  |Zb(7sk_k G҃ !NѮ\|ԍ8٩h v Ngi- @ v!_$&L)|+-g.y/0=112B2210/~.,*B)^'%h"vpad- tZp7 kDwНϑίwΡϬ Һӕ)ښV?0_]D ,FP!T#D%V')P**q++.,;,++ +G*+)'!&F$1"K A<r a"X9R !޳_ ݟ݇އߟL W>Tu .) i4" 3L7 wT "h%h~>|Yh4O 8~ix237@ = w Bjh"f}.^   uBCz;p$Fh9O+vJK IBm C OJQK @!0!! r  1Ki r M?4j Gyd;('`Nۨڄلطz&R֪ױg5Sڬ%޵>M &4@#g "f%')+-R/02)3&4405V5654x4321}0/=-N+)&#=!^? U e O7i޲ۥ;ТΔ̷ǭ*żĤG *Ȑ3-KϢ9Kݣ3XZG0  LR #&)B,.0245U7w8R999:98'8+7544`2i0D.+ )H&1#aN f~Z7J܁~ؠb*p Dϖ-,ӳrFFg8Jyozuq~m l38' !"V###$#?#"?"! [R jTB& 5]z+#u[SxcSrC W F E 9  We>\\J  ] V?3v+ +w]9z3 .OS\V.+!Kizo X 9 !  XfF"IH-f_ c  N a w N _2-w\3{*~_5lG1+*qdiz|)To$v*  [j|6B)d2|vV 4||J Z @ Y ?&\0 .lyLdߺ7s`k~ݸ1޶ߧ gk+[,x| T o?} !`!!!!! 7=F 9)AZ\ c >~,X$Kw5!xR/xbCR. c +&T:/% h ^ _mnb6{ \ n\Lg (<[  p Ec\Iyw: D <stinT aUWx'!^=;a: [ e:A8I.byHL { R6(f9X8&{GVqT3 XVTX,A%   k$kwp/m!{ V YQHmxGu  {  96"~V0f=6O-f "? 36! H uUB!NMm[ nZZ~ iCt3zZQ^oMM  = ,wJMA~o+y  ] 3 6  cL7K-uKL!Wy*']]dO`v&hV h[n0|(d 2-  mM}5RZh1yjs) yr`F30 %P4n-[8# v Q48  | Twp ]\ O  v]=n]:*&r,YZr|m_WSZLFdNIG/.uvY?  N / $ vD:bx=i |  ( j Zm,TMx7R7mS- #-0].O > t  4  H!vXhI|#ck\,oaVzUr9* RCHi0e'o>  vwZ6 G f %Gv#4Nur?"'G {I87xBpo;&2NHYbAl9Ivo) Nf\X & &  eXlW?Wp~c2xCN<,q 8W;!=3mgx?zgL8Zx 3h_w,[LR gT "    Y$c[(`ZS/f  ( K w  (O7m3zkF/$p^9~ Bc52M/\MQNuf\*5 s &  [ t3]Qx}DLr>z  |P[2f'sE7%=a/ [#V< @Rm:2|o  ,nZ/PYC}  F1U )Klu-u~w{en~+y9o( XF1:/!+@Qs[u[0Dw$S!CIe n4},v}.i 7 3n11NCP# !\-KGRUm7H5y k  DMOP,ho ;]KF#$ d 3c % Wt8vf$R#O-j1zBMJ;r{&9 21P08h7b'%$B|U=Ag( ?HP>lu P!!""D#P#N#0#"O"!! >  % xyXdp[Io|DެgK>_ءִ֯ ך1(ܕ$mmqqs;: Kn4o[6S$ K O A  gEW(P * K hN+1+(O\9Z34CYEi m4oi_j#|f;2;SpaxV.6  ( 1:#9?M0* E"#!%U&a'[(8))O****>*)`)('&}%.$" RqI ovvzxnUJ"!4-57Ք Xrq͍ͨG+[S%e0b]gXLNYjJ! Sb L"#%4&;'(()[)))) )(''&%#"-!1!Js &=sj=VHKa+}ym} <\5VN VjCDW&q]`WLj-D$) O 7+5l !f"##$e$$l$O$ $#9#"! 7W}g T4rSf"1ܕ׶ֶkӑSLӘԑՌ֛qݡߖZ5S 4l T 0SA- !!"%######A#")"W!w ~vKuhIE ; kH}cRJ`cuJzvM9&4}`(}mRav}0vUC TLA& 7 d<]a   W!!E"""""X"!I! < 5 kDa};0ݜ܎۝٥um؃EيQaeݗk4BTo6k| $ANX 7y~YmX I}](| &Kh s5@XP`3` :H~Ig6rnq*G K [ ;{vK "#%s&'()m**[+++++**')-(&%'$|" ;g7 !,?;CP~OoߍݯurҀhfҤқW2Aօsݲߩ?m d^ QX ?<1 j(&Ep9Ey;YPO M ; E O mdjjfE*bfnB߾b\ObuޱߎEREQA\P~ Bk'!#%')]+,u./012,3M3\3)32}210/p.-u+)y'.%"7 xH -qnSأ~ԍ>ͧ7˥ZAuS=4Ώ0م/wiy}d`$ W] !"#$]%&i&&&&&O&%7%}$#"! YceR? y  { >xA#!)3o DW8-86G\C b'}n{dYXUMvnn0K=8  ^ $ '"S#p$U%&&F''''';'&&d%$r#0" VH  x ^+w@ Y޿M%v֞zYxk׸EܬG_0e+} 3@f (OcL I,RJ@;}G q 2  Q+bkusLlxUDA[Tdj= {.;j#iNG\[R`Vs Eq G>7v fVd2a_3P  !& 1SWi/,r18J'0dE6n.u]Ne = p?= n G [ a8$<Uk15oND%68=8F ;ADM_OE+ @!m?,?\wvi`u$Q~o J x.6+6,T4qd-R TDJ ^ m n]k]ZVHv}vrs_Le}"rgl{WD[ nDfWrTK/c f <  > b v { c : *  ~ K f > !  i p n X \ O ] a U Q S O ; ;  ;,o )WQYXK>( d*yT6!0E{{b4 Dz8k05xG6Cc B44D :!!" #U###q#%#"."b! yB0[t|cK E+:?_5_Oߜ ^R]ux~u0{Fv=: )!r 1erQFD^#I w  Em RY4$+n)TA hiW\U&a8g!r[<=1I )t!b"v]__QDa l2g` ]r,r{e D qYs"lyE0jNFf0$_61'-DT Z=jlB > 3  < C I E  P p  q B  U>/F%6DxFop|u(U}{[2Y|}l9n@.2p}@ F-{a1vwyyzs_Q 3  K mn,\n\Sy E  ' 5'd5%y4XYRSf7;RhgU<4Xeq$ j o , (VYE.aQa Cn2LB?]k PT`*V,E8gB3/@utQ<95OmmRZ>t6 \B{HZ   d  J A S 7 U  x 1  ?u4LjWM @_PDCVqd2 #ApF}4h*)q<Mx{L0c0n;\8Oi%@(SChC*O -ti|bt1[4Mf T m B B f } | t X ( ) G * w  Z00@Xi D~{m5fil~/~PZEj%pIL-\oP M J  }Str^WRKQX{_ f0DT CvUK_*$_J~T5wiqv-QF u.y4z2vm=h8916 Zz'q0T]_e{ 0\ Q T'  DYlx;{X?3KY/$BFWVQPB2_#b$Ie#Gx=mN--6Imv!Bm)3SZgogiRA3sP*wH7!{.HwEK$Y m/a!?]rx{;-X>NAx&l#.EgF' .T6jK6Naa5}Gw*<EGG?/pIUCm%V[1UI#)?O\\~$gCoCa.AIVa__ZFB%R.c/^c;oaPA=>:MKfv;e;h#O@k8d  (61D1,. {YAcCLp1 g8bND(!!0CZ|2W9_2Y'I[utnNK9#cVF45luZOL;<' -5Pgu:_l=Cd|  zk4$j@!eB dN36Vv;_ ([/;l(#&t@;yO<eA2zwgl]fbow @Ro %5Zc&!"%"xnN<1s_X?>'#03AOOUXOdZ^kgmtw{}|w{xxtsubfcRWEBB0/'~eYRAJ6-0$   )6'16Fbk+1P_n_1K=NDKMPLSLLOHHEB?;961-'&   #$))-..0/0.-.,)-%*%!  )*&1+13,20.603303+.)#'!     $#       !!'#+$0')-%.)*+'*'&%!  ! &'-,-1/5111/4,0-,+*$%   !! %##%%'&'$$%!    $#$'&+&-*',&)%&(#"!    #"                                                       qTox/audio/original/000077500000000000000000000000001415623743500147305ustar00rootroot00000000000000qTox/audio/original/ToxEndCall.wav000066400000000000000000003037021415623743500174510ustar00rootroot00000000000000RIFFWAVEfmt data >I&RPl]AVGW==[?pW==K,ZTQ 93JnJ->EaL*Jj6a4vY%  %D?|TOO]<-!0wIi> ](<+ESmZn)FC3gSD T n"l"jx3_ Bl ^$ r F  F > { E   3K o  D Tv     I   %  3 O r $ n d  4 ,  - < 7 @ } [ f b I 3 x " \  :    r Z > R $  C ~   [ '   g b, WNp#nRSRfx(#l8rwfuap OXht.$ }A[b^5_((ixs1Th /5m].pD4.s=UWSWW]r>HCIN;f/&;L,H@ 3;SG&a1=H,7HmiZ%EorR>Noy@N;_sb"<aX _$#pXU   4  D  J   "@ S  3 v Q      Z L t Y V  V " d G  s F b ]  < 6  U d u h H '  R   _ < b o  } 9   S "$;&U]\, TB1pLc3/L\n8x-}H,!sA%/Pn*Qi9[uX/#?jJxA5x~SQ5#;I_ZZ L+Z:dRm`}_sZJB_s')R W}A9T*ia9])N so)jwpt ^X~3{.eF~g+@2)OG  1x  ) Yc   K   O M y )" DI RZ jM da k P /  9 a  O   [ $ 8 F W k    ~ h R [ $ z ( 7 6 " " 8 u vZj83r~ d)Zg6WZD r_s<wPWz=mg! .X, l l  g N 0 X ~( ~l  B |S    G X e Z u  5 e  >   &  6  =  v 8 B  W | P X O 6 O ] U k T d r  6"BR2,    F p ~ $ w [ c  ;  C ?mF*i9[%i8#UZ?r~)}oC Xy|{ |KIrQ5/wRl\M(/+:0J jt3?f"(a08 Y |.;P&VvuK08y[3_c ! cTkGB -6RClRwn=yIpty,/MFjjz_{5[3).  )_  1  8f   %a! N 3~ Z j k ZZ D^ (Y P P b 5n | 5  b < H ] U x " z I v  KnB$w!%8 B. W  V ; A l   E A 28_1.a-@sxm mX=}wN` 7u>>n< WIj\o^N7)E-f&]We g{inx X{E b EIO\J;tVJq26mXP$G&??go!R.Pmo*pX\ &vF>X9   ,z * b C +. ^, ' = a  ' @ B + P 5 3  4  ^  q  #  x r ' u [  K " _ d  @ 5f   GTp5,HYSprSLNX7 :  K G ^ x   y HbfdpE#QUV<9, J a7`\T@.'VmUt5d7U"%k}uxW_:]h.i!VLuc0 :@"`U'qIu;Za@G Vf? :-Xx|LWP?Y`lztvrE 6aa`7s1,wl7aZ9_4 N ^ NG hY s  R r     : X n   l *j <    c P   9 i } v 0 $ [ a A Jt  %IV|-8Gek[(U:! y S  8  f g  H\G<Gcof*+}`pK4Q%K2[=tD9*J[D<C {nKJ^"=Sd{Pu?1,Uq B}Ek+{>#_9~a/u_'xNt]BnbWgi"s;k`]iq`^M=4 ,~)x67Y5#`~_.k'JsB =98n X Eu  q2`  a    9 h    $ # { R   K O 5)    } h D . = 3 c F ~ ? J  0 f i ! J PqCjyJ@LRGK'R-F Y  0 D 2 1 O ,suQiO#-XMx;JUlz=S@2'9Odw\Z[@' K^f> IcOA/4Vo%.\RvvYkd<+jce>@KL-6x 6WD$D> 4EgfTpL'(Zi s|=O^u*hdk#[;< wZc\NbI-u   (w e O  @      !       .c  <  g 4. I  ] ' _ \ v F T E { L  ; ~ g   I wNQYce9UeueoB"s _C  X . @ <  ! ? W nV~~1R^rhp~Ty{ZiT.zr~`/B>9<348[jf4~0_R@}?(f3{rf5f&36mn X^' ,`[4a7[yma9& R=[ wl[M0qB?2m) ;_a4c=N~ K  ;S 6  - k  h Z "V -\ +d m c I & L  d N  F m : 3 d   w vx   z U  6 i ~  q y Gdb~$PSD*lZ\4o  ? n ` z8F=B1GF9y!T-!4<%l UA&)V 1J`~VNQd-V-Xhg]KpG~i{b7&oF0Ww1;2h$%h0/A P2u UtF|G}P*phvo'\^!{)[OnUK)xI@Uj7'pttyt{}u   w   `q    [ H 3 + +  j  l N5  ! GR p  E  K  ?9    Ip  % f x    6 H 8s:*lrt6Jh,N Gx3jBaqK v P   >!6aSYKq6_A HGg{A F/W-M} ;q~!KL$j$07ek!H,=U9_86EE*jxqCcu3U !!,Y1(:WVD!$^S rfR uM%Rw;igYdP.U>},*0=yX>b \C?#` r"$x|7$c}>(Q+!/*E)Rvz ^ ( 3 Ja-  !   +      l} 3G  r [ Q  <   : t  XS    k  x W +  k | j  @DB!KQmkD\_|n9`gd * K p I &;n(gyJA4Llzu%VU2oxu/R)hLX%]xU> e'IYGK./'U9sMoqrqfrLfV>$4.t>NKn)i\!f%g"aU^d0(X_4{$? 5 ;7R.Y2uY2ZO8L0*$> .!d).pug"kC  4 JZ$     a)  q ' c x  D  Xp  r   7 { 7o ~  ;p T  y I (2  |  5 J b  L{ @1v6xwZ6Np`9 # } @ F Jyg6K/IFQyXa?KeSm.#/Z'~]7#2LeM3cc}rnol? wr??E ;E/!wY;fQr/]HFPyNnlvN#&ko.}nUF$c3)~0' *&SKoYx},V4M `   u '{  6p1j 1  t a  $u   "  4  A N 4 0H Lo  c. E '4  M D z *@   70 W5&J%VFFw`xmsr|H/:?S n ! fhS8N%U#3vZpEmcYQ,y:Z4nh-~LT#MnP4VW,B20;6^?A!}vz/PH5Umz>K_KZ[%SJ?7"7C.H1W!vp/&`K#Ddt,GvR:>~%HN!_X * ` @ )l  1;+@  [ k+   /  ' L s@ ~ Y ^y  3 W d 1 O   S ) a K z'@A&+r#/5WcKjq TdjB$&Ktq7i^2/ 3 m{^AS*wK'[z.cU0 mE oO'E2dXB/j|jaf6X%{{R(fKiVߏߴ\S u^7mX_dL;sn-Vb:7 m  9B  O #D4g x 3  yN  $Y : <f : }  K  N mX dR>= _  ] B 1   yvqEV5p?|a3Wx|V#zjutRP%R o 6GY5Y CesG6LT57JA| ml1l Biy*LKt}kJ&Fd)aue7%CcyI5:[ލXc)#LF#WAhLtnhoc{)!Nt+}\^E=6Gkb4uuW@Iv &wrk4j Wu=t   7  U=Pv3Z))6 w #    ( u ,   M  G EI %!2uo@p\ -  u d > C 8 *$1%cYlE/QnmH';bl >  g 35KE2;^\Z~Dae%s}4B R |p:!f!']/#,jg3#(e;Lgw'"}Y >pR0u$W;Qݣާw߁>}JlNBWIK+F[?;3{F-!\I~,(+BibTDVV a/0_IQd},y`vl8o $0Xx9 (  J&( O t j?He%Q`c{UV ?  W y wa o Y ;  D  Y R&cn:7j } P  &   # Gb  Ish5{xM2x[j#BF k~ w  t ' 9@1O wQ@$O&Nr!6N'e,e$";h</9Ba3zL5,4 s^o[atN4:Wu>qޮ7wzxlމRߝ. uY.5Wy6;gWvIJCW p C3/]YHTe:\*jnZ?Z {),0o|B'yD$ V6ipkQW WH K ZRz7   1R@DAHG~A yg S  X b j1 Kj /I<  Y ^=+ c] =  <}VyrQ<#o bw\ob?sXfo 4 ? P M3 PH;[mo;sIX}yA Y+aR wNI09]pd _ d `Vu _8P,Xch='Ju9"F-Z}r߯=ܗ ۉܘhI+(.[x~#[nrQ@b9!A~K~.W!gmpHuihZkr.Ps-^4@SEo?zvPj(rw  wf  n .4]S|y  }e?5    $ P {$db  wZ &+tc+  ^ ~ 7^QiE?q%q[v4 }x%.joY( 6 $_ Lj!I Bv'Sk }fR!F4 ^!H?  W MGdU.@fUK|vq)c}gY;f   u D " pz $ n()\  n H Z Z DV;3Nct(b_T$>YH4DL V[!m[!v4  !"DI,M%e + F w  /tbCU=f0=!>^Yac)DcAH*/ckG7/pIYI`v  ("Q8$X?#sO'nv2vnߋcjۺ7z4ܟ%(&:b6h6CMdsOx;BG%$ BfT)/(`ou]:SD8eoU. S:3G ] _ =jhc + 3tK1PS:</gs"/    S5 Z> S  )x } z , j b f 5\T4PsR6O.VN!x"}"6!;;~ 2 B~T    kpW_aG "F+!bF9,b qi/Rt=Gp 2  ; a /n*ALA0 u'uYlp!{ U-ܞaڻވ|۸'P`2 KilObIkx7F`{$0("JYHB y %yMXG'fG@fC n9k/p~gq  e M 8Y}s R d; cR#n">ML/R(T :   I f7 7 ^U;}   H &c)~qw* dv" # #|j" O e&z>+6]w = RS  P=M<"=  }hWwV|C 4  e y W1BIu^TI!!##%'#%!m$"2 ch' Sw     g]$0^-ebSt1KR!~s>ija@*-#Kg \  { `cA;O9Ww)k2tcIYm ֳ߮ܫՙ"1܆נ+GZTzDsZC z|D ,xI , [ a , MT # .b7eqhq@ޫܽ:xAnML :""q$N%'F'.)x'R)%'#%!#!7 [i8IpbmHF % h   ,AGB=D4L NhPV" 168&Bs z  9bd~ H mrn}N )V`a7[]`OXAs!Oў`ԑ܁0Iٟc,+d*yf\p{;vFWN|+*9Me*rmlr v?+p Q;QQM#Im@%O}e&u2"YAff.uGK |7CQw ^ 4 8 xS"%!.(C((8'=&!%#"h!> K g ]Q O _ ]h  v\`81 7 b  >YL~AY 7!d "]"$$D'&w)')n%(R#%A!#" /;OS  zS"] h` mqm_!xG & )kL q~N & @ . ykz !  i 0N^ot=;.jD8"~e 6د(Ղ2ҁ,W|ږvW5a BߟX ,NZj3 EUq.&H//9#E86*@8^| T) Hyp+r]p`*%߶05 w?:SS[ Z ;v ? #a&)=/) 3(2' &$|#o5"H :B0B#O3  - B 5D  DfG!.~<Rgoh k  H u * n~e[/03~z X!&"t #|"%6%(' +'e+%)#a'!O%h*#!b W  0j  - ?Ocv69#4KeVR*\DFY)WbRr@sa ;;.J G 5h 0!Sa F oP7"o6G>w13mOxeٸ]b9אϙՓКTa٬ՙLwb~: ߬;`-YPwvjT}4%rJw`qQsl(EhgP_f0LI  >]v J  sN H#'E7*C*w('&zQ%/#p"!Zg0 Ti n   wd Y v; gp)%-9T|{1 T 7X<%0 tmN8U }W"p# $"&%)l(j,(,4&K*$(!%c#{!aG*a B | q 4a OkD:N%)C`?.WP; X/iRuepq_LM#F[  & F+M  zCkD(5kq{lD>ڇDמܰ>Gи,NТ%b w/ ޅ o-gJrx-$%dzo`* ,;sTqTejYGT;gcD;c+*q;}+WsJ@lA ; ;+h  d?zrl $;(6+)+H)(~ ' %#lZ"+ Cv>Ai f   Tn & * *u^(;Bj-;m. L ipt;|~-Pr !A# h$"%D$'i'*X*-'*-'s+%F)#& $i"H1  a07  gf   fd#q0`#`<j! px C Fr f]9  O)*;[g+ ##?uFދfكSh^ך h͒)B Ӥ:iُ9ڗر,ۡ!n1Z?vd$W*wSc Y@5 'Q%1`qS2{"fF1g^/pjH\`dJ[n|#D}Erx|MUVtz6k Y{I3<SH#iJKD8M`?fQZ#K`Pe/qz Y[T_{ 5 kY   dX  = y H  ; V  w4 C b3tde`KZwq@-c1p_KAxlc/WgYs#-p |ageLrw6fK\q{l2H-9$|5-1KhMv`(w)`YWXd 4m {&~KIC-Qs>3b\ bqP~Q78<yN @Rc@\!yznR iqVsDwjwO?J>HVNiq+kFG]~k3%PI4P_n~y5 khSD K< mf ~ c  e I   O % - . H J @ ,5 X  t P<  =j#0ok67C F{r`0 PjBc]k~\;+na=&)r(0;ojO<>Iri/ g|aTBjQ[7sGvNk^r&?k%3{>U_f>6qO O ~ g I 4 t I  P 5i m HU X x h V ^ 0 } p < t M  c %I8 Mgr[Y'tJg. o.?W@cZ('dPU]*UFv{HIaa|!8S5VxV@ x]JOacD.,H`p}To`JjX&*>EXui{'!WNH"Cx'CdKYup+o`R[]vlCAt%'y7%vb`*8l(fwWm5WNy#4NdE= q: 6\ p    > ej B 9 W } z j  W F S @J (  P" 2M b %l Kv {W .  g a { Q ' .Q b u J  tSQt"iU4;[Gua$oAQAFYNq]; Lrv_uL mq+/jBA:z*VFT63RAGxE=La#tKD3"5l{dl@StK(V&6[,RgcB>!9ur : QROFn0)9Od 0v`s&qUEx(   ?    {E t  d C. Z Q  7 " ^: J C h    !% q-  . k p > u Q  P (N~g<nYmuor[b;n9^aYjqecH3feI=K $Z&[`N:Yf5 U1IMA=)7hQJ*x'cL,Et`H\DVRQj`xcDm=H;jWR"oVtsu3C };b zhI7p_= YQ; ;_mL^H]QUBy/=hw/{t8T!kqe_DD1  / t7  5   S  c   >C n = v 0 N a ( { .2 B  \ i0  d !iHYcRoU[7?Pe@{"LvNJ4/T*KGJTD:)/4a D,Gj~.V & XL5 VY!t}JZG'FVwB /#E( (HrvU k:#] !?-o73$bG)z8T,hp(R4Etg67aj`U^L12$A :E]I)(Unx& o-uTC6U&*wQ5 i  /  R - g ` ?  X *. / E S U Oc j i f b8 2  V @ + 6 Q b N  a{8~]*LPrdXCt!Cx =W^fVNLRU,QSA_-0r2TxgB3 p<(tSR;aumR& |!'9OIO&=^acRayrv=)g]Z |TKA"zl`n{EC!7#,ATh2 gX .b \-F' Q 7@(oO`8  `i  ] .V x c )  >[ h Y G  Q 7  0v  A U } kf 4 T }@ F iG onc;{X%~Vs8Qg{H7} A6jtWN}!,X=*, TrQ{2^uK?ST:J/U?/18Uv\TK55>|_RDTka|xsg\C)'$l'E <>1z3cET/cd5/$xD7]h4>~!"y*V3B-&+6D)[9e>$@P\WWAA A(t e~   - n\ - y) w C x    Z 5! .. A H J OP ? < ! B 2 s K JG + k G 1  p6N(",|DM=OzMV; R9~$#A.YcqR1@(Esw< -Ykn4US9'xU{a\7 ' )kRrxOH$?HAGI4L#M+UKYyiB+y7mDmn`V^8bzv*EqI8f"-$4^W}T$]}lMAvjt9noP{V6Ad0]_w`i?oa^3^o"fS r  W  Sf *I x .  A 3  0R10K H  VN % @ 6 1 rX  rM;uFA i#tuMG8>BzW`cGM $}R] <z4(7q39 Xp"7J>{ >m?Al:#I(Yc 19 : Y x 1 B . / m*|82vKNRi-Qh2Y,jdA$@EegicMjfsme[F"O@kJ]jt 0 ] ,9 ^  p U- -K Y ^ Y T ` -( e V \  ; v  I JY ?) MXA?$[c31-\*_ 1t3"il qW'utzoUL0?b: _!W^4~Y+i.CIoI6&T99@" + .8 W Z b )n b~ v I5 VM/O/j*"6M\U (w+h&j2a99uL1h="?K3br |[F ep _#E c]  ]  Q   s m % L e` 'G J Y _ o { g W M D  f d  K: X P \} rrHh<n @hw)au=!HIkM*?6YNjy_#|tVH(" ]'uT8[fk{Q&y5%8%'1ORWlRO93J5t=WFCC,9?'xr+ k 4) x8 C O &g ] l 6 W %t  [n2#Lwaw";5sJ *f($6 =.]\+xISm!19f!<8MP'/~ ?\Cz}Z  M[ b Y] I = -( n * !r $ _  /^?#D.%1@<b | 4 d R8  h\  o 4 (( G K ? )0 #&+T hX%5_/oQ xeA{(fsAEk9Tsw|$8h4__ +V:z; YNKgPi?jY%'{H'oZOcm-}%zWh]h5> ,i Z0J n & o. K n , t R7  Y  U3w&q N+3oH! AaCH`Ho.+l1 B|QL"S3lgX/6l28f  j 9  (   |  4 |q u 'dF:o' 92JVo t@LC! ) G . f nP 7S  v % y <Y W- f X k y{r3;3pkq'9"79SG7i[nh`gO0woo5O F/ "{2&830ib^SP>%NQDW2!"ga=qI DgNN8{ek 16-=ZuAbI8YUEU`dXl4F  h e  \  E b<  $ { t L I>),L:QF5Rf zZkm% S+2Sy qDR3~fP!7xK d  *0 /{    Q   +" Y V  ;/ Pu J .  &Wckh_TSTie0tZ|HC# d B  1  : r | 7 ` { / F w?Fk9o{a X 8U 4UOJ+{z83,)@xM+M3[$WTC]3Pzl-d\65CzV7P3$TtH5 4[50YtY9 Uy y a   KM w " \ A  )% =,1I[ aISYU-ZO(4sFV 2fo b*~+_(\a d  S u  C k G  ,   $ Z    azomCWzEP (6[ %f  m . m Q < y 2) g ~ @ d FY ,}L5]D 1 s>y,SRkL@X tC-0sthH>Za|/pZFV|JAM[[&/"|k3 R.#E#(\5mx]O98n06 ^ y   o 8 al  sR\7x) 9CN] >{Z0wZ<]CE ^ Q  _    ]  Dm 2Dd( OOpH|QK~}&xJ@j1F4 O h O } L *  tB x E]    8 Cx 8] & XD g 6 : >F!_8T H}qB{R "\nWY^(bmC7<=&f$axCp`8i4rKj*|S. =nz[PkPNQcy=$Q &2%+_^>=-qlD {O [ U > : > # M 0*{Ir!d}H\j~[S@>Fc8XCX,7$@:'q  v N A  l   m/oil^[;)}}=I/9" %4_-f8)'Uo[d! Sf }     rY ;  { R | X  gv,N L@3>/XC)Q|cf4LyxnQ{aiB~.O"wZE xjh_&4GBkb]`v"P0!BE`}> #H/|qfd! 2c ~ z w } q gP & voX)O+ 1K =*< q,RU{4N3OvN5`  u    V M ; a R E  SFw~NQ#bDR. S OQaZo_UV5[6 7Ub5R.io<6% t    8 8o @   cN % ; &(%>\>,lGN~5&0Km2`Jhe+JA8Om0U<%udS9St!j|JN}Y6wi\ &gv"9=95EZ_%PkgmA o & E kk  |^OK Cj:M -J5utA#Tn8@mDE?X E  ; J z @ ` S " e d ~ a,PJy#~ysxe-N`|J9,$)T77a'G# ) 2y  / q"     nk %   ;k ' . F Ym"QWp#"u7   M!fDt :ax}fV(df +f:\Eq.,qnYF%mS RLio2`j,I/}}^>" SA^` Z=Kxjp%JwBeM7w,Iv2^]>.\k3@U[9XtOL`OT)l[FGLM` Ts5bC A [ X ]  x     ?  Jc  u %.= MbYT1zAjqpNB.j/l c'd &  Tl lF u( a   8   ~ C   i2C^`mdTN|s l?dIyE,hwO0MlyD3sjcM!o=}3Y`N bFzwdD-N;mzZp ikdLl&;alB/o_? nN,X:wrPZ,~c;va2XxU+a7^&Mu : 2 W  k   b ~ Ek !O  B [ . H ^z5@M<"H/W,HIU 5L(Q&n zlKuO\-H? t; p   k U \   ]w3_Oe-\'`z,q0S8vsAi M(B9:vV)*YiAeD.Dq#9PfLaAZ2QaULQO|^ ;gj#W7H`1u i1A14'3'%_E9vY2 q`!eQ{@krO{h;U@&d >    p i,-  n "     !@ @Rt/C RL>|fxcfx"0dE2[5..H\gA@ mh2}e/ic   K |  & c Oc~x_=4m,s(ZQ5 gR9v?r uyd1X4xpiP(}o;_{r\HP$p.^qJ ac l:`FzH8fDLc(BB)Wmztk%2bH77vC[_&XyL2q?k <=ddzW3,P|Vs %Z J < 0 c? G 9    ;  9 G sD=sw1NOZWb\FA@rCbLaoNllsK6/|WnL<I,=5+Y, q   fe : B Juu0nK$HyVA09YMr4::U|Od_!?:xB9#d8".>/GmpO}qw=2ILt_5(`r8n(6j9mMV|>[Ma@S 8;|;G)^5SH.>;a9 Pf%Pflx @>    mK$No  z T $ (   { .  M{ q-  sBho+;^J/ t2JupAv7=c J1>:Ns`Cc gy   J k 7DJCBM^%Iyv$^9/CY2]5H .T!taS- Gz I(( zd)xw&wgXX;[=r-:+b(rz~|  VnL}9+FoATfV1f|7/z<`gtnf cw(]^,z4%@MC(G}5vNdD60xZu}N4z$_Or703 7  g 7 8 W*oD/y  |^    2 GI 8   <  ~ 39 c    q W }n`coO'y?k3n46b%"3>X:D k>v3PeAv.u~cX&    4 b=ejmnZZk{ XAjK|d%/PF08wUOv K\ Kz2}RHYG0Qn4#e[>CrH\0n0uV}FZ6}RmMAz# c;BjL>3>+DIFc{lSS/fRJ6fx|b\7   ; K[8B)cw`_ _ LO   = a >[Ei   p 'H E#  G  I g gn &D=r }w^J{,i/` \.o` a~^S#G$q)J _  C"  "Ms j6>6+8K#l j\BOg '` pI{g2%3kJ~M@EA(6p^fQ22"uwk)pbGQ:P8al8 y2+qDa$5svlTWp"KBD&'2CF#H[O[/SDeR`{O6 ~%^R5aLR^} d   S</KWxmW1)   x 8  u   T > 1   | u     _(    M hT;RixA9m 4wN) M &6)?6j{z!F < y  ~ bz{St)m:B[K)"8W%/\(E ^ ]  g%GtY]V} e R U _$ir({)jZJ)6 [  Y  u  D 3 H  T , j L  /   l ? >wytL V)1Y FT v  ) D m g $  F 8 m - c& $ FP jQfj=i =W-l%,;Zn8q^cvl:"'v0\vNDl,OSdz~aNޙtq=3ݭW +Lml}ܶR;tx}ݭ3=޽tn{=1hL':Umx6\m;oQ,3&T%Hfur[mEshE 6v:  "}  7C ., `U ` ;  Qm TJ  /)!S!E!V!!"j"""u"""J\"{ *" " ! ! "n !!n!!c!y  c J; mE gV ?7 VId(dFSN  i   ^H    6 E k Z    { v y w ty e . _wR)vJD%xC#S*K5X?ph` p<\75^Pm@aB*[- a1~޴ ގݖG`TJۂ3)Ttnك>ٲپ%bt Jێrܶb6@jWirCX[ $dV67i% YbY?8*CSWW0stHTbIP| r T % [  R Dkqrw c  W ( {,7hO  !2"q""m" l"N|"}""T#\##w##F# "s"}"j"^~" " "B y" x" k" " ! !f ! ! "X "! ! ! 4!m  *  u4 2  B X +$!v , > O: h  /=  y  p J` *tVnP%+Pk 1<+UuHxF^AI0O~[eT{eBl0j^38D7߮e0ܣis5)=ar׉ ׷/C׽~j'g*{>x@bܔ_r#!j Rz_1:de Z+#Mm,"nt;<Ja fv *by 4 T .Ab~VnFE" g  f 6<Gv U !V""#p#v#"".#c\##$])$H$#\w#-#0"#'#8c##$##$ $##s##=$aO$P|$8$1M$#h#"V"|^"A"Z"!Em! 5 iUq;gZ#k O  /j * ^  VXZ0qKWM"!c9'"LEUY/&D;,}` Osܩt@&گ٣Op&/U;jziՒ6պ3Y'yֆhY`֥}׌=;)R%b܌ܳ=ݻݯf:k?=r)"!.$$!$5%z%%8%%%%FQ%9%$P%%%&g &U%0G%$x$3#XZ#6#!"""pO!z (kwSW?: ev \ ] q ;  < q ak tzhS?=*c*GFYJg]yACh0a/Ai 9#:8 f!]3Y݋/&Ut ٴ؛i(C*4tN,ZՄձKah %&֌|%"ם.FKXIbe[wefRJ2HN?Gc`UQ<>;p"QP`x3%7;01cz W DekB9o  u  5S I4!'!v"XY#C$$]%%%~%%%Ae&'W''''.t'\'8F'9'&C'c' ''H' ' '7P'&&&&#0'G'O'&K&z% $g$$^##2#"! ! %M]48apOivQR}z r 9  1 ` W::}/,QTz\uBF^9~v$#u#%.3M']WIk$>A<\GG܌ UYײ׶ד@ 5PYa}ԍQԸ;Ud՟iՏՋ~K^YE׊5؟ْ1۵ۯP/N[j`P97lYd;0*"`_Z"#,.uH]m@BY.F  ! Xq?8,jJ^=  5 Q CozK]Pr !"#$u$$Y$$Rd%&g&('(K(=(C(fM(o((J(%))=))+)lx)(6(W''Y''t'?P'&%:$#@#"A"O!!G U7= TV#Eij|do!     c | / b O : %%f:kLTP (^tmW`znNnr* XH/ 5ekޅ݃rhFaڷ;/؝؛؞دFخ*K֦^־7o׾ׅ~tNJ׬O<*x@3ڵ>ۯsܡ]=FߘFyizc[;hg7og#/<=ttBf!}a`j] z 6 Psc t $e r u ^6c8+j.!-""HX#9#g#I$$%J&'8(((((8)))q*k*E=+`\+G+A*(*Sh)((n(`(9N(>'(?'=&t"%$-#" "[!  `(M`cPgW d  j (  L ` G  svdbW|Sjp 9|Z'N d@Ho:$Pz?PFIH(3ݱXBv^ٗ2x{aFټ/NًDثgٔ ڞf^xi-۬MiۼfrCەۨr܉ ݪ JޗG=gߌkkl2JcMu&3a]P~U|M)/Jh,  B)   ^ <ovmo r   B %TvC-Dqt' h !!Qy"#$p'&$'S'z'=((/))}Q*a+L+'~,H,,N,,}l+B*1V*'* *)q)('7>&%Q $cW#X"v "w)!- jtPI+vR[2$F   FZ  z  !=! g/4v(9= 6(|J}@DeY/ *eNep&ݹ݊^l|ۧ"ڵZSp؈W- Eٔ v[LPCnT9p d1ڧ FڬpcUe}4,Ej;ڑkb]fQ%ސo7 aOLzKs&L=Nt#e {G[B\Ds}7V<[tX u * T lB!\d   p -\ 2+ Y@ Cq 88!5!z?"(#Je$%p ' (T((S)%)ZX*v*+`,C--Y7.G..-,7,+++S+a*|6*o)E'n&q%$8$#"s![ -T2 "4* J h _ ]  4J V 1   C`(+$kWVn&r `Q"bSmQ RV@0l޶\ Gܮ -tCN1Vנר aZw؛חK-׎ שYG؏؄,YX1"!'9=P|ْٝpُsd؆٫>.g~r${TMߦh" !WVY Aj8'\0CVW' +2 LN!d7ElO 6    b ].nK !e}" #\## $$%E' ;()\x))M))<*Y**+>7,,L,,a,+Ym+E*V*I****)('|&p%R8%A$'a$#"! gFy,ng\!6I" % iS J  = V qwvY#"wK6 XI-IhujVC2.$ae_:e޾t5vچ=S8#?G~P R!^"n#r7$,$$$$NW%X&9V'[(b')))c)]*x* A++r,Z-  .v...k..(-y -!-"c.#.e$.$j. %-+%,6%+!%*$d*$)s$u)F$($'#&Y#%"q$""!j! !O AOv/8G!07Do7VKO f  @ y L Kv%=C1$q@#6|_1ޝ-")HFٚ9!7qS; <Ӵ߈9*ӿ[ ӧtvӈPӿޗ`S+*':(ID֔f4 "0/6&-Kr؟L1~ېgTSnޅ~__D  'O!pyd6?H~V@y L*A4C - Y Q  7   / _\tLN]h/>[  "c-## $'$p$9%sq&'(-)*+jg++ ,.y,,_-h.v/Zg/q{/\/-/. ].}!8."[.#$.?%./%J/[&.&-&, '+&*&Y*}&)B&h) &(%(o%a'%n&$+%#$##`""^!]!~ * |;QE(,P(lrT 6 > r  F :Sq.y8 (SL 7` &o/1r cA>g/) }-T|֐Re[nuZԬR2ԃAӫޮ1$b݄FxѦѥBҿaO]8Llӌ[DST}5S״\HٲٚsLnݘ#ߕߚ&HLB<4}0B0 C2  z ]j!$F+   &^ 5  ^ m 5u_v !"#V$$0%L%%&t'/)$T*`+1-,I,Z,dW-K-(.|.*(/?/= 0!0"N1#p1#m1$^1&[1U's1(1z)2=*2*2* 2*1*/=*.)-)C-r(,';, '+S&*%*$)#'|"X&!$ #q",~!h W[/"`:%~nL( m 3 { 4  _  i 1 5u S p- HSVoV;Oa0sL6&.; SaAޑܶ6yؚ4eHviҷݰ*ݷҾܿܳWqaYJQG܉-hcUСtcщQUN\NѥRKc Uҏ-D<1O{ sԣ%EBױkKؙ&{߰hiDP`ܶޗ|9 ݡ=p0PXWdW`[DZ dg y T@H ; (  mY Y 4  ;Ee]R#IYa3 9!G"K#3S$1$U%p%o&w'i=)p*+,$k-U-2..h/00M!\1]"1M#52E$d2h%P2&"2'1(1)1*1+1,a2,I2,1,e0\,/+-T+,*+)(+(*')'(Q&'4%&#^%"$!" !L aRQ+R;}t6k^w   t n g [ 4 2 ]   o H "  ]b1-(jIqw8 zB <)dXچwٯ{Pj߈@Thү"Ңj6LTۘ`EۀKY9@f\ۈ:ܸёѺѪVޤ߄ѹߓѐxLGf"d_m6yӔ =ջք{օNVثa9ۇ݆+hܲVj6#4RBy5^~8\IIH_xP  . ~_#  O *  : u   o{D%#e,zVa L!2"#~$$1%%&f(5)&+7z,o-.s..v/!!0|01<2 2!23,#e3$O3&!3a'2(2*2+2,#3-3E.k3.2.1.?0g..--K-,,2,,++***)((''j&%%k$#,#"!!> %Ls[>$.8y]:; C X ( u x : j 8 c 9  `  W y [ x } e %v;Er`H;1ۿڋ!u9J/ԛ҇j҃NҼxۤҘڰ^چ7lѕІТ٭Ѕ%'яanG`;Уtkxs{+=҅ӭiӿӺҧ=KI?q=[՜վ ߦ֚.=.iܒ3݇fٗJOه&QVۆܤݤW7xsfHHw]Mkw+1|mT9C $+  % Rp   @i *"u}Ij9*>N0a!"1#W$l$Bm%& 'z(n)Pr+Q,k-@...+P//A0711]!N2"2$2%q2{'2)1*q1S,b1-r1/o101q1U11092/K2.-2r-1a,|1+1A+X0+/*.)-(,'U*&(%G'$%#G$8#m"" "! pJM5 {,, o % ( j:   \! 0 g n o C Q - B E g1 y ` S r {2NKJr rz5߮><U-'X3-/ro7]Vxo ]n ըa@ҙ֟ҊgҜؾ@Aڋ&۵8S݊Ιk]>q8x'Pr΋͍z@4 Ͼ&`=+9C}ԳQ2J֕ܛ׺ dZٲ݆޵ 6ց wr"٘(dI"A)s}^bS{O[ a*X< q   ~_ E   ODXiH@!"^F$m%Q&'])N*~{+D,'-<"/f/006079000xa11c H2!2G#2$2h&!21(~1)0r+0)-a0.D0z001/a2K/2.3S.3-2,2#,1+J1+0 ,/ ,z.+,#+ +z*V))'(P&6($'y#?'!'&&%% $#,"F!W t l)2  O 3+>$ 4 g i?h/ K x  v/7fa'jtpN@[+Zڞ؆&٪XxG@W׹Ҭ)ҭٺv[8lм۴ϔ΀͈vZ̞!fˏ˩|˟_oDL˩3̼/R/NܼZܔۊS}ڍ׬`#(؅Gײj޸=՝F֎'؏)B޻,A/nnOB0 +ijAB  G w } q   C#'E3!R # j$m % & ' ( *7E+t, -1.t//</n/j/f/r00 x1>"1#2%1&1(1g*s0#,&0-0/0|102/3k/3/34.?4- 4M-3,!3v,u2,1,0-g/,-l,++)*N() ' )%L(/$']"'U U'w&x(&R%#"C! 1^h->* R  lA ]5}HJ\l=H_<C ~ ]a%@@l$!I@Lfr]ۙ X؎C9m-բ?(ؗкo3.ЂQۏݬ߈L̤_̵̠̓|!.͢$Θeߚx#ݓ.ժ+ևוmِ۸׏հԼ Nrԡ x Wzv!!݉%GDpxw_gN1g&z~" 8 w   C V+ V9okoSgAb!a# $8 % &{ ' )r )*?I+Fv,R-.//2/// 0!0e ^1n"1$c2k%2&2(]2*1],%1.0/11 130405q06 0B6/W6/6].5-5v-$4-)3-1.0-)/l-]-,++)*("*'|)&)M$(`"( ('&S%O$#t"y! 7&rtPfSZ?K4 O\CX b 3~ /&Wb{q= "Y j t O Xbul@bf,7tI޸-ޔܶ_5ב٨~/:כ ֟ԙԻ06KѾն{Iz!t'$"ʯe@1>ɝ߮ɫߔxɶSɂJɈRLͲ4ۗGДPAT9ևb ۚ*Auԕoߝhl}S=o'ߪ&HJ|OyCJk/TZr\  mp  f ; Tw b7-|v4 "#.$%J '.(YP)*8+,-// 0w//o6001!1"a2B$2{%3&2(2Y*1,Q1-1/E11f193z14W151C606Z07/7r/6.6.5.4/4)/2.1d.*0-.,k-+E,++g*l))'){%)w#J)I!s(0'%D$f#";l!f *0rEUVv+. 5 ^  M  / _ 6  Y ii J ( 9?ti*\*IFSI#'?ڞ\CՏ+Ԩ:֖8X +HКӁ^HπΫӌ"/سʆ c0w[9hzdߑˮm |)ϐA %zўcҊjdI_ ,_Y|u܋9ݑt `PԒ'|׳ځA+$ 7B3 li*Uw^ $"vhW q)   ` J  6kx  '2v ":$-?%& &'(:)*K+,-*(.o.}.z.O./ /"a0# 11%1I&1'1k)[1@+0-)0.#00u020416#1,7170808l08(08/z8/7/6:05h04/0J3/1/00.h/8-g.i,E-++S+*9+(0+&*#)!('%$X#p4" LK-.w8$=  {G 76/X_Ng . a Yy s)h]Q] i$HEXhېIمi׀Հ}ԎWӋlҖыM49ѮC8ч?њ1C>$ҘFcY!ʜ7ɪ_gɿ{!*5ʍSܥw˲$3ͧہںvه/]ش"M' 'ׅ--؃"յռ4{9"{(*Z6eӍԿeܫߠ\}g q#E;)tQ2WQjwsT_13Co $ . r q :%,~p<Fk6!i#%m' 'v(Z)5*+,k,-w.2//n//}//s /""@0#0%s1,&19'1v(13*01,0-70/R0103g151C7*2u8>2b9/2:2j:2:1:15:19=282t7 3 62423<2V2]1H1~0h0/d/]/.H//,.1*.-(.%,#N+ z)e'Ju&V%#'" >M #UA]?A  , i <  1P!Fc 3 Jd z-Led$C-LAuUw H5 #/%'q()h)5**+,-P./u0112L 2+1 1"1#1~%2&m2,(2)l2*1,0:.K0//1/3 05070s90:0;0.<20;^/:/8/O7050[4/2.1".0-L/T--h-+y-)7-(,%+^#* (#&%K$;(#!jh SNE~z4iE 7x u }L NqZX G  ACO "u%e"4$p4%4+'4](4)J4+`3,U2I.N1/01u03060Q8+19@1W;0u<0*= 0=/=X/=/\=.<.;..:.8.Z7.5.4X.r3-X2W-*1\-/---+.)-',%>+*")Q (&c%I$M#! {-x6qd+;Y q !9 jo q uy K u r-Jo kk gmtM$U]LQnOSB8OqJ܄٘ ٝ>mVl1v2`Ԡ7|ь}н̝h,vD=͜@į@ç#p5gUנmض*/ڭLTM= Eȇٍp}*טϲЛѸ]p׋A"xxMީԨ߄Ӗ7l5GIϸ-Ͼ `хҥ}ԃVUt2unBfx)"BCZe)C5p  H E/ 6 n. c#7&()+,h-.a/C+01.2H3A4]556 <6D6J65}!5"154$+5>%C5"&5&r4'B3(2.*0+/-///02^0~406080$:l02;0#--.e.y. /0:112|2 22e2 2"62B#1P$2"%62%92E&1&0'p/(M.a*-;,-Q.-0.3w/X50b7@090p:/m; 0%EI[H i }  ,4b % g~ S Ie ubJbALXsrEQ0iatJq]gݸ۰׸գfؽEբӑ̙-Ѥg;ˏ̶$E"i$ɣ{Eș7.$‹~X9աċŰ׹Şk 4˗ٍH$ Yق҅ٴՔ(ۗH%+{ގJقq&|լN;8$b*ҍ0cmҾӥv#\sCoI?C[FA]);mYj  _o  ; 2ulARd!$@& )F , -..^.S/Yu/~00011Dy1111LV11#!0["L0#N0Y$n0$0%/3&.'-F(\,)+++G..,0-3B. 67/~8/:/>0>41K?1-?1>#2=V2<2;2;2G:l291=9718[1716+2a5N2@3100-.*D,2')#' %#N%" +w:z6Rr mE M v u %MOf!I  *I ) X i@ Q RA*yJEX"^LTd&(f܀8#_Եͯ≯Z Hʸɮ^%ŜQJDZ×ƺ-ūwȽԽɔλ;Nku^ZJנ.V /˧ً^o*:ٌ A8جL^ډ>ܝZۗ۝EJRר7AZ ѧt9 ԅ kً353[߱9*ub Y'  n V |we9!?@#&%$&(?7+ -l!/0!2!V3 332@F2262{2'a2)1F510&0/).K. -"|-)#_-#]-z$i-$-%Y,;&f+e'}* )"*+x*-q+i0,L3. 6?08?1:1g8>8I=O9y> 5c?6F@7@#9@H:$@t;d?<>`=>=W=I>}W;U>;q>9]>7p=F5;F2(9.5 +_2~'.$(, )'$y wH5  dC 3 o`F g L k  W#0aYHUhXU^4@ :v߳Y'{~äɀ@.`dMʆȐ5>踑uKÏf,oo̶ΒS%oeeP;ˉבͬC>\ԮB^ r٬۩ݜKW6ߩP)^; uN]֍sԌԣ|ՋuU 3gG.ݻ݊MTߐb& w}uX,^  gn _#B.&G(G+-p ]0" 3:$5(%7%09p%9$'9#75"6 655l5p4Fv3a210k/% . -I!-!,!),g!+ !+ -* 0) 0(!'i#9(\%r)'r+*-y-&001224364}869D8f:9:::0<:]=:a>:#?:?:? ;?;?5;2%8Z/a4+0o(-%5+!u(A%_!'gz>  9 r F`Q")Um&+(pBY:@]X~FZEcCnp-I\F|w6ܤҡϏ5χɄmP̦ [r}Ȼ5GŽB°k2?²=ľG&ҹλ Ygξ}Ҽ6>õռȺnL̻vY,10Nؖ>j^\4y`J|d7׹V/ׅZFؤUGzv#ܜ2PޏANApZ-t}yO n 5#gP!$& )l D,".Q%1'4)7)9I*?;);(/;F'9%8#7>"O7 @7s65M4H3?21f/.)--s,+r=+}*)jb(S'&u "'"O(b%T*( -+s/.G11z2[4{36486 :{8 ;9;G;<<<=(<-?l<@<@H=@=@> Aj>FA>A>A=B.;>I<?@>@?@=@>@@]<_?j9Y=5e:16[-3/)0<%W.E!+( %b!J6 pQM!j@.JEI>mG2+c^nY8Y)<N`$PN>\;(L q K-w #&r S)#<,% /`( 2*5,%8-:/.<-p=,=*;h(:E&92$8W" 8 7|65J42+1/.,+*b)(~'G&W$B#b"! !" $5$'&"+({.*1#,4F-E7X.@9/;1+<3=4e=6_=8=9=:>>G;>;4?P@>e@F??>?<.>:1? 1G?8/=.-`<?=?->~@>A?cA@ASA7@wA>@;T>8:36/23n+/L'F,l#J)>&"!2ug TF :-FQnJ=4^*(V&'M4:[sk 3QMW{y֤Ͳzq͂# džJ*:ټ<.Ӷϴa163c:5=?7?7A6@4s?2=1<;/a;-:<,9(+8H*Y7O)5(4$(2?(X06(.'-B'+&[*%($k'l#%"#!{!L > u ("$W' F+/#Z.;%F1'3p(6)7`+9 -2;.}<#15=3=4= 6|>7@?8@9@:A;B=D?3D@CABAl?C?;;ĸѾɒ$̀mΔDž 1NBѭcqm׀TڎH89G@' FvfWWg5ކJ݈zLN cކwݰrݩ{[ޘ߂$`3pWtJJ DT Wnn"}"%w%((F,+/n/32j75[;7>09A9B8B6dA4?o2=}0)<.:!-9+{8*6)4(2](0n(.Q(,'<+6')i&(%^&l$$#"!Z P Q5 t#z'*-!0~#3A%*6&98(:*;,.=.=0H>2><4m?5d@72A 8B9C=:6D;cE=F>RFX@E*AD@A>=;F9743&0/++'9(#$. !PR6Eo. S +=quLO=ow@[rb*??}om=.p}@lBZdA}Z܆o4h>5ܰ"ٲǛҊSϣQ}@Ϳ߷>q1lήcɲMAR\o?ʣŬk΀.O%، ۈ݇ۇyPFYpcZ@M1$ ' ީNط_~=8A93 5Z.0)],4$( %GT!n@ Y %./ J2 =3ߍg+DbqnwuHou)v7Fo/L}<S`I|lϐYو}nG@DpϹ>@˳#-"%)7$MMľxmĖXIГԜѳԙב ۆv޾c6^{ii&eދAk+EUp/!4%hpۣF.Eܡۧ3aݟ!,)EIT  E x .mHdKGT"U%"9(%+(. ,\1l/4387;;=> ?AB?B=BN;AZ8,?5 =2::09_.7,e6,+4)3%(0#'.w&,%+ %Z)F$'#x&"%!#X !}Dos'w $(, u09#3\%T6'8(:*5=,?/5@1/A2Bj4B5C6%I?H@|G@DD??*x.O}@T H\{1ޤt][ՓߦѢͥcʯKX'UԿ|ʗǂzË}4EȰЪ,,G}-!7ü ɑʌ زw2^l@XC[z8cSYsCBߍ@ڜ7*5u  #(&_"A)G%B,#(<3/@4LAy6QB7rC$9ZD9Ep:E:F;H+}͚ѭF-QȝÎ4_ ?E#.<֭ϯ²ҹ}O?@(pǏ̽NЧӹ՗tLڬ&ݒ#8-a^l[|_l>2ߥm@rf|dۘXk!r٠Kٸaׂb׏׫o` % v7  >X]!"$%'(i*>,B-/H03377g;;>\?2AOBAC@C=A:@7=65/;290}7 /&6~-4+s3*1^)/7(.',%*$)S#(,"' & %# c:2@h$!(%+T(/*1,}4.6092;Q4<#66>7?'9JAb:BB;C;DM?(:w:5G50L08,+ ('I$B# ##*^. ; Ql`7Ke[N/E$u6qA0>SQ6G_{dHCzo_tGR}@bPR*ѝӧЉʳ̂ǻɄƇêxɶⳍѰ춹ڳǰeO,/3`%h->1glǃ,wγ' 4Ԫ[אZ]ߏV.bFmI+XORT 1-m 9EdڢHTNۣPs Km!# p(S1; ##&&[*)--1d054:J8>BU;o@8 >66;-4927/06.Q4D,1*/J)-'+&)$'"&!H%#,"g9mg$!0B%(T"',%/' 2)4+176.t90S;2)=4>6@]8SB9CI:E;F;FH<J>K@MBN-DQNDKC(G?sA-; <6621J-#-((f$$G 2r\= IQn.;W!ݶ{ݿ>zvwO=hNYt _6u2X3'*IO[݄ܷٷ؀Ռјuʐ%llģԷ;lr޴\ұ7Iɫ2ݩ%w˶`κﺞj6Z5ɳȺSЫζөֺ7x|d^ :C:& bz-U3gxu ߟޔݧ۷ q0սYѶ!xֲu܋Uy], O 0S!% ")&-,*/.1U246\8G;l=T8:5Y8346613%/1S-?/+=-)C+'7)%'$B&T"%k #b&"dmsZo# &_#o*&-U).1+^4X.6709$3&<5Z>7@8B[:Di;F <5HM?OkA?RC+TuET FaRzDMh@G;B6=19,A4'g/#*%n!-  ^@dWRXُDCYHxLS0?8Qt^ZP%0BW@P2=,\T*yb݌ԾIйEES1lb v&}uP&٬ci"¥(  &0Ã_Ƀh_N,GҟY[Cۮ4C.*wujjkC So<"ܥq7B׎mqIԘ ߲JHSޤ٣vߍyYe  B=x V"R&") & -*Q0-y31_796;$;@F@zEcDIOFKmEKbBjI?3G =D:B8?&6C=3:18/7-#5+3l*0(.&E,%8*g#(!'( %}#Z!O+xy"(*&~")%-v(~0+3;-6W/:9q1u;3=5?7 B9DM;FJ?LAODQFT~IZUJSINE I(AD<6?7:35@.0)+I%e'X!"6 z]" 0 x37<^EXܻ%gN v%R{Yw4lIvNP4N$<blCTݲ{՚@ϝj%/"W5(pCV]ոrڭm?J?Uo֪ԭ[oٵZ!|_MRr_B͎16.E@_`OpHߝ#hu }#Rl.%Uoh_6jkO^OوTkդ߻*kPաe0]}^  (R(6Odi"#2&')Q+-.512<579<>B_CGEJDJAaFP<0D2:A7D?59@j;JBUE?GAIDKGRNKOLNK~IGDC=?3?:K: 6d5h10,,[('##T@R 3 >%Oߦ0YHD0{$F|m@A,4OVG?1[Y9VVAָ%ϒ_N齇 бۭܰB_,Uϳذִʸc)@ǘɈp<3ԝcؘت4)bu$|y`tm-BLY#mPޣvJHXj`:ݡ,]9d[#Y ~I$x !$ &(*,-01469:=g@kC`EUH$H K1GJDSGJAD>B<?r:9<$89573412/0[.z.,,`+))P'A(L%H&#$ "K  >C>%   S6n2"(&+ p)g#k,Y&9/(1+4y-6/7V294;6<8>:@XD@FA7H(D-KGWN=JPLNJJYFD A?"<:7c621E--x(v("$$ n< q}/VT g,Q&fi9 ٭y6Xl3GTr/V2\{68qbmXn&Z0rچ݊ ҕvOt;KAS=jC)?ZE@+GzBwIDLGP?KR]MQ-LMaGGAB<=H78;23S-k.m(N)#z$d4% h2R[VGܙM;?tn{Z|~=h~52dgU s Okm7osuަڞ=Ӈh@ȉǡ\ƽ=]۬po`(6vנ|ȬWŰմҾ (c͇Ȉ)4[ܤ8[bzqc"oi75Qz'p3M{ qeRjۓڜx'ӊѯmԸ8eB(W[A  | &L(""&'+M+j0=/428g6=9Bg>?HC N+IIQKQPbJMF5JCuG?vD5O;2I80l5-2 +0(u.%,") 'a$!iW3|  S ( > aD9 $ e(o$+n'/a*22-f406295H;8=-;O@=B?DAF%C5HzD5JAFMaIQBMaTO6SNMHGBBx=\=77}2H2,-,'O'#" T g 4"LwZ2@_+^ل٢ۍ߀ޟPeEv"4^`" E^m5 ;  ] 7X g{Sl(3+>$nֵҞ&ΉtZϮ<ms߻2x ?IOӝsA4@.zrݭAUû,]Č*̹ϗКCԳ _~(" gI~ q] \-P޹ۙ.ۀrqצن ռюNFӆϩխ߾ۑdi"WM:q )0 S: n%!5*]&/!+3/'83;7?d;D@JcFQwLT P0SN0OJLvGHRDE,A]B=>':x;6V83I50W2./8+7-(*$'!$! XI P  m l!%#(I'A,*H/.1O144*789M;<=>9?AACnDTFZF3HGIIK#MIOQSTWrSUMPHJB;E>=?7Z:24l,.&)!$s  }:'%;%_X, ү״ԓ.@ݤ-T!00{$w 4l{a A :q +'~xݚ݆@Uџ;),4Ś@߹ĴOqP8Q'霪VƞJ]w/ɹOx˜2iδ48sװ6.c@w_ | n yip_M^^4E0$'\۰uvyv5 0n;֬̿҆ȰЀƴӂɿّϣgC/sa: )g2$6*p$T/)q4/93=8@;E@LGSO(XS@VQ;RM\O K;LG7IDEAmB>?;>CkAGC!JEL[GNzIPLMTKRYV|]T\OZWJQDL?6G-:A4n< /6)s1$+i& 5h . N3Sp' +Oؑ+ѱҤPiۨ܄N>Gf e>[%| h ,W1w 2Kߖl\)fD- w >{f mSC4]:|cre^#m!ޡѹϴqλctϢ ͷwϼˆբ}D۹aA M ""R'$,)12/64 ;9?=tDBKJSQXVAWGUSQPpNMJJ\GGCC/@.@<.=8B:&5P7z1J4-(1),.%(+!(R$ B$ED ' ] A0 & G F]C"!%| ($P,(/-2m16589<3=[?@BDEGHJILJLgOPmS8V1YZ]Z]UXPRJyMEG`?/B9Z<36.0(,+#Z%7R  +܄ud$!ҋ)L- } .eyM, jEI w *UuYl^ :A P:T1gۇޟV[x&пQN=z͘E|XSX§Ъ@p۰;:F(͒#ۃ݉Y&vaDQ0 AJ5 H  A   #;bhcMB;`43Rcm۷AaF |{ϤˬɈ\ɬ{&սڞٴuh:U& L~}[.0'g$$**00<66::5?>DDL;LAUT`ZYY YVULSRP6OLKyIHF$EBAA?<>;:B877430/L-8,)(*&%7"5!FrJ{  fY @ \Spsjg^|@H"+"%%))--01(144)8'8;;??CCFFHHlKlKOObVfV[[C\L\WWQQ8L@LFF,A2A;; 6 6g0p0**&%0%~xXJ١ИКɞɩȡGAБӍZ_/ ޿ލ^_PG9>"uo<??>  [U06rsVQ56,( WPʝŝŽmiLFتתͥĥlh #&B6ܭ߭/&黯ń{xphY[4- c]QU`WTL  | z ]\'& -"OT81ݳ݈ڋG>!riDCͺMRumBDIG73#$ LN%%++2 277<< AAFFNNWW=]7]k\n\XXqUfUQQNNKKzGGCCO@R@<<88)505O1N1z-w-))&%" "    9 4 RL2+D 5 s    "" &&**..11z5{599<<AAFEDEHHJJ M MQQXX@_?_L_R_1Y-YSSN NXHSHBB<<%7&7V1Y1++%%QMNOjh6-ئέ[O:>BCdHXHPP[ZWZ``^^ZZWWSSKPMPLLHHEEWAZA==995511--))%%!!2+V^ wyKG74 [^"">&9&T*Z*..22[6]6::=>kBaBFF^JYJLLOOSS,[*[aaFaFaO[R[UUOO JJDD,>)>588882;2K,F,;&A&: 6   4/z{|ށփypUTĶñÀ}WSʆ͌ͅЇбӴ0+Z\MGHM}t6 6 $$ss  [ Y oh7?JN~yӧ́͜ȁȋÅÀ}-# 41áȡ퍢:; ۭ׭ZZhi[XĚɗ82ھ XSbdG=y|lh  }{ t Y Z :=#VZTP fitz$IJ>?`[,&ݘڝPC&0<,JQ/,ge@='( OR!!$$''+*(*,,22(6'6440010--++@*?*&&##!! GJ,(  T_3( svYQ_Y  69LH  E ? r } ' -1/)),;6$'2/\] { 4 0 34ml&E8,1 g_y|A@2-<9(                                                                                     LIST.INFOISFT"Lavf57.83.100 (libsndfile-1.0.28)id3 8ID3@-   ctaaTXXXSoftwareLavf57.83.100qTox/audio/original/ToxIncomingCall.wav000066400000000000000000011117401415623743500205060ustar00rootroot00000000000000RIFFؓWAVEfmt Ddata{Xg!(j'.*2+3)1%- 'Iq 8jj6B#% & -t,4p1:q4>4>2;-n6Q'.o%k V^J + f Cy*|xAGRcJٲ+=ѢHΰox9k^Kb WPGҮ33E}ѭy@UE\ w"('_/*2w+3{)11%,I R&)kjx%&.-Y51$;4H>4>22z;S-5}&-$G h +P x _p'lxu 3pCLϢz¾44ƨԕEK_xKݺR؏`u6lՄ]-hFsm!0#)f(/(+*3b+n3$)0$+%PTQ;6 &x'.-6N2;4~>4Q>1 ;,4%,j#, lkX [ 0jo0I+m%A̋`?B9՝݀#rNw?/ݡZ+?4 #6mO"#*(d0O+V3B+H3(S0$P+$qJlc!'3(/1.62;4>r4>^1:+4$+^"%Jl&p h "d ^GMlUKFy5{UmՆ͞ϩ8GE\1ΛO 5C"w(ܒ׈7Q{p&xQ#z$D+0)0s+v3+3n(/#*<$ \Kp0/ "((m0.t7 3n<4>54=09D+T3$*$g!]P t  ].6HŢ/ÿǠXޚa"y 4OՀԼG2pֶ/Ե<$%,);1~+3*2'm/j#*#q!,p}8!"b))E1R/8W3<5>3=e0a9*w2:#)Rc f2 h 1Q ? | ),'}9S ˖si˱̃VѶN׷ עlC7< 1m&ۃۣ-סύzr` ϻ|ܸcR2%%,)1+3*2'."])P"y^ SfjI"#F*5*2/83.= 5>3N=/8)1_"(ym~ U bs aNPvJ]oa '0d>Ti؃мޟP1<G["kt̔j1׮XܤYݖK &;&Z-0* 2+3s*a2%'d.G"("h?5 '#Y$#+*2\0Q93x=5>u3$3<.h7_(/ &r` V Ng k+A^' k}؏҆OK˖ f<4>2#<.6'.%d  n SD  N<#z@c߰דЋ̉lkżӅgR 570H؈P? 2ͥC\#?"('!/*2{+3)b1%, & NTV8a.%&-,51:4G>4>_2;-5&.${ -] T 1^)gk iCިFר1ȕ WEӨ۲$[#_5ݙшդ-^o!{(!"k);(/+3v+3B)0A%),%mbe wq y&,'.l-52q;4^>4q>1-;,E5&--#KOJ  sg/ 0^ \ # ޡwQ~H̰µ=ϔ͡GXuGk։>KS*̥ʹيٵV:"#/*(=0K+H3G+S3(0${+YK%8=L(!M''W/.v62;4>4G>1:F,48%,R" H}> F 1},&RbZ|*,ݱ־R½1̖-Ǩ`ܬui`L"YA%ظ<#<7ӠڏBeo"7$ + )0g+z3%++3(0&$*$=At !*(("0..72H<4>K4=1#:+3f$2+y!3   L^jL P!]ܩJ?ƲZּ֚݉ZY j"%w:ܟ7X8:zas0,a9#$+p)1+3*2;(/#U*;#k/ESK.!")9)0)/7*3<5>4=09*2#:* l    M uU $5BvކۿS4ί{WtbgW%+/ZHd'eۺUϠԟlJ̟}ۜ0T0y$%n,)1+3*2',/ #)$#$%nC'!O#))1/|83=5>3j=08$*1";)% Nm2 nY js$e,g>}@E5RvWݾӡkLH˙-ñ ɴIЁ0dFx(($/Ae>ϑxfsf?K|%&-*1+3*s2^'.w"(x" [#3QT"$**2+093^=5>3 =/a8h)1!D(s5 ! W J ePXNҞͣON4Kb Dٟ!HYQv٫ӓcS̩Ԛv0"N &&-Y*B2+3J**2&.!>(`!G}kv^QC$k&-,4J2u;5?e6@4>%0z9)18"(KWl v{  F Y s lB4.U--Ff< T? nԹ̇MHÑww5̱ٲ|۳ K$/q =לصп\ɼӣ&L Eܠ;) %%&,)0*~1*(H/$$]*#:9aNc$_&-H-P626=16A6B4@K0:)<2!(p # Wo I 1 BN;G mds#VڎXξĆE¼ϤԘ)ԯ&`g'B0nۍܧM:ɞȬ׃?L-d!'&1.)1 *!2'/a#b*#$YUucHw%Z&.m-62==H6A6A4>/9){0,!&?   &  k F =y9fH pqڠ5tK!C5NՀΞ"J%4)XCY ۼڶ)5ѪȻ=ZzܥPRW Z%%+T(.o(.%+ 6&%_Ie 1 Y   ?,"6$j+h+3!1:k4>5?2(<. 6!'-^#`dd / N 7$SCS?ڄуʏN̯Ő-`*׬' =mYxLBҾ̯΀ƪ3է= #& )(+|(+s%' K"  \ 6 rO!'I+(.33:=6=@6=F3:;.5R',v#R A  tG pPBbՎͰԨݛ #%((*')$% )Z  /   !t(*.3T3k9x5<4&9F"3*0"(v$u&#!*)1$1/66,9999<6-50`.)'$".?v)sJ UbY%sTk,ۿ[n(?%(>N%:m f4U,s$RRK H'H$/+62B;^8p=;$<;8710 )% "7X {  2]OPwKJ`h /]_үǙʵ[aŴˆʹ ځ Y(rb܅ՋM<}PVܹ6W+.4%w; *1@#,ZAA+?'(;"4 -F%~fU&!6.})50b<7 @?=v: 92D1C)l& E_ t W+HR4V!v5Bx}ЄٲƧ=6~Kle;ž*19څTf,31#0zБ^׍^ M sl4i>h&D*H,iH_* E;&L? 8/<w(d! cqp%#C%)->25-9-B?f,Y<'@7 ?0l)*#up%!)&|2.:6@>|EDFFEE?$A-89w//&5$&{ *ZOJ! }W}*Tj۰X dr·gGʛ;5'՚_z=S(=ۖլpЪðm_4QԵ0& V&/&6+:,;5*-:%6]L0K*$<X"'^!/(7b/>6LD >\GiCGEME/ED?xB6;~-O2%'a8  [4& 7$MK=(!V )Ƙũp2ߵ?ն"7n~֡g0n֙Ws`ˆũ&ґvߛ'&.^+k2S,3*2&/ c*Vw&"Q>%.$M7N,?h4F;KBNHuNKwJ1LCJJ9:D/;'12 N(i=?2 (Y [N4Ճ_`~2h_̽T`҇͞,ݘ"ߑ)$)c[fv#!ʏR ٦19 >&$K*5(*(('+$" 'x0Y9& B.=I5MJ;;]F8B7@58?}:n /[ --9)P0 )l&]'p+=5@(Rt3zS*)"w1AHՇءԳֆQյ֭سW_J5 ?)Cڑ9~:fȐ!EۖՇѪ*@sȱʊGa ,v!%VE'X'"&$"# +V7uD"JE*:Q3z<$EGs-N4zT8W:V:T8M5qD1L:-.^(#$X' 4  $ E  %)q) & .   I}ĞQ9ە'q*\bի ٱµڊƖڿɭ͐8_x iׯ؄׺`ڂԀos:ĻSN1 l' /0L6<#LC(F)GV(Gv%C ?h8+1*Rl#+$7!+f&B36-:2(B7F:H9Ik7H1D(>%7.j.&45)i89i?# iWb3shݗV״bо gDŽ ,Ƹ7}ϽȘ7ɬ߀!e[/T;w֌oqM %$ۙNlb{Z"x%'&/,e60;4?6A7@5>!19+4$/)g$ 8H> W%o+b 3IY:AP\H /N$O&O%IL?" E: 0w $L 6?#z :6! NErdѷ'`<ϼ(爳='%пzǴo]t%-"ۗݦ'VעӒӻ ѕݕѬ0ԃL|J-6j ?)$E9Ej3?A+;"5,P\$u sd c &O,E 7F[@  >ކtל4M3,8tƿjǫ@GʢboΛϊ-ЏІuBz*¾%W¬hi]@.? #9.6F);W1$>6K=:890#7}&3^ /* &F"{1r X$ +TL4Z=X2E K$O%P$P#M"G>>2 'b[9 T*(1'I5Ԥvd 'K\Uh@ ؂ՔOӼ{уy>ŢZp؛mB{:Xgo!oNQhjǜOK^#/z /" c)M 1F.;BG J I FBTF<4 Z*O4T  ` _m 3n !#',!1:#4!7L08/7" 52.++(%f$#" $*%%'b)N@Gweb?kFFԻ:{Ģ}4uhsAlL  H7$;ͻ웺5]1Bi=ۮ.$n')e*b) \&"Oq !e "N)B2nJ: BOH!K&N)N)J'G#AE X:3.)2q'%%% z%J!$g)#f1"7c!<)O C!) :vI:V+ ?<`Y:8ƶ댰Nܮd߸`nԦUo'lbr=١kԣlq}Řo@ƾp[;ǭ'߯%+R8L}AHKnK"I"Bb7:a2L( . tz@3 |E!Q)<1+K97>CBNC VzBR[>^ :]4Y.Sn(J?#EA7.'< "V!%P!Y!v#%($+XWkBr6 r͕gȄ“ٽr$t򯴾Ơϥ7һo[ B_  \ըGo+)Dnj0ǀ ~0 l,[8,9B8tGCHKEO/@$Q7P`-L#F#?/7 / *d '' &4 S)x07Z>$ELKP Qt#P]$K$C%9`'.%n## .K xzT/hY-K\˪wk@˦/Fɘ ɋXG ItčJÉaLɿО(ջ3'4% aD M ; 1 ӏ9adtŸcWي,L5]=\C!SFa!}GXD> 6,*S+: }F*X Y '[ *6Za$*#/378 9@9D 73^1f˾=Ѯ  !%+43C7M8Sd7X32Y.eV(Q"2Ij? X6gj.h(&P*( +S/"49>BtF$FI)gE.?3j9u7072)6"2'g,o$`B ]&gY r$ o #+ 0ǸO:ξ\3<`اlxVͬ(ȺXýĞSHe!>Ag" 1'ellNm .V+ԐAyVn.&ьU804g<ۢlj;ɄZʇ̀Gоա՘n %>* - 7//u/.E+X*7) *,+-L/dm/E61 2G3B#e6I%*8)8*l: +;* ( ƙQűN"˷?h(N>4vp̂3,@Ո߽?۪VNڑٙd1ϭ3۝ޝ +&дt,b2B"4'6s*7)6T)P3c&0",)\((m(l}))((+'%!/"6= =EPK!O_$Qp(PG-ON/*K4F5Be4 >|0;)I7!_1=,I&!O> hYY uF\, ߊkWٰ\Jb>v"11JR} j,]Ӻ5[/lt2"C :Cޣ]AĞPƢecڤ*s45#=%D&'J''QLe'J&_F%G@#;:R"2L `-!('R!&o$f$lv$%&',u(#'-7&8$8A"Es!HFA9.0&B SW }3InXc yɌuZlw/LxRo>cm͵c~۹]]! uYl0D!,C0ס Л ?: tIo ΗV$`)]$,g-&{*.%4C >895 0)7!r2 1\q;&,.7@HQL TOy]QQP MAvG|@5*o*(? \ THR" FNl /S NIԅOiᯝ셬f۬mۆU{NٺHL)Ӧ'YSB9וrݟdOӚ@##1,D4|;mBE]HWG;DB>I:{4 / 'wc"KpCiX ~U%*"<)&--s0-5C40Af@79BF-{B1?{=9#6 2m-(g$V#_s{:d$989HAcŇhjJȡ"nBʔCOci5HlcXJ=5띹$ϼܾ\dAc_V(!"$$k)&/z'72(k4)5$* 6+5+d3l)G4&6!8,9%4,&YDD FSs֍,{ ߂app܈FkN˽雨ӣf*ܫ<]ֿt.̴u6yrJQ 3>A|*ۋֲzJ&dE6@ Ӏ&LٷD6 %Kb P$SR8.PK!;E>KL9F(3,+ & eO F&2">FCKLKxGGAP :73J+6$_R-wzF!aq$8*-y ݊ >f }d IpCzح.ո׳ڽ$9$ύ( $$т"S҇ѵ " 0/N@d8jːՌ$%%$4g"AIMeM1NIAe8!.t%x`K 78L "!(:1{ `(`[F#n il !(')6)u(#d $EǰݘN仮='Tp)D7ڙ! IάhԬ+ȵ=-n6?HgOQW MS Q& zN H w> A3 '| x3 O 2% *.76?!tH+bN$5nS;fY>[<]+5^)2\WKS|L EV=4b-g'#?%!!lrә~A_>  9& BsfʖdܯY۩Eѐ/WȨE4EDP*w $ k I&Xsy 8P:(a%k0*7.<1@3'B1A1/B(@G>2>;e;<2>cAC1ElGc|Io JJI-H#?I&F'C&q@#;5 8-&u!Z 40 z$b ZXyIJ1rgmyAt}(ӎu_g8i꾣'󝹗]8 ĻA LȺ".#7/^'չіPͼǀfuJ ֓(?,կ,9"D.7?`E[GJFJCzv?)9 3//M*%}#z%u*\18mWBL]Wm^b!kb)_/Yz3P5hD/57I3-+'1}/,*k(g'''* 09E%+05&21z/r)ڪ!CC# Ōn 0jydT"UҔXPZ'@s/| ?g혼 삸 &(8ۦaҨt5"*,I~Q T3/XY"Y*DX1!V7bR5'23(t5":"ICO(zM2P:6\Q9PB=ZPIBQITLXK^ILeF1kB|pƬ<&@Ɋqȷ]3⤑,#6ƹƌ˜ž&ƞŹ:؇BH"$(($%?9E+j (1 -LDnMFDOyEQFSGVI\ZI^Gc7D!fv@e88[&RJP /E>A0A gBW ^."2# $."LZ 0IAkXұDҤ̧VjWd`d ,̟/Kxӣ"ԩxےV㛳.钼bG*2;VVJ9 52/*/$0 t12Z=p s+DnyЬ׆lϿ)dEĺGўa܈͋JT՘CԆѶ-~N +ոFxwrV3: 'AFI {H$C&=t'5%{-#&$H"9& ) .q!.4$;*C1KHN85KE=L@K?IA:E0?%}:Rq5 e3222S3=#3(' Tb ]? FI{߮|[WgٳAUȥh_HV;۴ڈЖνGh`юNôm p#g0<ER YLi$NF'J)F)?&6$1s#H/#-%r,)-7-1389q?p>DAFB0H%DE5Ca>@5d>,t9!%4D0. o.W-.x/u n  u ^T#Cސ7^p~XB)G,S$_˃ь֠+W NLpnc‘͵eΑׯļZ?=' T2;( A&AC*=+4q)A,%! 5 Q '08%>3;C>EFFJE=IBvCz?;r992x3&/*:($J"#!v8Zyvw߱ʴExnofjٽ܄K-(?+4?9.%,׬`<)e m Qe$$),^,4&*^$5,!w(T /K8!?A,UF4$J79J>8H62D'> 6O-/8&W A֊F0Qw'{(&3~+`˖mġ舿Rd;C[սl'EǀB^E%HS̽&,URt-]!O $) L.%|0(]0(-%c(F!{"=3H J ' S1 Q:wBNK6 *P_$ ;qu60 ,'%:!G!&+1 /7";=t&mA(B+A,?(f:,#s4).P'L 7!Yq"&#T z.RZZГRغC_ƘȈ˅уҬa !?t):߮؄Ҁo~S˝Z %8&* 4Q%9q'W<'g=%;"7!3S .%\)!%G#%&&*(-,v0B1T16"18.9+76'1$-+ $O} M/I  M5/bHQ/΂m˨"TÉ lRM٬"ԣ ܀ȎhRն~y2)Мפ  -<<';%-/*d0+#/r+V)&)7!%6" ]}P!-%3W)<-1#3-251:=.<;E*9R%6V e2<.=)Kx#&:T '!8Htli`k.[ח2ɧn*uWáKN)Ovv;K_!Z.v;%ګԋ9k$ !&D*6+FX) %|!lW)#)C~1>6?Q:1< <T:5/Y g)#l"O&r|@|BL6 P ٧fs39 G ؖy+?fN]frʡ{to 1A5LT6`6i 4n1F*)!% S)mTx1 >@j+^6 = @RC: Bf =w&7"/%SSۭu׾և0uީYjxJؽ$(w o֛X!ԍ̐PVϓLطه=3 {3n%܎&؅ r :i)ͪCԽ$B6D Ei 7EvMC9> z9 1,&## $ &%(o,'20779<9=6;W085y',S#OV^LvjQhZZFj$>ω lB 8Ұe~+!0lټāωEX0Y^Z^܄xgT?ޯϒґپ 1Yb$&P,?26h88iz9;g;< 8=5( >0,A8G>LAO BN>L7Hi.A#9!//&Yg@  !r%*n N1a d  #22ס!СɱlQh"YaiӰIѤ\"]Ѳaiͩ[̝Bњ؏uˆ SwR)!S;#)"U#O"# #_%u(\,{N1[6E;?A!QA](g?-;D1643/4@)3"2k12+4?7!:l'k<,?; 8H s3F c{х񭳶sڱ\ZǝRs]fA%{:d+(㴶 Wڬߛ`PO%l( 5*E)']%:#"#UW'+=2@|8j#?BMlDDCE@<9E8_ 59331Xo001 35z~( *4ia jh1Nиh4ĢQ S_wJ"3n jF! ĬH G7 Ƨ)DIPMN$FJ(AK'R8",/"  V7ux !n,B"n6a!e=v9Ap@ 9@=+8E3D؃.)'q(b*,S-" 5x])  A1ڟ"[́rF RF=ƽ|мڕm'jT3b̊ŜꬽBA[i(NԻ X- YG1P/MWY0 lXFSL_D:}U2m;*(&%&W);08 Br  8zVal"` .*3"@?.OK9TDC[Ha!JccF6`o>XW3N%~C8o.1&!l #)1a9R%Gp0 xZؖ>؍ּٰ%Ъ֫Cޯy͉ڤ9'cՎzֽ ?ɰx*|#$#94 NKm Zi1=3$.T8!BvJRQSpR,MeE<3* c! (@E%y~&1!>*DI{ #(V,N- b* #֥ƭDv_GA{/ڶ1 >|WC _> Ąk~I< kR;6 % \RU Ha ^ k BS nvL[S!ͯ)т3^[=EwL^OQ9MGAX;" =5 0.E-"-C/4q;msB$*y-.*! tEp:y^yMԅxer' 1 g C)멣H%m|럮-q^7S @2dD$E;B~;2S&p\ 5rz$ 3z&$S*'B+)&(*X f*(%K$U#$o'e-,1ڠ8p#Ɓ&&F$ =ߧ>; f~s|JPWPRy^buO1 + 8^p^zuNo-w E tPg4W[\5Zs>So Go;0%YH}8# "/I:!XD(]K-Nt/L-cH*?&?4!E(&+rH ` GG[tJBV RՉ¹b-u2$ꃸxi+8F]Tв.ːbpMłș:tأE-f׌ [#q;)a+=*2%"<Ab - c0+ Pr-&%;0.I+;SBXEZDY?Ty8J/=%0G$ |O|F )vA Gb BP)Oi~Vлӯׂݸ8E- r̙3ҼQtٻ_c!?" & )'* $oxbz f0m V(4 g@ JP|#T'T\'sP#CH =2 5)Xu"[!"#Q M"!0$ dʽ޷wݧ$D証E˗ *4F u[^ c:谳F@#5vD?/c59q 8 ?4 . )&Jn  "4*Xt4=F 5M PPGMztG >06/-+o:(z()+1F9B-~&i,ױ.C5-T'ۀI}X@J{̷ʲĿ͋Ӽ->c\ $W8d oOPz_&\ 3>6IsQU@WUQJBE91(*C& $%**&0,70=D3oA#3@0:,2&&" ^U %[+/~2W9obqø!\ ɪS C|?危aUb/ס7+Fƺϻմ52o1ϏɨRȿ1仩ǦDz:a: oJW'a!ej"|dYa]BVt vMBE9&4n00N39I1AMI9RE"zW$Y%X#oSf XJ?G~3P(seSc3,Ule` ٠X֦-f8'ݸ䡵6zcÆ|z_W]z;ڇҰvElo@= -28!9A&ET)D(A#*:/#9 ;P ,  u'u%8.dG7U>I`Cf#Eh@'d8\.Q#EN9 k,<} gp9qU&s 6.,.D bزѦOώ8ņ^)%C+]ӉU˓ֽ'P9)^l:nwe* [4i:`>>;94+^"# `mmiK!H+u75ERh%\^,If1i'4(i1"&JJ&!  X9F )|#ޞښ4:PԴzPټMQ,0*t4=,`$^X1ڝ\06^59o;;{:!8 1s)R"vv 4 & / >%)LaWV_ 4c#Cc:&`%Y"RO:C9 /&K! !% ,96~te!f޾zг"Ņ̨,GNՉԺR# .㎴D䇶r啺Q )17=!A$DCGB_@`6=:d988g9"{>)B0F5sK7P6Q2P@+sN_#GX; /)#Lm+vL , )4K"Z&>w&#ݡ%. (NunW&xxRjרXٝ>úο=Y͌ϵXtQ̤ ?1jɮ9ԜҬ Z ` 6(j0)4 7]V7s4 33Q271WF1 1 2V4W{:AxEUKQu$U.[1a$,gak mn$Qoa(o(j(dA(6_*Z(+U&7" 3PN 7+c '#(^lUW&Tk[Ү{왦죆up= 3W侪+\7~e?\B@9}-1* [7Ʌ ڹF<5sX6 &,1 /?,"I'O9 ObrI An8u-0K$M|گ5'^WD6Z7@63/u,nχ)TK%"]ڛۓ܂Y $!շlZٷn*LGK >NPPcPLH]C" &6җ)+u*f'm!6{ݕٲG3̖b,^=%Äc݉*׿6ctݝ.ݐE܄ܼې ٛ z֮ .*%}^T5$뎧?p,%/.J6?FxOLeNKyGO?% 5<m)H2 [ m wXN  $ A/u<{G#Q#W_#!\+ \7Y3RK4@6D.)K)E&$ '$*& ), i:RMI o Id؇ȖIʹ?&ÍøŇ+ʦ2ƔپʼXϊ{чեu?)=tuJB_ I֡q>2I5aw o+L6>$"XE*|I/I+4F[5Bg3;/{5['B/f)5&W$J % % )8R049%> -D5Fm:2?DDH eMeQIDUn%TD*O+]H+=)D/$< w~q#۵5 &T.KOEs-±ͿH}~ |JL&% GZÔ읙떠KB5Sxמ\VqDoOV<h_f5ޫ )oKVaRicunq qI 7l dDZ {MF@4Q*"MB$&-> 5}F< '8A3C8>B[F=~J7!Nz/PM%iJ E ?N8q2$.Y,<,0o4Tn8Y< A-!lƮ$'&#&׏"FexQ ٮ"g[۸Yv۩x{t'ҭ`Y׳U&ٟ   :Nq)"એʥwyըzCm/'*87+CLQS* Q5$TK$BZ!7q,"cP d g   x L+"5%]?)G)Lx'PM%WRz"LP&kKBU9z.[&T;BJվPڵޛA(t9}Rբ׾ф53ĜkX#~'b21#R1s5[ 0ռyQΦKxԗ`J%(30e7#j;)c<=,6<:,)8*1'a,!'~"|"0*K!s#'* -1<4'55 42'/e*$2 b7pr < @d.l q |ސE.8 E (  + Nϓ q;=tMV6MPȯGHQW ^ _LxUڛ:Θ˰%ʶ͂oԄV?Gʸ\RC*W1>7<;>@ @ >;^P7\3ו/Z,:3+ʯ+,26D=BH"$L LM>I:C j> 5!,7 " =M7 $"p,%R3 v; CI0NU02i3ѵ2`z1/-*&f#|!¼By˜4 A:根q0SQs!^?D*J˒٘##t5ӫ#sȏq p7,>JCT>\"@``#^)XOG]=H3^+ ,'%)-24 Y;S@\'D5EA^EtL CT=a[7`]1\*DYa#TMbF~r@ ;%8 :T;<>ADF|#"Ϋ6W ۀH䐱Nkɲڨλ ۶K+عx9:aH3uSحYӶ\F튽X ܩ{sY=KIVV<])_2]7W 9O9D6;/H1'(#*yg O F##$+W&4';'C'G'hJ6'6I%Ey&#>&Y7#/ L(!g 1n~ j m'H }ٌ^4iL Ga6VWڟqgk,PJҙ^۫΅.abApV'QaԠ#W1R'ؗ\j!).]#2'3(1|'/#+f(A%?%s#"M##$B{&n($+j0 51q9=j@AB CBAa>2: M5 1 ,Y U($V!jr `!-"X"%~>-w  x Zޫ._ QԬ:n}n4*A9nʊŏÿŴɤ㱣Fп!)11?6)-:<<;DA:07q4}3 р1z1dԪ3}6U ;?E7H6)KM\$Ns)N+yLF-aIV-C+>=*o9(2B'/R','(*$ 17?wE FI<M;#O P+ )z%P!?o_m9t&> g= 󱡚~@Ӳ7،]ح m҈fZ6c&$ܨָκטv;O6CrF؆q"a6s("(4a=C&G HF Ck>7 /)%#$(O.G5P; !@0C=*EGEQEVDyY;CYAVD?}P:I6C2=*:#(9R8y8 89~9 ;(+;(9(8% &{  G5A8 2-߹*a Ʊݦ+j<%¿O ԱF 2Ş٥Wj\Xa f,"nbånx<ڮ;(< d1.68k9>z9:BK7"A4)=/18d+K/S'F%6# =!60f!!")'0y+7q/S>5cB:C?CCG>Hw8AK0xJ)H#'B\:1&d)oE F  cQ =#a e c$ TI^'hGڃ-r"^r+9'Y"4ٖvc܈O}h K| {^؍FDŽI暼o-4٩l0/Ǖ"#) ,.n!61F)/b.*1#4775W3"/_)o$y3!*!6&,2 2 917AGeM* vQ S T eQ Nt+J/ES?9t 2 )+!%11  UF 1 1$_ 7hOw !U"K )+Q.Р,ﵨH0f!x6̵ҭ JfϴsŝՂHoĆ̸sٵ3ś0ұ@pwQ7Q%O .A69;?7ABA}>< :!;}; =>F?' AnGCD|$4H&I?*J<+K*$M)ON (Q&Q%P$NT'fI(@#,5:0f)2C5 9:;<B ?=h<N:z8+88@8d9- 98c6&30190C0GI{2M5Ot9MN@>oK@dGDeBF=yDX9?p76 4+`/+(X }%Ce"`'[/Pn h.~GIB8OPY3ݣ~NjĻĴϰ굧d~VnFdCV=/\(P)]l0ݗKٌeeYČ8̿yǾ׿{w:6p 7#'#S0 9`@x*D EBC> 8/T(" #"(/ 8uA1JHS,<[h _b.ncHao[TI%V7)%}ڪ۟զyɜC{ׄrРWS~J=*&0:CLP4hUHV:U1SPCXKm:EFL>^@6u0 +Q)(* }-'60,4 26P@}NCFtE"<"Gw-iFbC PAEF>1;r8324/hI,+:I ج ݢ )Ck N>Թă2%Ǹ}9ӳ׹]7_ͽsǪZБΖ( LZm4 d-ȳ]쌬x o(ܿCˆl "$.).A#*1C)2x, 5.69.,8-#:+9({7'M4Z(1.*&k/d36M<6 A >G MyOOl"N+?M4KT9JM*8 0p*4 "U  q ! \54@"?ub`ޗR4=&Ӣљ )Tպٛ¬6%"S>6&?՝)݇CڲkqFӂ^.54؀ݨ;BHK"J'G<+B>-:+[34(,!%N oosMB,.O:nEN+nSWIUS3OkH@, 7{.(& ZN ݤckEv>$d+V Qf{z!H 5? 1R(Q RD3 ƒc&<ʌ)3*'reDC ˴/Dņ*w4Rë\:O,< KUWEZX %[-WPG>bo5S.'@#]ܮ"0%I,y4O>dIS[cgiXhbWyI2:.*P hN 0T- (t a8!H)Fw0R3%4$4t09*Er!NdM 3¢K v84孙쪴zyIgs>ޭьOX$ ا`>өk캴mX>4m>HzR Z[<]%_v^kZ-TJȞ͓pղLݱe TF͞qƄc Nq*Jcfcq@X&9$B-k,V3f377;;O=:;`<5:99366*4]3yy0/025 O79oC=Z?1@ @}$&@V*B,{@-.=I-:O)6/"0(p )spe yޭߙ ,yPf{FԸF|ԛϩgO켸˴ږ.ɴ&¸X忦L >G6uך_ :܅܎ܤ h [ӑk}JC' tϜ JMuO%O/L<8H/:*]500.R049ޞ;UBTL:V?`g0k|jQgo&a*}VX-I.^<-.,V t,i*(&&J%%(y. $+k256Nh50҃)¨o޵7e✦a.ڿ)?ٳܼ;nШݚr0OSyĤ9RھlӫV%T`iW 镲F&50@OX+\I``[b?c$bi+*`1\B61W2H.G<,2.+3*;/E9IHh3a"YR L G2ޒV3 Azz?f5wĬ6˿]e8ģCn/㭯1þ#ą~T/Ĝ*k?&R`حo D# )#*(&<kJPuXiBM IMINlJOKPMqS$PGV-PZN^JaF"`)>7V_,LD?# e; ;$v;X%&h" GAؤJҗ̌/&U٨NJ"ZŴCϿ̠Oŗ/ȧ؂13*ˣ, 5JZ=)#%CL*/E0C3>Z2$6b1+-J"g+u^+,92:-B+"K'.SSh9vZ,DF^ N_To`T_OT]DX75R%I-B ;s4e0 ,J+(k) pڶφY7°Ѹȸ`Y)E岐i?^@Uuء9fڵϫת6ӁAțRͶnY0 #+12 /!!+!)%!!*!!'$%.)6(1sA:L[D\ULZQ\^S^Q9]JCZ.?T1tN!"GB <>[V;a:/:<;TP>  Ocs"O7 zq[y-$pSH S䢻>%ڲ:ݩ~ѱDБ[گl[ۅC#-cG6 :gId,_E#WA{4<~o7.41.:/m/1 3h/ rݎ S܈ԣ" ʾ&F_$ʠͰՆozc5ئؙD=._t,ŀ틸#d1y&6T.? E`LON#J%D%<"4l . )V"(%t(*)L0G, 82u-ɵvДP&Uznp rc r6"25- *2H8/>AVAS@ .=7Cv2->)R'#c#!:("e-#36&9)=r,?/>/U<,7'1,,n$ho IVn3,!encmgAߑP#NYZԾE귂7V{C#rR1>/]>'DOLХLL 53= ('@&1*6;-9-:+\9@(5'426%-#(8%%='4&o*'d.)1-p424574:v2a;.8#+3&,;"a& X;e(%kB =M 6932F*:!lڎ(֠VtܦHdV=ܛHZ.oRٽӐhI\D?d=Ӽlкڃd~!` X  ՂؑO;KvEk!G$H$QH!F#BS<4 .).&G[%v&'9-+3/)M52o:8<4LB;HB?NOEP\EYPAN;J1C&:#$0b' ^ m ~"&c+. }_   poudwhУ@cޱ8d=H(Jn/݂%'xI. Α6zI}Tz܈$ <L F9!!!!f"#9(%(,17@;? B?!B'@-=1R9324w,4q&R3R!23_5 19&S<+1>1Ak5>5 [/ J)X1l򙶩 t=%(Ȭo  {k BۂX9䲴]I/g޾WژȤgAZ#p"3t% A'&Z$ " {f4!ә$l8) ݌/G5I<}@ RBCAv>$;@v7~ #5r20G0i0123SF5*n~  `v9o͐J #[<q!d pBӳ$۹®cZy $~EA[KNO$CL6(D&f:A"/ $g M|~ h a"8u)"3!:)Z>/> ~=:5F 1F,'%tE&(b++" ' dz i պz[Ժvx4(9湽 4 Ŀ!zvM݁RsǏvP ЯTТڡ3d>ԕǃӝcԯ^¾4ÿ>Ļ/#]^&#Y&u#${v9 [am %;).3g$>01K;\TTE\JbYLdH]a@Y5O7(D_,9 / 7'">!Yw$*2W:^%:hT-߻ʮ۳u;*T7X^)μhԤۛؖtأÉ3?dXם&R?`"!5!oe,<1N$f$.8BK Q>TSNFp>5, M# 0oRB%0#=.,kH9#C(^,-f*#HnT\)s`=cb)2X? axlԀ I^e鶼NYXXl%Fr & 3 J Δ("Ҁ2M<E`KNIOLFG@:^n4 `/],8W-xU/4;nB"%}*}.//*h"S(z J/ދ1޵ ֌%޾X&$`- `N6\ԥh HKརק̯ mW-(z7W@DuE`C<2f'l$  8 ~ 6 &j"h*&1+''( (l&)#"!9V"#ܖ%}*0f6 нa!$;%N"CFpo׌!Qc卿)AŎh(./;WU imث Ͻ*ISDun E PqW[=][C)Tu H{<71&d}QK$# 0}6;!=Ex)VL-O/ N-I*M@&~5!)$A C #k_?!rylVJZHP^$obӝґݐĮjpĕ͈  "9('v+)$11  q0 _bV-%;y1I;SCX%F[4EY@UA9K0U>&x1^%" rl#~?1o w/q)A6 3| Ii%ϼWZ05/;E+'':*179SBR&(,u.T,'[D z۶ ?cb=@aԆ%ܴovi{ !+^ l϶ ÆsKu[l.S4h?JQ&UNWqUOQoJHBf090*R& P$H% *&%0,870 =63@2?09V,1&%V"h 0!v FQ% +1[8= ƃ_Z#b'ċҽcնO/a>rb԰j泾>5G:<ڡtgu؛t dӪX/dȽDYGljѢwP;A TK:YXa!fS">eb]W =NC=:4%R11B3 R:^nA{IBRS#GW%Y&XY$+S IQ>3'OWx>>Q@IK[}O1ۺa[Վ̭"aXiqپZDQ߼ $U{B ʨM,]C8Q!@'D)Cq(+@#K9."i ( ? 3X K`'8%p8.G7$V>`CngEdiz@d8p].R#F:; .-!!c?kb(^ z-ݵ}ڼ5%ʘ7ΡNWhCΡPҟfkDwi|.^D̢ӀX) 3:4==C:gE3W_*! T$y-D!F+7QER%v^-Jf1 i4g=2`.V9&/Kw=1%##:)6W~d8 xߖcpǻ޸gbtਲ\9A蘸$9􇽹zb#7G\`oMM¨4daHڇ0i59;{:y7 ;1I(j!!4D wB`!_0 ?3LW"_]bM#b%`% Y\"N_C.9& R/f&! !p%8,4qы)e+\*ߜeް_PӪ5<7IAVAEqū WѕA׫YL&zVwp恼䓷|N T˿wBţZ>*1;8e=B[DDqsBy@W= :2|988m9"m>)Bb0F5=K7O6nQ&2{P*M"FY:/c"   )4!.%N%Y"+X9n\Ó񌻊4F{؜̺t 4ͣ1мϔ̎j=J﮵TKħOt⬼oCʮ'پ 8j-(0`4Nz7/7]4q 3i2 1m1s 1 '2 5r: "ArPENXK[Q<U[ VafkP!0n%o)io(j(d)_*Z+U&O" Ukq t2v= \V~>gR$Ƅvɦ⠦sBF]{( [+976?B@48,eT( UR':dQ & ,E0j/>[-gHF( O 4OHz mA7 t-#+ZlV+鶝-64L7_ 6 3/8r,ϑ)g%f"J؝I,. FSڅ54K(RMGڔ-CW4*F2JA MOaP)OK;B;97U79wAIL7Ʒ!u&`){*s*q'u!/v~و,ծѮ#c-ܼ͊q4|#[}MM > O HsƨuVmz_-NmS}"%H.D6!?QFFLGN3KGO?-5)j| ) J \A6 $g/de<9 _GU#Q#W#[R \XARJJA^@6[-:(\5%$M#% D(, m|-LX_a' J~ۯ}Ӫؗ0ɹaJ&]hʬZOR!h(ל%J1W`ɚ\޾Ӈקސ2*M5D="D1+H0H4F6B3 ;/4'.# (%$u $" % )804.:%?-E5[Gn )/ $_$c\&X|_^8%@e-0#qena?,7SFb.$S촔땙ꀠ]25¸@/mqN}d$ל `쳿ذcoK)J _VWain& Tq Kq k~ c YM @[4*f!vo &D.z 5<%'A3SD>>CPF=iJ]8!N/'M1&tJcE'?81Y-_+,v/{37u;W@J $PB&>S&^&n"|ڬ <ӦͭGŶc&P߃QیrQzgX өwևײT tن ִ Ҙb> ꢲzIKY^ޥwWҷ *7?CIkLQ?TS Q$K$A "^7";,c"m$< I w ]V   .  X+T"36B&?t)QG)M'P%~R"|PIKBl9.} &?J tK \?6!Q}-!?j ]yUV/σF (NjQŚɍS_վ {-YCb!Q3ЪӶK38*r %O/a7#-;)<|,;,7*e1(,!'N?"5v.i\ (!ET#' *g -?1r4`55#!42U/*+$Q /~F"o7&H bn V9g4#}z܉ہR{\?ԉ |Q㽥b?Tzcw?P l 0AgڸmӍO=<, ۺHsϻ*06;K>Q@s@>dF;7,3j/-,0+tˌ+U-_1N6=CJHsfLh 4M4SMtIGD > &66!, #%S $c,3 ; BH[NN/:2S2201/J--X*0&q"D (%)4F{ѡ|nٜY@ѭmuu%޻ݮڄU>tзn;c<⟒1Tz3bϸ='J{Tg\_d<`T]W&SOQ3GC!=2,+'ny%x)v-$4B T;.A'Dy5E$BEL+CT=[7]1\*WY# TI}MFS@ :89s:"Z<#>aAQDF$#_"-Զ)۾߄O L64'2Ԃ_Eϱ#ѭ7 aAtִ\bYѽf'J.wקӳQ)Lw"?5V鲸xG=iJ9V]s)^3\y7xWl9N :D{6S;_0&1l((9 "XK [ #)$$@,.&`4'<'ZC'MH(J8'vI &1E&E> 'p7#/$ Y(!z 5#W~.Q.o%KBӥ~"Z^u!Vln@ʭz)%єxN,ĺڃ+ 4}c8&TԛKGiҿWRp$9HC !(.#2s'2A)[1'w/}#+?,(%'%#"a<#M#b$&(~,0E )59>@l C&CCC|Ae>: 5y 1 z,. ( %!#2e "! ""Z9dG- < `<;CEɳ+_ Ԭئ::_ ;NH}!׹/֯q8& ʘfŕ3d[:v׼eYv!x)}1)6:o6+;?OE {HuK?N$N)bN+,L-I-3D+>^*9(g3L' 0E','7)*%06>E% IhLpNOq+ն(s׹$ V_^˷f ӺI j٥s5RKSڔMz׊%eQ7߿ڻ7=ߪA?ݢPw׃s^R6r?Ț]ZB艫|84a=8C/G HFD 0Cx>7Q/ *%:#%Y(^.5';L!A1C9>~E*H2FBQE WEY{C ZB;V?P!;I<7CY2=*:# 9{8P8) m8G8[9:= ;A9 8 c$eW6֣#χ%`c19D)ѽK.xb1Wnn$ڟ9շ͈ŕ߾;޸d_Ï%[7@M (z 1/689>9yB7fAT4s=/b8+/'y%}#K!0 nh ""g)q'1+7/>{5B ;Dh?dCND>H8K0J)H#^B:1&jCjX T  SL FtX&eCj@O<"ejСKβ ӢIӪ5Ļ_pz} r p dm^`DO0r`; Ƿ#x),n.![1x)/.!+2#58?8*53/L)|d%!;X >"Q&,N D3:IArH5M8 Q) 'T U Q OeJsE5F@%:G 2 )ES!R o 7R  (v l7K>cv =Ȣ֜R `p)&.,;&̵ѪAέ{ Lkfś)5.ĔYûD>ŬMӽ`hl% /y6x <?ABQB>38=+;C; ;=>?W PBuC E$H&JQ*lh?'>K<: 888Y89C O:C96W&q4.092m901C=1FI3MG6O:SN>zKVAoGmEcBF=DH9y?763',\/ +( e%z:"sz6?q T v3vC$F~(_ҹ)L|׻u`τD> < quGZ¤WlϩԴvI-T.#6c' !5'#.$9"E!N4 UXyDXScK@5)y \ ` 6Xk M}TW> %fioz#+選Mkͧئ.؏j`+Æo,h Lݏ،`jÅ㥿0׶ףt0߼5x 8#'#09vE@HD /F`C ?58!+0(# "(/c8AJ>VS*l[\ _=b#c\0aw \(TJgP620 +v) )* -'Q0,52ZP@NC7GEO<>G-mFzC7 CA~R>;p83\$/*,n+:| ؚ ݃_ @$Ԕ̶ `!මӵOijȳ.tBsUIJǁ.|T٢+&e' [뙬鋮C8W@˞׽ $=)1.d#51T)2,"5".6r.E8-1:+98(7'm4(A.**'/h37uJo($=%MTZB 2 Ƈ2%<ʌ))'H4;54 ̺ʮ(z9a$hÌl#}>+<} KUISZd [0WP G >cs5p.'[#k"R% `,4c/>IS[cgiqGhbXIE1:F*&Z P OdB (h [$!R)4g0O3"44Z/2*4i!Y^6 =W3uܭ어sL(Ke+ބ|Jƺ`1FE쩗pδkVS4f>HRZiP]7_%^y[ETJG?W3'cV e"#Z- 6 )@~2G;jMASDfV^BX]:CZ.vX} UHPMK}D<4'.<\($d"# ʃ pZ ٬Fu'/T669ܦ؎h̑ԷKųqɫūWƉșn>գ?ݐP60̬p͈̬_ɶ`~mEŽ!9K:85*0r.m0.4R޻;iByLNVb`gRkjZg&a*yVx-I.[<-.,K t,~*((&1&"%%(Z. $+;2~56"h5\0r)S’7˩FT< ڈ #ڮܢ#M߸Й݊V'D6yĚ#}L ۰}ӫ_ +3݁ٮ)鬲f&IE1O`Xh\J`Ib~c$Pbo+X`t1\N6iW2I.<,2.,3+;/E9^Hh3)a"YR LGޙ4x= ܧ5Ҟ8G;.9z-·ͿaI~bLè T-ͭaէ{Þ;5,ĐN3į5F؟``A#)*)&z&nUErY;6^HJMFIM7INJOKPNnSOPCVqPZN^KaG,`_>DV,LKD ?E z; 7;4;r @Xp"* &HDط]қx 0b ٴZ" ţI۠'ŕv?qؕf<t+ T5K:=#BI* E 0C2o>N25Z1+-$"X+UR+,i22h:B)"K.CSc9aZD9^N_Tm`{T_OQ]DpXA5R%{I:B ;4`01,V+(w8/ϵ^W®Ը b3MUXeد]݋ٵ/׬SӝE\ʹυa5 #+ 12 /!+!%3!m! !Y!'$0.)60oA: L*DPU}LZdQ\0Sv^P6]J4Z>T0nN!GwB <>+D;U::;;KK NN_B7f`Oc8q(d@J7H堻kF.ڟb䩦ѯiЊ}毘Կoۍ/K#-nV6 ;sACA`@ &=75q2-7)Q'#\#!A( "h-#{38&9)=h,?,/>/V<,7 '1D,e$pl GOq6>!ozewgPߕG+WT]>1aM Ƃπ\%5 +]>9PWПɺf""C 418 7'3&1*6:-9-:+_9>(5',2C%-#(<%%1'(&h*'V.)1.^420574:m2l;.7'+3&,?"V& !Z-h10 gDI:u?ۆHՆ[Q l!lnz)gȃϖգޔwڤ6+Dm:{ϋ:IeÉmʷFƈ=̟Mԅw Q $-$)*)z,+*d+%H)%" MEq #q',0J5!h7W+74583*:c/8*6%<2"_. ) G$s P "a"X##=  vE{GOESYė7~, @Og"hX6p010zzђۏgS "5&'%1 e!C+B/  ߀2!3'/ 45?9\;;95t/X)#^\F" ]&DU9!v>d{qB J n ܔgZ{V ] n o  ۏ 'ӵ%!Ǭ" v' \3*6J8&8v 6V2+B#V\R0T&t M&'VV2^ Y9 < >@ =Y A9#2P*E!n ڡ!֗pi=Ze"ܕSbx8s`ئӝ`8KG0I=Ӿfжֺ{ܺp r| A @oدBOJaEq!G$H$sH!F>BA<4 .3)z4&Ru%y&'5$+Y/)D52:8<<<<=v9:24)+!!IZ ESQL yPӒxe&k(Ρ Q}ݧܯйVڞO>5\ݾM4d_sl ΀auEыd  I^5L%br+|1S&6~ 8H8Q9;y!<="$=(,>4;B;HB+NNEP]ELPA*N;J1C&::0m'| m s "&`+8 yq  kj~i~ռ؄Е;zױOb쳷MQº3OZB݆1 60Μ֦2ȅɣS{A܉, B4 C6!!!X"#+,%(,173;? B4!B'@-= 1V9324t, 4e&R3B!23[5 /9&a<+3>0AmJ-+ O7 F6N..k򖶧ﲚ7*Ȭم uu),8ۀg3mG8qYڭȤg*>^$"0{% P'&f$"!qk&-!ӣ$O?)ݜ/@57<@ NBo,CAa?;-7q 5i20B0Z00H3K/5!Z Wh7ӼtY͇X ry0s!K xLӾ#˹î]kn %E8`K OP$FL>(1D&h:D" /$e  D o a(Et) "3"!:^>.7>m =:5M1O,'%\S&(?5+% . Ǵ v շmRoɂ$5襽滽!ij* |kO݃IuǍiJ9?U zH2QoX!<["Y`TMi E,+N'Y&*0#|9"C*ML)T4X0TK;7TKE/\JbULdHRa@Y5O-(D].9 / 7'"A!B{$*2\:!n&2rf*}߷ʡ۽sC1m5izԞ۠ؕ|*ؗÌ)2Fpc׈6j?N'!:!]i2:;G c$.8BK Q.TSNFh>5, S# |a-$0#=3,bHF#6(a,-g*#3ـPX&j\2r`*bP Uw'qԒ 9`g鴼PSU l`$Kx? > r' ?  ζ(*k2{p#"!Af"ܓ%֌*0o6ƽu!&ƹ$M%S"ACeכ&\^}剿.MŊ5\9+-@E` M{nųت,ͽHA>]l E PqW[/w][5Tw Hs<+1&hs8A$ !00;!BE~)UL-O/N-I*W@&w5!)%>v |` .U n3y {zTR[O۸Q $殙_'ý0 ΁Yӧґ݁ŕcĢ͊÷Ɋ  "0(*t+)$A   h% P\Y-%y;t1H;S CX*F[,EY@UA9K0I>&~1F%' kh$s;7h w*q.9- 6i 3uӵ,W^4>I:^!`h -ۉfb^,ʸַZ¿Ħ[4㻾aZ!:&' (h'#8U YBJv mel*(4 W@JzP#T 'Tr'tP#`H'=/3 )p"#[#L$], "!P"E,Q=mJm~ZeAǑ wmk~e pLrzFy8ꉿ ІO/5: 8 %4 . %3+XL*M4C= tFLc PA@PLG W>5/MQ+''-*1?9UB Q&+,u.k,l'LF#޺}ھ:h[,@nԉܤsnQ|!1[ aϸ }zApVq P4c?JQ'UGWuUGPJBAh;90*W& _$9%** & 0,E7w0=?3@3?09_,1&%R"^ .Dw 5Q%+1^8 : ]c"l'ĈܽtʶnfGF5֍hԞe氾?7>P( ڰld|؛j v^ӫ_$pƻ7c]ѫH;4 TK=]Xa!fZ"8eb] W 9N#CH:4%h11K3@:iuAjIIRN#CW%Y&X_$S Jb>3'RWqG<V39?`|F<۶Z\ ՠs׸"1 ]Zg`پkHR꼆,Iy= 0ʴjr,FE8b!@'D)Cm(4@#M9."i - 9 ?U UQ'B%a8.G7V>`CxgEbic@d8].R#F:: !-2!MMp~~u-G.}1ݬx<*ʠHΠNXe~DΝLҤoi@ǁ[9c93̏ӔV) 3:;==I:pJ3E`*! 8X"79!K+7OER%~^-Jf2 i}4gD2`.V4&.K~=1% FwE#3Wx]/ nߖblǰ޶W۽qtߨi46蓸0(yr)yGϸ<|eojOL²>YfSڊ/e55l9;:q7 C1?(w!MJ 8V!m0)?,LW %_PbW#b%`%YU"NmC9' c/d&! !l%=,8|ѐ e"_9ߠQޫr>Ӯ79>DFLG?sŭ _х5#+עQN+wMzs拼z䈷ふAB뻿SŘGA*1:8g=B[DD`pB}@Z=:;~988i9"n>)Bm0F5@K7O6vQ2sP*M"GX:/`"  )4!,%J%V"+d- eÎ{ (I ׃؊ܺD͛>ϊ̙e4[冀~a:ԻWv㬹uHzʲ$ٺ <f)("0^4M727K4s +3M2"1]1n 1 (2 5r:!AuIEHdKNQ;U[oafukU!;n~% o)uo(j(d(_*Z+U&="% 9"e^ 2z?XV?k]-Ɣ~ʦ╦レJ5Tj(U+*7D?uB@$8,X^& TW'DeV$ &,I0g/>^-yHD(N AOH} xA7v-#)a$i￶(䶛061O7[63/Aj,ϛ)_%V"O؞L)9 =Mڏ*@C6PEKڛ.6_9*F2JD MObP1OKLB;@7Y7{91}AML.!n&j)r**l'*݅!޿݋ن8մѭ *j2ܴ́t:u",QzEI 9 I u6ƺṯhwjV/SkקV'h+%:.V6*?BFALzHN3KGQ?25)vt 5 F [N. $u/`e<@ RG_#Q#wW#[_ \XURDJFa@ 6V-L(J'%/$F#.%E(, v{1PVb`$ Z!!ڨ}Ӹؗ<ɶ~hEb`پʱXFT!]'ؔ3wH4YUɕQ־ҍזޞA*<5F >"D/+H0H40F6A3#;/4'.+ )%$y $ % )1043:%?,E5dGS<_G?!FAB?=2;j5J5|*-# :kC  K ,t\=՘ؘ۞Al_VQwe`෠܍mHɠtRt 0)0ܪy^q="|Ijqz8c σpCK#WaOhilL#o&n'k%e!]VV9sNTGEA_;C9K9}:?iEH* MuRkU%T]*P,H+>)/$|s!f(G*riPT6%:h-E%]isdB*?BMu)Y&$K촔눙ꉠr%˸8;\^QV)יh촿붨ٰO sL'JmV%Nain Yq CTF >lJX8N/4M8&dJ`E7?81Z-h+,/x37;K@[ #OV&>D&n%&V!ڳ":ӫ ͫ?żg+OvQےhySvkXӠyzضC tك ֫ ҕd%ꇲ\aW>OtGз*79CAsL$yQ+]S Q$K$A"T7E,e"q'7 N u ZX   6  Y+a"06<&?)TG)M'P}%sR"PE|KBb9.t &D[{{O k:>#VhF 9y gP83όѹʽC ljGřɎRYq)S6e$HFЩӜZ D*\%\/`7$.;)<,;,7*a1'),!'@D";v&gc2!0Z#'*| -.1s4g65/!42\/*4%Q )Q?w*(R cz+ G.^-!܇z܈ۇEwk&Ԃ P۽c9Ofn 0H(Y  /tpE;0+5ػ*06 ;f>Q@u@>h3;.73ׂ/%,,+y˕+N,`2O6=CRHlaLm >M.QMyI@D > :6*!,' #}/1Y $o,t#3 ; B HVSN/=2F2 204/J-%[*8&n"T &:4/) wzҜW9ǭi'f݁ް ݶxY2€շg@\F⒒)X5V=4۸JTW\_f:`K]WBOYAG9=2&+ 'mz%u)p-&46 L;BA'Dx5F"B~ELECT=[8{]1\*NY#TWrMFO@ ;D79:a<6>fAVDF/#O"AԬ)xV S75* 7ԅZ @ ֱѰ@馊j5nUXiW1 AfׯӱD.?y+5=ڮ^u驸{F=fJ8V]t)^3\v7lWq9N:D6d;O01u((/ "dEh #6$$5,3&c4'<'RC'RH(J<'sI &4E&I>'i7#/) Z(! 1&Y~%] {"KPӥ| _Ws#Jup>ʨr6љqT0Ŀڗ$) Z Nw @ԧABoT;q$@ HG !(.#2i'2F)R1'{/}#+8/(%!%#"T5#]#W%&( ,0O *5v9>@gC)CWChAe?: 5 q1 ,. ( %!FL 8!"("V:mQ" B _09+,?ɢ"l ѬզzA;b-)X>8֟bF#ʘpř,hy^7s׶pG!ml)(1#6:gS*9(V3;'#0J','M)*$06>E, IZLNOu+ղ($j ,>fe˱u ֺI oeLZ8F ~ڋMp׎1]f"')߼*HݦSnw\R/r;ɚLYK膫r74e=1C/G HF? >Cl>7L/!* %:# %P(d.|5%;M!@0C7>yE-H7F7QEWEYyCZ$BBV{?P&;I>7CV2=*:#9m8 _87 \8/8R9 :H;E98 j b`5֣/ϓ`$o㹦//@-~˽M-y_-a_`3{ڟHհ͋Əľ:#W¤kÄn1j)7M (f 1//689?9tB7gA\4r=/c8+/'~%q#J!+ \w ""n)o'0+8/>5B;Di?aCMD>H8K0J)H$kBv:+(1&u>iQ J  YE A}W !lOxrE8 *jtШKƞмFӳ 3Ļcm{{ o h aqZd>V*hg0 ǧ#g)9d.!b1|)/. +&2#58D8 53~/O)wZ%!?O Q"[&,_ F3 :\AeHB N3 Q% 4T U QO\kJE.8@&:H 2t )Ta!G z 0=  # $ e9O(yڸp 5ҢȜaO.!7Q̳ѢLΣ@_gŢ2/ęqû*>ŭ[ ӭjbj% /6q< ?ABIB>.1=*&;F; ;=>?N NB{CE$H&eJY*dK]+dL+M)N(lQ&Q%QQ$.OI'I(@,60)2zf58":;<<_;؆a߻?CZԗ < ߣty?ʼʭͭTGٕU [hӼߢqتԱTԮ0` ̈٣鰞۠{g(U01%6# ;!>un?"=K<:#888^899 Q:E9h6r&o40A2n9/1 C=1BI3M@6O:SN>rK_AxGjE]BF=DH9m?763 ,[/0 +( e%|<"tx<:l [s)vPOs6gҧ!Ryֻ|Y|: P ) \{pI[¢ShΩyC)Ly.#6f'830~(# !"(/U8A(J7AS'[g _Ob/cR:av\,TJkW6+0 +{))* -']0,42[<]6MC:J9= O{>P@NC=GEH<4G-yFnCB OA{I>;v8w3S//",r+Ok ؑ ݈F Y Ԙ̲i ߶εNٳ9cFͺsXĴ})U٦4]̳i뒬鎮g=Pxq $6)3.W#)1j) 3, 5;.7Y.88-6:+9H(7'h4(K.* '/l3 7k)3#H&BrO*/ Ƃ>%'ʄ)*z'B=+, ̽ʶ)ň,m0]Ûc%wM+<KURLZb +[,WPG$>^p5u.'`#^"C%y,4^T>rIS[chirJhbWI8(:T*#V tG  9]B (g d#!F)?c0I3&44S 0**3x!GcI y,Sϣ4)qϭ잴kX3I^(ބрMƶPAKTܩ}Ĵu\K4l>HzRZ[O]6_{.^{[JTJ`?L3'N.J%p #I-.6(@2G|;dMASDaV[BYd:;Z|.{X| UUP= KD<41.5N($a"##ʇ {\ ٨\v(1JJ 3ܲ؆c̖ԴR̻sɹŠYƓȐvBա8ݑR<3̨o͉̰hɰ gnBѽ}"5;u_7$\m&?$h-|,q3d387; ;_=<;T-:K)6#"0( !fUSޔ!| b]ǫS3Ԕ/FԀuʲS쟸˴pڏֶ ӴDſѸ@ h_+ aeܺ'܅  O{9O -mϗ  JMO%O&0Lc8H_~;Ax'P1e - . ːV ]Jr;@.E㬅 vek"(+Į,)X˶"- " J\ 使 ĶR2'3=xFL ONKYL0I3Cd>`::5E0X.`*064R;cBwLYVN`g5kjRg&a+Vm-I.m<-.,U x,{*"(&&()%%(J. $,:2 5 6$l5]0q)[›3ۮѩ:b9ڒ ںܑ#ZߴЊݙh3@:tğ |Wۤpk7)݅ܮ9餲q&;C1:BFKSR UiTPXJ/ Bn7.$Y&EQ"&+M1#O6,$;L7ALCFIyMLTI.ZF]X>i_2]&YeS J*Al7&- "4t7\=;,b3zyޞoMݔ&ͺǩ@P)έԱӲa7stʘϋԾ].D5݁hw-ܩV_3.2$4+;t3@9AD>HBpJBL=M6 L-HI)#cFTB]=X8eX3/>$+2D(j%G#"!(!0"6O"8)#}9#75"F0A&K _ y (سCQ鼨¦+C ֱnޕ.`+ *  qZWr P%8(*"+g , +U*+ -[0|2s4q3q31-)0$/M? @NGG~Q{$1^[W˿gIɁ&z ,ʍ<ILM:s~ J43b{ N w !#T'(4'k)+. /[1 1'34s4j3C2@/j*e %(W3 S0 \ f o[   %4ۀWG"EcӧiԪԊj(JZ2֔Aذښ*O+hVr> `).< <2e8YF8-b "]&9'(}(&$@![B0 (i -pT^n['}^ \qL   cw*f L Ji7T~2 35{Ye*8<By r$&&%%!2yv 3]hc!l4q:~ 4N+/h u; hG)[3r F . {2*!*< D '8 W9pu  T"#"U d/@ \R!UwnpJ=U9~.,&߸݌oaeQmt   K { h " 6  Q"5GNE1l \!#5%&J 7& %  q#{ YEQ' Y3mSf|f&  6[KxFӾ[T֞Qړ{>OQ2RF5 tژ ٌ ۟ ݺ J Y  %nRd! }p߹&#ހ6 8 q -@e$*(&# p: [;ykMdE x !vޚy6?"$^%R2# !jO g 2-+'HlQ Y$;#h D!# ["l![!:# O`l 4 } 12 = Nq [ O Gzh#J6q_Z  L: [BH']m/r5 q8K6-1-r*$jE)6( 8  T `   u  j /    s Uc SCz!]& P! t;XZ -q3wOk  6  k 8T ~| 7K/y_OB\x8 G 4x|!/AE O  2X ^%% u 7ix^ s q M 8 ) +I Joq!,S5a:IKsPl( [AbVC<]X 4iS)'7h  `  :=bߺv    (Jg K g ]&l xX#H( +.u/Y/;-(K$/-aULxt'F #L)\ghCH&Iϔ4jMD ̐p@C&+V _.jk\A?o w |  {  = R % ) 4 =C,hEHPC/@ = 9.>3,&"s h!"'$@&)J+-f,4**'6"^f 0o;ii1w4}  c Y <3> ɮ ` ~yNAՀf`g]v { pZ0/F T ` 9 1D,@C1R{'w1;<B;GEFB<$61)*V"DARfV t " tO }5G Y]  Q0Y_)&8=v2 ua_z ٜ < 9Xܚݢ8_!r%jt3pJx]aS2u0$g0c#c' ('" GHg 6_  A \U D i!  PC'k   Od-rMV^$S*/1n /,!- !,f*'z#!w QI1 0!$\H'=+0/2,4)41,'A#]sv< C 8*Hqk6N&ןH? o· gmš2Ϳ뿈TJ Զ O( ܚbސ$'(('$Q!F|[  S^&;#+5=a9DGFCC@=952\011k2ק2ղ345656Z^51w-'5!qJl k~? MҫC҇ +of:+ T7KGAg~r`uM|sP tD"9gKt֍RؾtR۪5ݔ7߷1 !]"";z)Y 9 ! un] G-'w++J+n-y./:<.,+yj+u+*n(t(m%*+y+\*"*)#'*L&$!t"/$xt\*= ?iEFl߹PLԳΓM ĦuxHTŁǔˈ6n,(j9v#<(2- 1r2qh331Ri.PI)#:d :0 + +<c"v(+a6-h,O+++*k6)&$Q!ݳl}V4ͻdfɸ ˾>lcܠD Bm#M|Awo Kxa Zjvu |ϫ"_I$ %"'%#ד!iU[t/Sk}Xi5;O>{; >Q7O] UO5" 's+p-A-\, ,+")t$$]h&'Jqyj'X$(O/Gn " A[ Mi<I{V\< w `#;3>jچ.؎j  p/ny1:Nu  a ;y|'^,{ scjy" " ,#:$:$"R o4/R %pi  G ^O|8 " t/1,Y2tcY AJ-ܲEQ|"f>fЄVҚ;ج<k ik2R X ygA ] h0+&Rt"onC%bD,*`rxBTP; KK?Y# ,R8+@f@4؝ O[ A1oУbӪ_օbKOz)lEN )#S%(A*:+*bO)c'$B 9g@ L $?p :~4]$FU(')z+ .I%-*&v Azxc ׃ ^ *d o "ےl. jKC ` mOJ)7c ; L(C ^oHV K!("~V"l!N* _A<L=]'  suE >RMbH7VPz!H prF4 D#K%7',&s=#$j{^[7߯!Cr"%'7 (I\('%k"4\  5R 0 M_? Gs1 oۗmk[ӂ!ّL    ^9UgTJXU 4{m |a DRd %)n***,*;(9%! xwr2L{Piݟ?"E"bH&A()o*(\&7!<Y nHQC:.3; z9W$ *SJioiYD Ԗ N kU`J' RT##!E 7#5`$c $" E% b,"`( 3%- Z+ . 0 2l 3q1U/`_*$rL; iٴ%w0ߚw z!<"p 5s@nn%/Y E}R8({6f@#yN 2B8!z@#[$f$ #h!X)6}){ fR# ]q[tP6 xFD vIFԬV pѝ| O?'|$[T?O1 ~ \\w%pmF1 9L OAV#V&}'@ ('}'_%o#`y } Y%(w)* )k&a"^r/rV uc ]z]>&Evoڰ    'V ͚ q#/0M x K ~ ]_˛c -wԂ.i{! /?;M$V(])h`'` "S\@U6LD+=8979&@ IR X\~d8juvlidS]q QE9.h%sX\2XYm4zE̴:ѡӸݚg}A%d6Bg-Y8ûʷsLLL掩⦳٦c㈩J.7>? DE5H BGZAE9 /&T` # T#0O? %uNe,[3eW73lh8m6m0hl(_UIA=k44.b9+k+/x6 уgN P#e#rD &WyĬW]yF·Թ=ֲ!l׾H܎myրõ}%8A+#7 '#1nN9?fB1 $Cot? 9 m2g+0U'2#Y]#%G+?T3o = GcIO#TU)bW,0W+`S3)*K?#4"U*"_I-$}%-t#!Ң2WWS軽#c8PaÀ^?hÄ^ؾh^= G ڸӮ̊hi{wؾR‚78\D?OxX] ,`H ^" Y SeMF+=Ai=J =b @ DF'J )O%S(UF)T(>R&qK]" A5{)ouSv H_90!5 ukfۨ]s AY^JD.oL>[: 0gj[pwڻԳuR]п.(ȷ9, #x1"*<%B%C "dB>/\9Zq3V.* )0g*;/i7@!I -SZ_mha_ ZmR 'JL@n7/!\+/1))+& " y$@uUufm}~[ ptב*ӕo%:łY`[w镶=eR1 \<:DI!gK"J GA89/"'bV!^~%%"(&?/34l=8E9/K8K4-Hl.A&8YZ/a%{($o0V]zs׏J a ըѻt&G OT_ wnܡǓY51fr!pWmO>M؁ӏ\(12Kl9l=!y?3"? =(I81k+& }&#~$|'-4'%V (%; ' ,& 18^@GBLyOOLlGP@77.#&LJ":5:1(?-& +b (, \/X4";0B:!HM'N),NSf.U-U)Q"LE=q5H.D'{" Rq.lڒs;hڵl!Нi:'OŏäҦŘYȝ!<˽99qHtĴމø{;?n0@Q: AFUOI*IF`A? @:>3g.:)' l(c+Pn0%N60 G80D0g>&70) *$ Bx #MN.g 6>`:WbX 2|sɒ·Yd8جzw"殲謸QNlTΧ!ձhgԖ;џ9؅&ԋǴ( ˞σ T" , ?5:= =( :]s6Yz1q,%) 'O(U+3718 ALIOPS-TpRuNgF = 4 +#Z#2 ;-YPpP6U2$le䒵4c7 /),їs֒g~هbjxY޻bqūFMև' ,,0[2!l1$.$)!u#q&p >  b8&/~8@!E!GCEj@: T1e(( B)DpOr:FU+M'ŒFF<Ǵ@~߽np|A c>7ۛε?˚x02a-~Z/EO6S ~;h&>).?$*J>7(;#E7(1,) j''=)k ', 05): >%>7(=E(9%3 t+"Rnu h }.?dA=M۽;<9g]=|A`h`^֍@1]hʊF]Msha70!8'=6,@.?.;,5),:&$#Vd""6%) 06&=/D|7I=dL @L?I+H7 /'C !>RY 1s^$5σ t'r@&D .#|3/(5)B5T(1#,&5!BF i#*y2C9c ~@'E-F0lE/@+9#%U1j([  ?. 6G [[` hjWt#0ub[vX0wӬϋȪ8IsϪy ҏ:Y̺r݅@C ŏG(I %f?0%9T?!BBjA|6=5- #  NNg&&0"^9+@3Ex:4H=G=C9=25)-W&/ FQI &߼ c'`|IӨ ˫/đᄁkſxԜ͏?I;!ךSvƅD`T΃`MZ!D + 36o9"< X; 7T1)!qOKpKbi'#I1,:3C;8nI9.L8KI3G,)?$o4)/RP@ | A BF ';J/qk U =+KȕVνD Ry²8,/4Շc! <k -, *(4.!=s1C1+G!. G'C+?*.C>.*:9*3#+#XSd#K9F :a.`EyfɿJDghʸ??/[;#DQџ;`d 4;+?H$M&O^$O? K$Ej= 6 .X)8& Q&(2O-!5*G<2CF8 I@;TK\:I`6\E/='2.&B t V/q"ze l]ڔY ȋ |JA0ðѡG5<]L2u mզ/` 'Rʂ<˼г{ԛֺC!7- %7D+?\.C .D*  ,X(g9<@xJʏwk >CĖƙ˽6MґۭRC"7'NߔHڥ҈ԅ͘&Sh5k(e-f&)036r::=:>7/:1V4)-!%A 8{R>J&/ 8^*@2G|:K6@LBBIlB B>J88[--"";4 T =# % d[ l` fxiŵ+ ܴ>du2wɩJBfXJq1ul|vJҿmϻj2yF0bu'1r&8+L;-X;,7(2"i+t$`;@/!)!3<'Eg/hL$7JQ+P<K8BL1v9'0e*(; W\]H#|'p 3x@2|h߹=o`^\n1o?ZNاN).ڡC<ՇԙeA.ځTx' /8"}?RCK PDmB>7P2d 4,&@ #"n#&((+1187>n=BQAaCBUAE@;a<34`**"[O _gZ_ f-  j{M_ئЌDɜ vƨ˖ҋߚ["Vς$KLýtf©üĀʗӞ݃S("{,$%4@)-9)4;'(;"8q4F1Kf.+q*P+..f$2,J:4A\;H@O8V X@5' ,U,(| w!~` u~-ϼ6m*R6d?KD(E 0Aa3\92y-,r!L4b[m&yٗ44E?Aaw~. y)x= |SؘVݼt߸rcKߜt9Uz?'  .K9M27;/ڣn- _z1ܳCaIȣtc!h c&,,31" 7(Y;.?0AM0\A]-?i)<# 81+>%Q 2"!%3(0!7&&?{,F0K~4Nx5dNH3M/I)$CM;1) '"gpb )_eX~$C4eخ=ԍТ@4U{i`#WR3!q~ҟէ07k]W;ܧer>kҨ:(&`)(2+;.NB117F3aH2H1Fz.4B(="72^=-M )6$&$X&y*}/6 >2EiKPzTU Sp P4)KA3y5 (D2g>* E Ar|b}؋V5.ĕ涸ܰ粮J8ǬY\!mw]+o:я`/]sT ӤOغܯ% ]-g7@ >@HjL8ZO$PN!IB& : 1o<'_d;vh %&/8Y$Y?<.E!5rG:F=kA=:e;26L)0)3"4 `fx=i7") (ړ\-O|@ޟ>]IѐM̺˜4ϻnĢ-ҾEjՕ>ca1I@V$ťe'ŒÐҌ.ӱȯ%JF !j-l6 >'A-B0@3!:0T1}-l'*!%H!} ^N t %-X5 a>%TE0)-JY+L*L'J.$:E &>O4(Zj T`ohR RjeRF͐G¦7޹ŵY#Q0X$ҹTvX#נ%ۛԣgNF8K ![M@7߄wϴQB:ĵ2$)/6>#Cd&F&BGb$E@;Q5-e $_ p kS7 } %*#/3 6 B7-7)a630=.-(x%]"!p!V!"$&':)|A;F?Jg^}Ԕ!S>śEԉ &i A o >yDL(^6sDm%ȋD D 9< #+&T*K,,-{,r) %!Aj|+Qr>K"*E3ZXMGM#D%H'I$H D<2&_qOy R ]g 1m2 %&* /32%4 5 4 2Z1f_.*($G#j! E76 Xa4i\{xt{3A.ΉǴ bB¡Ȅ?ع^.+/S" aiyNc!sᢿf^ R$ؽrߨQ^ nU$> )!*!+4+B).&^W"H0;=" "&?-l;4'8<&%?2)E@*>)<&8R#1,:(#! 7 * $ F$w&+a1^5w\'m 9k)f Biʶ䳎 c؎ ׸wN6tEDT"D˖ӜϮW0^*k.ܢ--)wQ3/;A?k#A*'@'<5%6 /&? 8L\ OJf L%2#u,/U2=5H8P<9W7Z5([2X/LR+I&N@#7.(0% #!'k ?  !"!>Q%gxdCߌ܆4שe͎> *IR wϰ ͷ寿xK>>g/"f܌Hf2ˊ`jξXzk5 !Y*/T4=;JY>6S=8X:Z,5Y.=U'O 9EE:s1J*K% $ &*k05o;E{@tE$KG5)F&,C/<344*2x!,0@*#r EZf K3hv.[ PPYVDwpP|&wӨ|ͩxXþ_“(|ɿs3}϶K**# =I \{ʯpز}4A~<$DC)G,PJ/eI0El/>-3,'~)*$!w x,o$U  "*)T/}48 9K:952.*M2')#5I5 \2sch:yr;Xw^Iv0նa!9Ÿc*M , OE݌Ȫ7·*ʠ=פ/ y@: )m&*-g*/ /-K-{+z@('l'Qg*X-%14e 688|Z:*#;d'o>(?*>+/=)|G{ͅԸ}Ϊ ڑ==pZl4ۘĽ2J8ŲԵ'v=WQpg4Y(=+D+JV+_N:*N(K%D#R=5-''#{#""#.i%kD(n*5+%,,+f5!(>%E!GbHs5D0=y3)4 plTLwT3M ,{Qv*P`6&~Ѱ5:¸/Ѝs{j/LnW -ߩRuzќO#$.Sy) RЃ ݼ& ,'0j2$W2+O-Z0s&49/65Y1+h$ML"!lP6O8%*.D6>OFULN |Od MO K G0 6@K *8 Z- "X Ewk] N 6߅'d MZO `ζ|{UsDj&x%i,whډoq>ݽv#ڜ*ڿʌ߼[pBj",6g40;< A6EFGDA4>b:61O?- ''$*"!U! ]$$;')e),,A/|2|1g713"=3_@3HAn5[?7\9$90{;#;a9% 863f1|-+)'&GR+*WAbLٽ}bsG{KQtrzɗ;9[Ą*Hܲiֿ!J5IbxOH띶4*%l1"5J"]%%(D*;+.+,2*@4)4(3T'2z'G1_%.#0\!25|;!? >CGKWqOx R0R8P M_(oI.YE1uBA3L@q0b=+9$4-&$ t!Fw^ 'DN7(1|L k > } (,$9y k`{ 9%E &%",Ej VHe`Ik~ֻҏXb lGLrԶAV$A#Abh[V;vsP#-Gl6w>2FLmP}PUP LH@q97f,o!b O  -3 F/ $ - 5>kE'DL!0P6SZ2@C1?-<=).+!*B)$ &%.%&}%&:(V-HG!)-0/1N0g,K% ym*Ø}ճxjۿ@ SSެg?ލ#ֲF΀醾} ხa/oױlۧ =+h 4")3OCRGTWKiY$Y,^W2T8dP:G4X92O-q1%|4"xlLDNDQE(TFJUHXJ[I_HSc>D"f[?`2*W!=N5F CBCg=F*$&(.(I&q#h"H#"_ (&3U^z1͋=PߧLpܬ9ctG a 1H:BGa!J%H:'UC',;%1#R("#3&=,  5 <(@E72YL1<7SGVOWSY ShXxL/U@Q1J"eC#<5't/Z+)'2'kفޣgG!ݿJaē˟ͱXWܦX+߽ϭؾTxݻ}Ẹڳgл{.\\=Մ;߮v</ %/^561#5$1%.D$>*")'" '$(=)0+:0/B86B[?JI*RPUTWVwXUUNR2CM@5F(@FwpӒ!ҰҷTH!E=nĤњ1}x+A1٨(՝mίR/Ϻ}S9IIREē7&3"?I O?%4P)Lc*F<)='5#31"j-o"*>$)'Q+#, 0+368==AEAEC3GDKEDg?B+7y?,9\"410 /d'/V/0 h N' gL.x̍HQͷj¶^bħ̨˙Љ;֯ӻѾFƮ6ŗv~Ət̵G۱n/M)G 3=< @%@(;)3&'[*" Jjs< (r1/8}+>8:CBWFJBGoaӢzVNI*qUP;5㨷Zުֲq.GIL s> {&:$Xp)+5t+"q'"C+!R(& '19y$$B.G5Ja9J47G10GC& <2 Y*,-#o7k 6 aE*Vˮ撾mǹz+޹PO<_.AI;ǧ 5Pr 섿S{i&g 5 !B&f+#.!&/%-u#)Z$ieShF +#+3r j<EL(Q S/RM  IV AP9hA2 *,('"zfds Q߻z>8 Z6\0Ttadĉgc ʎڤ Ѣگܦ tڔ\O] ɑqz b_w){0 _6Rey(ZA$(A%>#8i!2.+T&]#"]"%!(U$+9)5./ 0^4h/7-p80,6)G3%-!i'h!&bE4]!c@/9HO2ֆدVDŽ%YWp[!ʔך2\"غ\>ӉnNJ /(?5üYzu-n" +'0*2},E/O+o)' $`)WJ W$ (.2*3538=K2]A/A+?&:p"L5.K&+ )T5iz ?2zdߗ&[+V̠I RM zc2`{[߆dщmˆ$ܕɣp%؉a?tU .5"5(~+*[(P#rq!QvG23a "*+17;~ >"=C!:T5p. 'D  hI-a=R_ \ }9 ` `wmxtDm? mH σ$DId࿌ EŚkrA-3߹ -0C121->'  @D  $/9V.?H CE IBi=P6V%- $ۯץؖh ӑ<,dg1)э̴֣Qi:˩`qЇӔ٩SxBߚ@Cݼ "S`)tU? <C>Ic@YAZB@=y7 2m-Y)&|&x%Z& '.!t+*0C2385K<=4g<1^9+2S#v* W %| ZY(Y:W bϘѕ,џЫͥǵJJ~ݞ{֧͂Ϭ̶z(Қ@j>>tJ1܆_٧TF d( 6^Le"+13A;e>C@jQ@R@?>\<;!:(:/'?57D;AH/>0J'=}J/9Ih2E()@ 7/pw' !Er !!%)c   BSONj79j3ZŻȤʰ͍Խ)a"ЛGδ_~,="3@;(85<0<*;%$987b8 &9#O8':y,{=  : ^L ,&3}b Jo#:cG–o`n(/30߄0fxϲ;=W0:#&Z), f-o@+($  o$n)0 v7`3=^WADF>fE}B)?#= T9513R0e(/./i1( [  CQc&,YyCSZ chr @Bn w snYw H}lN TuAm FBI!I'rC)S<&3 )g : "s_rahBq&2#<CCEDF0DkF?e9oE3Ռ,'%.&''*  {ί e Dܔ \Y(S=ԛ"ῳFO!F>C)XD^هK&]|7Kŋ͊[  F3O$ZThVRTP J$B$:^61+'T(,19Bv DKd.Q9U#AZUTE4QtDOH@=8i1.% %7 1%  . Xoo  wLZL6A_mᖖއޟpxnh7(;3߃'ړjXM¸16m!̭@@, R9 H!X#6Y"!/ "X!p!#(=/7T%rA0aJ:SSB[,GEaF=aAG]8U,LsA7^,%&!!%G+2p 5: I 1|!h~nFdכ3+ۅͿ|saX h"J9\Cʰ߫pؐ Ҡ}ȩr5kd {Z  Kx[/> *vd&.73@iH MtO\MgHA0:|b2t](!d1X#,d7$BC+H@ "(+s+^ <'Ff!H ax1ױ Kj Cل"Jl ;am䅺&(-qѳ A L ~H |&?"?g*Md!D=)30<$NDJkN NKGBz*0͡z=|4Ț]ट8D_.G+9?C_D?M8&.W#gplgQ^  3 ;47#$&(*(Z.#/0ON-+)L'"(؄),\19޿p!${$E!bfz ,[ӫǫyñ B©Ɓ*vNs,$p⍺\üIlFr aO7V(ZEY[,W FNiC:7<++8")lFS=$V`% J1<#E*J0M1NL0CFT.=)0# %P  n !LYO VT"/ Ewnlo缿÷Ꮀ.֮)bҸc{ OHt׏`уԐ̨͊ǭǐ.yڅɅhυRp #X()k'*"  ( *z78.+%1&?P35K$RQ7Fp,9!-2"* ?RG/< C XS}>N .4# Oqچӯeն"5"Ξ"y܍C޴\ϙpʝ9pɛ;j㶗ۿT}P %& 'Y&"!y: )Gg +R6 $AIP"!S<&PR6&L?"C19!//b ' %PK}r"* *aL!!Q"} P`GOM uPk?PZL*F=60;+(s )ub-J&38;C=&ѹ+ث-ݧ+>%g? Eeùü/إ׺=ٓu݄& ;} rpر}6˱8})UHB7cCMT;YY ZVQH|@Y6s,u&k" V}"A(#/*y7/=3B@4=43}8i/)0*#&e%$ !!q=$r'f-02-9A?{se!tǥ@i(;@׆Ur2W&Zu1LE]RQ6%#.PuGϑKZ۹DᏮ "-w]>kB댿54C]Rϊт2^̌Y$ B/v9 @&Ct(jC&4?&#7h&+[q  + & !  b/f%?/N8[BAe4FjEh@d7\D,PCD58'x,W~ց$ۿC| o j!NqeèC4̴5[ĻڪDс[ɜƌr?Ι¨ô|Ʌӑxɕϩ.+ 3 :o=Ns=9$37j* T zn"- (;HzV$T`,f2Yh.3ef1^-S %@F8 f,!`(,TW;d @U-n ۚc;wփׁ۲iXV1e)"mm˹hd鄼]'ؽ+,H L3VM8A:\:MK6/ ;(l!/m q ''M5) C; QO Y/^%bh$b'_{&:W$\MB7A6,%/!zX q "s&.ETjۧ y;阳y܄WnܤxWť̆˿ٱK 2rw9aH`bޱO@* D)92_:yAIF/HGDA\>Y:K8# 64G6 :}(>/C64I6N 6P:2P+}M% E9e-0"Yts&6.KC8 #H&݌&,Q"s2W԰N1k!ܱ}Ȱe\'w3Ⱥ,DLuʱZni1^׀υES䵀YEѻ- $.E5 :;I86V 53H'1Dy/gf.=/ 183i>Q,DKoLR*Z?^ qdcj l.$;o'"p+p(fg'_%0XB%hR%YMn~.2V@ -mzy} `)@$:d ޿BrgN2YuUՐy0w~m#/)9?W@<2 &d KxQ/Iڿ I."2*+D0)=1^I/Q *oUbQH#@q4(e DQB~ii *=;@1@Y={r6o1 ,Ϗ% ,as  cjv٨\71۵ܶݒ ~o 'X+J\M QBQR*RIAx <]9|9sDQGI QLWJ~+F?6G+9g s H   e xTd }!+6}C CN"mU!Z\^[V_ NE3?;1HI+&d$7)$?$v' , .Nz /Lriٗd{ɪCÛ¤Ə"@ÂէiN˱Rc`%ִԷzϸBNlݖƒyqоAy^«++|Y) i4e;6A$F+G\0E{2hA 2h;.*5)q-"'#!4!z $R /'G,("3@8#4=,B4G  A[q=ډ@݀M[f8Tp륪H汢`IܵG؊ĜӒx$G٨uߟ9ĚfS&Tg5Gb{LލmմQ9MXԙIUU `gWll![oJ$oE&l%6f!^WOd GB <9#8}9@>6B5UH LPAQ<T$eVb*BR--1L-2Cu+6'k%i DGZr޸ Rټ 9RR",*%`,q#Uq[ |0gn8G앒e\ O=쒪qӷĭHt)ۈ_gu#q7/ɳs\RͶB&KIT_ hmp>qK r, nn g" ^SF#:0W&x d}9#* 2u:!&@/D:4E*DAJ;LM3/NJ)KFA:3D-JB+A*s+/59{? M$'9)\)'մ" ׹.1, 1{󊾩,B7൛¸!3O>j#{ٙٿ أ = G Y~Z~mH,锬㬧ქ2ǧ̭X郵\w)N?45~@J} P.fStRMNa"3Fe ;4y0WI%n  &   w L'`"3&=)ZF:+M)P &S!RRO$HoK>: 4b1)W'YٻԼ#]hҵL$ S.7~ ;'m=*<++:*4',"k&!d/ sT#K'(+ 4/2'56j 5k#.3"0T-(E'T! 7:] !+ &o W , #5U(07u; 0yӍ&(Ι A>WN{4m m*;HZr8Z\tIQ#K'Áמ%ᐽ^y.)1Z]83 =?}A:AK?)\;71Z-+҈)'O'/i*B-Q3W:LAuuGMLdP PEN mIQ"B"m: !/$p88 S  ) $1 H- 5K = FI M, P/m45z55rќ2"Д/mͣ+c&0"r7C  `h\홵ZB_b.t'6`Ցp ćրKԥoh#VOqN˔9YFR3#[`Ob`3.\dT#JHA[B6@,'%F# $8).Tz7wk? E.^I^7M_(/%\&7XQ!J B<`816uo9Xq;g=oAe'E IK',˞'@&ѹ!g3ߢ RUڃí($_۳) }ԧ24mP{)C˪iήLҿ2Ӕl5vƎp' ߍd%|禵`8{GTIB^#b-wb4]7mV8&LF7@22 6F+C+g#""jTc962#!)'))=2W*X;+Cf+I*M(Ny&K#E"=k5!,$(e UEk @*=%״QLlPK'.h4)+ǎÿܺH xR~v3֞ONuS*H_eܣ|+O*Ԉ0@"E +Y1!.5 '6(4)l1A'-U!(#_ Jh!":$X&1)V-A1 5 9i]=r@X!AX!As@ >9.66 71 !,Fg(0$q _"^4 4G@!|#% l_.[G" = 0 Rެ&L%骛bP+Ghaqz0QЯm•ȷFp+5⽗!*1)8;g,F JORN.j.*0&""&^$:FS8 (`F9y=AŢ0kᠯ<Ǹ]äچܴ۝޿H.sߗ\ъےbATҧ/՞Н3ҧtf@Myk+2=q5 D++)׈(6ygЄs赪*̳)SS̹Ǐ"˾AsOVqa΄؟K5W0ĩFⲗŧnȟI%0`&93 >;D?+@=A9{?5;.4)+o$ 2 }Mn')"'J&0,71>5D:G?WHAB7FjE?G8H.E&@m9N0'6 I -_ K r nWFM+h1s18&kȬ+֬\WEK;ТŨ{i܆BV+\ub̼j ?ַشGĚqҒa!(\.`#1)1:/.2k(%4 *5@5d 4sv1-.n@*%"5!"5w% ,Z 29vAIG1MPyR' RX Q;QK$F%A8M< 71A+ #a _OnVn S pKk<  #ۜ0f 1ʧ %<Vvd;aU_ȫ!0uNL0Ѡf@xk$ Ɨx6aZ£Gtf?^%a h.Y 5:JA>y?N?(><8h8 ڀ6a5279u=f@ CGI%KK*N+Op-N, N*M.(L$L"J H(F":@$W8)E.B/#736/;^>lC@5BdtU`L5|v%  8u-نX§H"ΛѸdؓiVʢ,Ϡ"x{Ӌ!ɹ߽@1̛$à ˝Ϭ O3ϓP);2:cP?"BC"CsB>hT; 8&D432 4mV6 `8!&9p 9n,:8~77 A7I7O9VS;SSv>P.AKAFeC[?`B9_=4961,-M!N*'' % A$"7w"!l$g}sֽU`qa Nk[]'ȗs` ^KQ{۴UҎ+ЌBP­ 7ܲ^< +z(4-;u/@(0B/A?.L;,F3*+*_'t!;&#p  u~E.1!s!%:((.S*7*f@'J{%gR"VEXTPMB6 x*` Z c<4-Hf?RiZKs/:5YӣVluTكr @mƳ޾(qյZ4hwІ oʉ_Ѽ:lпR0 ZP"Z&''f.&5{ ;ut@BB!>81)"); {%$-X5m?=HuQ{ X ^6aav-`Z SmJ?m2|%% P5 SR W.; QD eLW  . bULj%B"Tӵսcĥ*ˢ-BcCևL֡Yי3ֲeʹY`Q!t8$+ 09>BIfOJRTSGQ4NbJSE~?:3/k..-+.!m1'4-62:6J@92E;;BJ;K$;J=@?%1@K"@op>=5^׻wд!V4H琬soC!h 7)ru-1m#5(7k,6q-6*-6P+5)6 &o3K"0t"U-w$'(!.m49@8GsNS}-U!TE)fR1O7Kl:Ir;F7dC0P?')M9 n2+ a%1$lw HI, ><ިyJAL.7{Nɜ8v^׵WҔש%q٭ߓ>YUc 2HTIqcԏPIU@ֱؔ0*tֻژ8<ސ :;CI|O:"O&tL('G*U?)5,&@-k!=&N| {Ezj"~u 'P'5AL8/S$VUQIv@ 7V.$Dv C$ i  y5GOKe0飸㹰޹S} {#?#FL)h%+(#ʁ2ȣdǪWj[$w\ CRΏQA#%{%4`#Co OVjY XRJ=cA7 .'ݤ" #.)1H<;EFQ [;a hg2ij'|e\vO)4@t0v A  ny r P^uX& GެF#=*@/}b140+O/*_# p;:䞶CZk<Z䳂#̙ջ8dw.Jq󼶾< r嬪R큶B3=1GPW_H\~\\gYLUMC1H8-"'!\iE"2+5 &[=[/@E7J>MeCR CrSy=U2U %+RYMgJE@,9b}3L܃.+>((7gq [zct1d  %(7Ӆŷ͚KB,'Zɡ븬ԾܽP*Xyxg1Cſu*PֺȸJ.L0k.N-{i+g"M0*Y519<6;C8.=X9L954P+.!'!a iU VOO^v *5:?PqHyoO=R~RQR&O;H2@5w7bY-*#9 y b s fi ' % S \)TRϳ,-b=|Qi!^)I-/Ȋ-'I̔=n?ϲ AĘK嘿kḿįܞ[^V! ;".a:BIWMMJJ1gGBU<084 0ۛ-Nە.?3ި8@IgSnq]f@ kll/j e%Zo(M)U@(2_'$&{& 4%_!$k}$$% ', &#M!)Ij/j45I5`3A-%&n-_/쌥忤aܣQܺNߤ8ܟH0!ƨ'㪻^hٯnKcCJ農I&.8V0Y ]`+c!c%a+^{1Z2R-C* 7 * /,+4,=s3F<H>vHAbHLEGJ2INPLQPPSYN_`IHf|CkE`( I $#O}!G N;;h<< }lҹXޒkEtĈzYٻ?tދLٷyPϠĪԯ{##?.6 ;@=%9Q2H)!2'd$ $ ,5=!D$H,+aI0J[1G,D1%IA^<=8 42I00n24@7+e p^VGMчnM_ӄٳSȭʜ6K ">Ѝɿp0Yg/욷ٶe޺SSx4!U&$ %z)R%Z/(p7@/j=7bC=EC2FFFE=CS@?,6;](5;2c0P//J/^.S/d . b +1|ʗẔiU¿w͝OqKZ BֲɠF'rQ.'ʶۙ!N M/.;CF$M(M,2KR-E+<)5F&0%-$*&0*)B,;.m158s:?>lD2BdHC)J{DLHBB An:=0o6)&?0a-3+ + c* * B, x /%89~XUƽ3<( ;Ǥ̧5҇.ѱԉRKXFK(J;JG>H.BC9=-B7"1."*W&1r$u#(?2/jzpǩ2#ڽ׌mٷulL\#ǎg뎹WҲwc(ҏʱo*?  z!R'D**g'3#X@#B*{ S371< D*J2N5KNy3K,iGy"Z@`p7/(3#) .[!@9ׁSmA8e5>?nj ܿ55%Svjļk>3y0  չػ:hYǀж &,Q*2r!a5#6c#30!.')c!dk=' ] 8 'h0s 69+B1J$ N"QQVP"LlG @H770 +C8&D-"zul 8`9<QQP'܁޲ 5tƒŠƌ9/Q@R6p ngXSё 9ɌudC*c1x 7=AB Bi!?:5/*}& "Z$(.E3 9%r=/)=,=- :*I5w&60)H"wkUWuw#|G:O=Y#־ Ђ:>лƠ}RsˎN+DUAu*gC hjܹ_25MdˀΣ/W; % g!e-' 6,;.:?(.?S+>K2;(5/2(\!g^+[!xf߁Z1}l= K CuA*I NJKI5m_ yJ F Lí.MJl߳c /2H3<43/(D  V+Kv D +mn5v:^ =? @P = 91(F1ڗ ֢~_Mhاok{Pe؉ӘV^ֹɠѾDzό M:8OCL >}q` M0;"ֳ?VMfAICk 2EE@D@x:5| 0,+x)) H(R)*"V.+3269_8 <7;g4@8 .0g%(h9 m1Pc} ^ /g.S)eR@i@@YN@?u?=]%<,q<3@:vF>INAK*@Lkk y͓ړvW6o@%)|ހcCE=@ǐv̤fzܵRe! $&9&y&%c $% D'*.N%3 8;>=$?,?I3=e88/=:4Y=.<0@[ 3.7i] Vг *UԶP涓;bԳ]yauBt|t? o߮ ҙ-׶KS) t L +& )7 *(PW%T "1YNW#!R'k-41:CA?v)CD^DD!AF?< E9,6U3G0w/W/v]0P2cJ S }?%-DYK{ y65F; ̘kyRnp[oeI FCF AHmKI!JH'IE)#>;&4*p` / &gFZ Q~#I0q9@BC%B=>7;1ԗ*%$6$) &(oU  } ~ ۔ڤ1~ƴ꿾3vw>X!`xcۆvmenFoH 4}GPUW0VR KC;2+c(]),>2O;:,C ,L.R9V$AV3ERuD7J??83.'$Y 2 IE yY4V)՗l{ֱwbݪG܉޿C5=zG-ӄ۟L8׃II>Eb_6u 0!N9 *!s  V BZ' 3"N (r.v8'Aa2KF4 G23i DכB8ƹ,F ԯ΄:و g  B}:*zN WI( .2=;\C\zIxbM M3JFF:A; 52H10J3U7V<Ch$ )?,J3+& s F^[H҅Ϯҗ;tS@KjJWϦzը)Aⴜ߹ M$~.!9? C D8@_927/=$? } 7Y 6 #C#'(D(u,]#-.T+w)&'%a&' /+C0ق7C; 0#P#d܂zߠr8~\VeQ?|w!#8h1 AW]f#&úֶ;KG PjV[\\TXOZ ZDI'9=T-(x#yP& '2.<#E+K0N1aM}0mG%."> )1#K&  F $p?|M WD!=ҹ7柾␶ ͫF#媱L6ֽW|7˹I!qԵq̀QF|xю1" "'\)-<'5"7#p W OVB%1h'?3XKj=RChXFI[DdYq?R/8G-:"T.b"l Z &! 4b:o+IM gDAZm7Շq˗ц3@*/ ނ @܌5A\PCT~67{ g%h N'i&!| 8oy -+z6 &AJ&P"GSI&RN&Lh"BD[6:f/ 'Y1 _G )gHI#l+?Q{ "W#, h(΍?$)AM܋Mֶ 9jȕ$ 7nѤ%t Šq_}Y檳 &S&ϵ.5y6s 41E* " U - q !*4=4GKLT P OgLE?4=`60+ H(("-2P;C5r&Ѐ+-؂-q+h%  ^L-h,ϒ0ˈ̒swݙk/ *Q!W!s[,k5:ǩa 7CM&TLYYf VV~QHSQ@fP6*,e&KN"T ="1(#/*6/=z3O?4<3m70/.*"/&##!H4!#&=',B2h8D⻬$š 2>Z)c_WSn&MU֔smԾ6gǖυYٍB"I/p;Nk&> tҋpB %Qm]]e#f#eb1b]>TI\>6g/or.7-19BMU]&Z*\*XZ(T$#J ^>E2%%IaaS ;JMnbج-šޒ8 ٚR\QODRZViƁ 0 6ψК_8קK.$9"!<@'5C)B1'L>#16>*dA   G n7!f/R%K@/O8\AwfFQk_Eit@d7],QD.6J' OQH(vt*ܴVi % +ӗa7 0:fq8ٯϞz^XG`+n͛}`Y@~D('>T*` 293=q<8H2)| ; n"N9- f;{H9V(%q`,f2Eh3e1V^U-SF%F8,<,!`2-/`& \L`Rvx I#@4ȫ۫s'jEVeIv nܽ`6ꄹ_鋼k,؁=,p 2p79955G/! '} Z  '2m5C /O9X^9a#a&^%V)$L@26}l,s%.!_5 "4p&.NYRxdP܌ !޺ݾٗ#yȭ`c"UȦ8Xµ յlֽCjeˀgջӵvXvwnN)!2Rw:AQFnH5GjDA>~:.89 547g6 9D(>_/C3I5!N5JP1)Pb+Ly%mDX8),!kJ!9%5.-7V#nN&%_ގ!ݓLa`ԸjeĈﴼT ۯװwgq>vLϒ_˱0޻ⳮԨ4Ϲmu\[M V$R.659k;8,6 5v3"1/_.$6/ 1k86>C>KTQuY ^c iy!Tl$n(o+oq)g'?_&W%>R"&-M3zHH _U Ts d ט+IccȧFsڪ`Cޚ᧨Iĵ#.)9>?{M@;t2I%ؗA͚ 5ĬZB Q'"A+*0<2Hu0;Q*T P=H?4Z}(*صTJ\&[. ?׹ݹ15G߬P5n #e/ Կ  xEE س'/P/u7'>CI L&JE {?O6+e "B a G  I Y3N X!.+L6OC (NK"DUH!Z[\h[U ^N1ES:=1hw*+%~$R@#M# '*,+ 5 ZE9f`7 !`'Ӿ͵&&WEɠEÈՎz M ̦f&֌|wLՑM,rj^Ι˿;b?69+u1 (, a7 ! s / 8ܕQ*ϒ%𠱩뿪â&NYܱpVԳN{өH|DCOH +MaQOU$Vy*RB-L-C+6'%o XE =  iu`!6)m gOɦA\:īY.# ,/񇗔빒qc#l6 얪"H|ODug;`k~֢ǺBr)2A.IT6_rh,cmp q hn& 'g L^%SM|FB9/&K JE#* 2: d@$/D9}EDAI&<-M 4N){KFLA:z2-*)M+;/m48 >9 0$!%'5͈(г((^'+"TBt״\˼TW'4VP2O;YV̎,L}h A ! )̈F|ʳ-X0HN3IJ)04M@I P0S RN#E!u;'0 %> u h     ("P3&=!*qF+M* QO&S!R>UO]*HB>g 4j)~!vlK^"ԡ/3 )SO;cw6 ݾ;ϖO,O>Qj`1d"eݿ=3QR^(ߤSڎGՓVѳHY}OSb$ -6 B;'2="+F<<,9P+4^(8,q#5&7:!* 7Vqi4F"1'+ /2(5 6 5w#-3#1Y-F\'h! T#KG I p $rikZT8vYHڽZ]^Vs_"L}~w_r;Iмؚ% ~.ܤՇ·ۯȇɏΗK6 I)168<~s?6AA[? ;6B10-X)''R*U-P3aq:yAG!`MPFPN I`"nB": 0$h ) T n t$a , 4o =2 /Fr YM` .3:'54N41c.*&Ȑ!;2*Hկ-~av&j.o8U7^7[_`/<\/&$X]"QJ$ B9?;=AoDH&25'ο%>! Wlۥ  ߫E` b孲Qm=˫}1CӘ'ʖξ~=қr6tƫl2fڭVv𻽬78GnS$^X#b-Cb5] 83V'9K7@25++# "I  +#!'*(2@*;+JCh+ J*N)N&K#E>"=f5I, $Td1 l^&d\eBR WNefoFP_0]o^ڿh ڹ.8ԼER̼\!DWzti/tLCi;uyșֻܑ6Ź2 ֌hE"*; 1/"4s'6W)4*01'-!'j# O["K G5!?$&X)-8v1 5\:=@o!(Bd!A@ L>::6 1b ,($= "> q%@i!#$O)L4mXpQ  f Ρ )ά& ~KʺzVf^2ϣ+ө|8y}lԢj݉\j0!KY*1:7T;}<0=s; (9=n6D2/Һ.0.ѝ/C37>DJNPQ%S+=S2/P%0M|/H-c@Z*8'1$*(#8'"#J# a'o.76d>yRF' J( jO"Qw..؞*%ڱ!KݹU+R^! om2(A0 8@(F8JNEYKO0LWJb[QH\Dc[AbV'53 p67e9f:I==t=OC( VX90 X Σqƛi>Bd&$NoR 涸&˧|7 0k}1Xuǀ|;Q[ð ^ſ<ԝȔ,eB%1&9 4S>75)K+$!c TR"'&F0-682??V/? !=U8>65ߔ76: >xA&ZDHcJ%rLc*4O+]P-XO-|N*+N(( M$L" K IpFi"}@d$8)u./=#36Yg;>@+A`CB(0H'kJ uIK鎞dȜTӏCP3ͩ3Fd02\bήy'f8Ԙ=M˵b0¥,^\ϛ{)z2 :?"B"D BLA?;V8G43:3y4u6 8!9e 9g,8u7J8A8I8Ol:_S}\/KJcmsK%^5ۺ$Ү۴k(]»PܲN' ,(4%-;/@`0B0,A.^;,c3+>*'!&$ 7mn.!!%((.b*P7*@' K{%R("WXUMC6r * M x lP#:faIZdu0$nQzӈѻ2)mΩ7MYػ38rݡjԭ/U3Cx"mЂ ,ʛkf"薼>LثԿ&x`OJ"x&A''.&5 ;z@HBBN?"9!1)#m@{ f%-N6T?,;IQh X _eaaQ` 0[TnJ?2%' = l7 gy#. L|BbT ڽ;uo vB)Nc:ެ-咵퟽elħʨ1{Հp֘OhqϩיE޲JԴ&f@*W; u$9509!OBIOuRTSRQKN%JcE? 4:$4/l].2-Z.!1'4-62:6q@'9TEV;eJ;LF;J<7F>.=R?N1@"@v>=>&|3x"1"o-$C'(!.49'AMG%NSBU!Ts)R21O7K:I;F7C1W?M)e9 }2+ b%M/R 7v=Q ,ޏn.52 v oGz*8ף'9|ƩֵHٛhGOC CKhIԃ/IC3{֨+dفL; :4CJsO*"O&L(1G *p?)5A&e-|!J&q ^6 0.T'5ALUIS:VU Q!I@ 72a.$ R߀8$\ I,=CDd$鑸㼰 ޳Jྭtaoÿ#;N)E+(#rY/ȚEǮSU`rfC@Ζ<|<#%n%4e#Cc O VyY XRJVyA7/.'ݴ" !&,#R)1G];[F7Q&[Ib zgAij.e\O&L@}0 N) | hmF& bު4#g*Q/NN1608/*L#쳳uk-0˞1Lb( N۳sśԪS`_SzɶF4e彪M.펶"L3=KGPWxD\v ]\_Y{UMCMU8J-"'(@i"]+51&V=/SE7J2> NuCRPC}S= U2&U%$RpMdJE@)9qs3Q{. +*((#_i >n;PD Ynʼn͆ )#=ɘҸԽܣ1ʶ+_UˬQ(Z6GںҸH6t2 +!S : .EMQ:EMva=,`wjjN Įwzщ `ӎ(ipܡr ނEt ]&ҧ:lwĕ żɏQ!FڑLQQUT#T,R5M:NG99{54j+/!(D m j ;ap *:65L?pHO\RRQb!OHH @G{7x;-#(ܫ& -D ", Y bҬ/hpN +=h')oղ<0t^Gh!/)4-ı/{-ʻ'5̋OO7ϭ YĜW婿nܿCļCc݋OWk"/:CJYN{NLK;GBs='<8400-Uۯ.k38AJS|].fT kvlKj ,e%Zr(N3)@@(2n'$& ' 2%f8$Bv$$%',d "1!) s/E4_56F3WK- %gD&묞d兤bX{M܈={|a tܟ<{)Ʃ+㦻aSӯf0@Z xH(麲2&.~9V-Z a]m0aoc\c(%a+*_1[2=R-C*87*=/-8+4-=3F<"H>HAxHEGKJIxPLQPPeYNl`IQfvCkF[M\OO^N[GW[<\S{.?L_!EA# J>N;;l<< <6 ` jx?&ˍǛ£ſ̽PCǺj̓zҟixđhtįpvػnrކٸSСɪگ҃(5#-p.6 &1$-$*&6*)L,;.k158l:??}D"BlHC.JrD\HBBA:=0p68&H0a-7+ + e* * A, =-( 7TKzw`ȾлD84 Kǥ̲0Ғ6ѰԒNEK=calĸ30oɿDм_^%o}/8L# <[)8;,6-.T+_%&D" YKt %-0 5|K\FK(J2JG+H3BC9= .!7"u1- *K&A\$f#-w&+@vwg7н%׌tٷz#o ! QU#Ǣh꨹zqዮxGҡ!ʻg3Z{ !Z'{R*3*t'5J#/B#i9*~ o3%< D*J 2N5!5v<V.+N!M#]#S˿ں('*_oaİW6-ʋ 0&繯ӻRnaǗЬ &',Y 2!n5#6u#3j)d<[pO96GRtή8M0   q!m-'6",;x.??!.?I+5'/"V)#E_70MG`Br3ra*E v1\+ִÄT,\ESʖd5$ٺCyڄֈ #fdۻdlQ{Ʃ~41 N@ '',S+8.A-*,$ )/!% _. ".'l,2%7!(F839;74@I5A00?+:(_5O$;/ &nU! "5 !.2NFD-,0Ԏ80kLƤ<g%ؚhuO@qY3Έ/zuG?B] ' Oc$ J'' $ v!t  fU.!J})s07a;Y>>60;/5v}/6(E!m^ 9S2p~q|a{N; / :o!J5 .E?GU|; dM U \1TNc ] / &2?334%3/(0  9aZy0 {L +g5}:R S? @D >(91(V&ڭ֗֌bIi؜zn{vx;h؀ӗCQֽɠѭǟϒ4J,EL5BVL l&D$S+ַST9dABCf CEEQD@o:5u 50/+)) f(U)*"^.+3269c8<7;b4D8.0e%(< s$Uqje)W=H9׼-MMͼ I2(s[L3-VZVچw^#u}Eܞ? &ڤG"z*y3:3>YT@g@@\J@0?=W%%<,~<3@:F>IRAK@ LC$?,?L3=g88*=%4a=. *(Nn%I "CcD[!W't-4:?E?e-CDW@DA>?{ q@5.JUW s& 6V5̙qHkZkYj 9-CE BHK7!J_'WE )*>H&4*uN ,6 ,*fWc N#W 0`:AvB(Cz&B =L7D1ԓ*~%$ @$"&()Hc  U ف ېڨ yƲ겾9xnHdK_{`dnIgQ F|GPUW4'VwR KC#;2(+X(x),W2D/:+C /Lr.R9VAV=E SoD1J??83.'$q  6 QP r <{ղhl߱oݬMp޽WKfS}ۮJ#ג0UB?[_6d(!)!G6 1![  r ?R7 '"M (g.n8'A[2%KfJ{NH{n6]:&˫CePm w֭+QG OfV[W\MXOU ODA'9>P-t# a& (27<#E+K0N1eMk0dG9.->(1#I&{  D "xBtH]T':ҷ?松⟶"Ϋ#J!岱Q7ؽZĄ9˳Lv ԯvӂ́^5cѐC%) "'U)O>'4"5  j Y  DS8%1]'?3fK\=RCoXF:[DcYj?R48Gz-:$"M.V"s  V ! :U>p+DS `9LW{-Չˀ˂w8I+Aޓ޺Eܞ@5$0\CNQ}<5j /r%Q L'p&{! =j '+v6 ,AJ+P"CS=&Rc<"?De3:b/ 'R( g6 UM'P#p+.^e "X#. G$Ι5/;ҳK܎;Ӷ/ZȘ0 4wѮ_ ƘqddO泳g.Zϭ.5z6l 41M+ " f > w !*$4=5GGL[ P OpLE<>=_60+L((4-2Q ;C/|& ~+؃-p+\% XT%f7ρ-˘̊ pyݖb0  L !V! pV%i,zEɩ]7CM"UBYYu RVQHSU@nJ6, Y&CU"T J"H(#/*6/=3\?4<3m7(/.*"2&,#!B0!##@',<2s8A߻0¨ ,'NY!$u_Ah}Yf ֕suؾ8]Ǜυ\ٓ/M:h/U#⼴v&ANjs"B &Q]]ne#f#e b#`]:TIX>6i/ec.:.19BMUP&Z*\*]Z(|T$/J Q>J2%Rb]bt6LKikأ,‘ލ; ٙZܭ[[S4Zk\,iƀ18ϐЎr6׮J.%9!C@',C(B='K>#26H*v0. t   R kG!d/V%X@/}O8\&AxfFYkoEil@d7],QD6N' MYB)xf$ܹ Ul  ,ްӈg:1)bǁ9٩Ϝ] c?h,q͙}]YF@" ~Cb*c 2 96=q<8G2)| 7 _"M=- ^;sH)V,%`,f2Ph~3e1T^^-S?%E8/',~!W7*:N,* TSTV}g yTœD6ī׫k(sHSfOr x׽N(ꍹu鉼j2}:,t 2o79 95-W/' ' ]  '%d5 C 3O<X^>a#a&^%V$L@ 6w,h%#!b9 "4{&.I^OvkL܉ (yݰ٨gXTIѺ(KG–׿-֫mq ֿGlb{cڻ ĵohdrkJ)02Tk:ALFlH8G_DA>v:#883 548m6 9C(>a/C3{I5(N5DP1&Pd+Lx%lD]8$,!]\~^%.G8X#uQ&%hސ!ݘG]mUfć識]۬Ѱ}`t:{Oϔi˴,ڻ⪮ݨ4ϰry`ߵXI V$Z./59d;866 531/f.#/ 1`8E> C=KEQ}Y^c i{!]l$n(o+oo)f'a_&W%SR&!M2a{b2 eK* [n$dד&4DYiϬ鷧B僧ʪZTᝨLе&.99)|?V@;~2M%؛E͟ :ĥ^5&$ P9"L++0<2Hk0=Q*T POHn?4Af("شY F_(O& 'CI L'JEr?G6+i '8 k :  H "T=O I!7+Q6PC (NQ"FUK!Zl\\[U UN;EX::1h}*1%#ST#T#'16+ , oS-r)d: #a.͵&-#j1ɴ?Ä՛wR̒b9py֋PwB<{ҬtnΌ9X/:8ց+iKڞȵeI)' 3;;)AB%E=,F1D3@2:/y4F*,# ';b#G!K! #5 &'8,Z3r8#=k,VC4G<H3AEBBCxB>?6$9+e1.(M W$ % u +  4 ܟU%ϙ%'ت柢piܑ׆WԣNrӽj924Klp˻qkyXCQH CM]QQ(U$V*R6-L-C+6'&a LS 4+  s}e!<)l jQɢ,oRĜA+$ ӫ,*񌗕뼒^_.sA 섪-=ԂMFjl1]fqֈȵȺ7v=2òP IT2_}h"vmp q fn% )g J^+SG{FD9/&7 ]A#* 3:!z@ /D:E DAI <0M!4 N)KFEA*:~2-*)H+D/n48>J) $6F'0r(('\'*"]Fj׺eDf!/WYJ:dJ̗4|I] B   ̃Ap̳!E6FO*N:)B47@I P:SR N #E!y;'0 %@  Q     '"j3&=*F+M#*#QO&S!RENOX2HA>X 4)r!zrNv~0(Ԓ L-0tI'g1(ݺDмϞW)H;Ln\3c[ݹ@2NK]ߣTڊCՒNѻKLtTGc$ -6 L;'%=+O<=,9M+4Y(G,q#'&6=!6 +coe8H":'+ 5/2:5,6 5#83#1h-E^'n! V)EO D  | 6xjjaR9{Z^ڬb(RNLxa5Pqnc[ 5Pдؠ*s ܣՅsۻrɢ΅C6 H)18"=d5L, $T\7 {$V6n0;m 8S @R`kRIQ1lsNb5IԩG`̹[4MCmvZ;C!t #hyȒ܆7ŵ5QN|"*?1%"5w'6R)4*81'-!' k# RZQ=!+$&@)-=l1 5V%:=@s!Ba!B@\>6:6 1i ,($= "@ k6^K!#$]/O*IsU  o Λ $̬! k AʴK\nϵ*ӣȊ%uˀiԞe݊T_3!H[*167Q ;p<B=_;B9HS663/Ҵ.0).э/X37/>DJN`Q%S+9S/P=0Mh/H-f@X*8'1$*!#;'"#D# h'h.<6S>UF J3 iOQ{..ؖ*%ڡ! 9M3U_* i"w-=Mf퐢hu~'˙ڍGސۇޛݏgפN w 9`Xاfйaקܭ唦+2F<*QEI{ LYJaG 7Bl; \48++%4 P#6(;0 8@ (F!8JFEeKO1LWJ`[MH\Dg[~AZV4B YR* ?S ΢pƞ k8?k.5>i Ҷ<˔(5my- [rǁؒ2C\ø$R̶4Ԟȝ,e>% 1&93Z>=8"27?|5EQ;0H?HBzFE?(HK8H.E&@ 9[0B'T/ d  %c D En&C%Le m>bʻkHL*8epd7HbLT/i@~25\yK켽ݜ٠9{9ہ!(.#1)12w/.#3(t4 o5x86 41[:/*,N&:#!"T%,u 3F:AH HMIQR Sa Q#KJGA<J71vj+ #21&j;fm \ j:Ff |l;2 N}K&FNmȟ@(O$FТ8m$ݐыGx~z(m‘sʏӜ% . 5<:>??\'?+=e۾816!5ߣ709 >kA!cDHjJ%nLm*:O+^P-[O-~N*0N?(M$M"K IwFa"r@l$8)r./?#36^`;'>@.AV?J!3S)aC JK钞`ZӇ?V7͝@5r7#m\ηp#j>ӐHD˵[(ϥݬ@hgσu)z2 ;?"B"DBSI?x;&Y8=43F3l46 8.9V 9r,8m7R8A8I8O_:QSB=F?E1A"@}>=B)#6C&3{"#1"f-$L'(!.z4:.AMG"NS>U!Tr)~R71O7K:I;F7C1D?U)x9 m2+ d%X$ay -wFS -ސi7/Hpx =4q Jץ?{ȩ׵Rٛn?FL><+s[u#VH*x֤&"i|ֽڄ.7ށ :?C IO<"}O&L(#G*t?)5/&b-!P&g T |5 &9p'5ALiFS' VUQ/I@ 74].$ Fy 5$] C'0PSZ闸㶰 ޳H༭nanó # O)W*(ʯ#ʃa&țTǷCby F..H΢19#%}%4S#Cm O VwY XR KTmA7,.'" B#B)z1_T;nFQ<[Fa gBij:e\O.H@0 N< v}  ` `T& dެ-# j*N/RM100 7/*H#|e,3ž"X^ UԳw԰Ov`+`CqζB4dN*풶C3=GG PWmS\\]cYzUMCEk8 5- "/"Ai"Y+5*&`=}/BE7J>MCR3CoS=U2U#%/RqMmJE?-9rs3Q|. ++((DJZ OkXIN ZtϿŃ͐8HɎظԽܤ %`\ˣQ!%f37غ۸9Ib@sN^;+k"0*Z519K6;P8n=R9VG{" L7pmV rWͮo!s#ыc"Ӑppܧ  ޑ<s J6Ҳ+Ϸv|Ċ ſɐQ!XڐLUQNT#T,R5M:TGG<;>955m+/x!0(N q -jG9gf#*;45G?tHObRRQ[ OIH@Fx7}E- #8ܰ 1M ; G \ҢHHsi  #'A}l$0cW)nAe!8)D-ľ/ v-ʿ'@̃BXEϧ GĔX嬿nֿCĮ:R݃b^f"!/:!CJYNx&NKK=GB}$=:8490 -Xۻ.^38A%JsSo]-fF klFj &e%Zx(M()Q@(2\'$'& A%Z/$Rw$$%',T -)!!k)q/=4a55W3Q;-%]L0ߨi判SU{K܇߳B|~mpܧ0v9Ɲ/㘻`Xٯ\)SixN#鿲0&.s 9-AHN2oS T[Q LtD:]182)9!DC $)_r0U"H6+i;59?@}DDJ*JMOBNV*JZB\7\*Y TM{E^<3')s [C+eԿ~\JfZsN:mܝŎԎT]ı"nYl³*'̖ұb1¹C%7Qה6،ڻܝ+iܯ,,~$3|+e:2@+9D=SFWAlHB-I?HI:Ho1F' D 3A= X96Q30 .,)( 1'(%0%5$8$A9=$7i#01'uP =Q_R{18_~ݯhIm 7r(#W*{+t_' `9T!"S'S*+ Q,8 ,,\F*/,<- . h111w17$/r+C'j#oozD gA{O:Hy7i%/p'IoDRʂ+TѓYV sYk <2TW&+ 2 " `G#&8(o(')Q* +W.#/l0u734l44i3"0t+Mn%q z  e m } D B K _ p7k\:41Հ5bC=f.LbP9u_< {բ# =GI}{s 2 ns;4^3W='iF \#?'(4L*|$*)'6,%H"w\8Gw ( MgS_L7|p,4{ }bo;)jc56ijO6 is_ W J~}uIw !nt/n;hG##`6')m(w%?"0- ߲Ly0ݯޝoh=J,r[)QF!v}u +&cs}q{ (V~w@@ 9 Vi< X7  h A   u,@' e#$O#!//i*  r+_M3t~+0<_,rxNݩG|#,ޭwy4:fw9U aZc~  | M f;FO'RJ2 R"V#|$ J% % #i1 T5*c,V f5B>d`Hg( ioG5H ҢԲE}$7fډ >M_\0XO  7Y b:zg ZsdeE&RL<:E yF 8 ao"|u&&0U$!p u M2!%&jn%="$$v `u84kq%^D C k]kA`gv ݹ M7 ֺ7ڶ.l7^PK  O# o @ 1Yh-dYe* . 7wl$,w37:73X0E-' !?^ !K)=ckYj d L +3O0 ; + 0`` e ]c `VW>TQ& `0 z:n ma 2( 5 Y qR r -* [ 2 nmm6 ( ;4w/ pxbn0t ! po:u&}PI 2.NNvE :   )wOvoVr1@<!3|/Z6'q( yYaqgdqIDoF6K < - p 4 ZTe-WZosn{ gm3jt_ G3o t Fj%-*,7.Q.-*%n-hyCa:(2kCi -iwJZ]`E\DnXhJ͊΋3->ؑc k yFqFP>xVq j >   6  " % 0 :ADB 7@ > b<, 6H/|(#2 1LD!Y$(&,_.>/^-A*K&9!|qV}5v :~'mK; ՅE9Ϡ7 Η {N \A2 L F  WζJ {7:cc63 / g8ms + v  r44l1m#.+9iAYF3}H_=F%:@:/5E.%*'&y/T %2RK9 K c gMy}Uk?;DYsMRgܚڳ ק k( ֧?C4ދU6F AzK5hY)fgK|K$sI[^eF\$) ;+ );m%3z A L)  @ > aQ cAYzY : sY-u ~ 9~)Y{Xh~0?6";gL``Jۋ]Y*S$3٪g٪Nj,b ?v#(do+ *[9X*#=cnU) k k._ S  #s)n.\w1/;!:$)w%.C13~4'3.lp):$~GP/ ޼X؎َ" <&۪FY ż f{ c9h<a Ԋ i-ڼcܚ߫8^?#&Q([('O$0!Z$S@b H/ v% ^/M 4)3;BOF<F;D Bp?:!61Eo/-///|152r3"e5P.77ܪ87V4Oh/y*#&Z  $AE[@xOoy ,y~ 1?]_b'ɒwuZKei9?1 #uԟ Cݤs[:I,~WB  +x`=' &"=J*c0=5C88g6433[/4+M&_!R` uVD^%9O!(J g@ B  /&&TR!b W:*E&Ѡy^ Ӌk7eCd .?p*J u  R>d&88h2Z15{!Y% ' '','&7" #r"^:sbF=g P $  H_0a$' FrPph%52b^.9k2֌xpӴճיٟu ۦR܉QWMN QrhCgޜglSo c tQ &+t+i+;t,Rw-E-,*0*))AT)S+(.(/*+Z -8ܵ,z+2*l(&$!<n 17хv~/b@) =6&? ,g^=Glƃ)N0lӘ-V!ݲpt?#'s,x0@2s3+2q%1./)g$5l^RdY wB#$)-1/H.++*)'%l#uN! ׇА)wyȤ0-I6utv c G4I\1@XcR":eL ArVԯn$:τ!Y#>%ψ&%vӍ$nK"](S*.   bu Op. wtJ  &#$(XT+l--?,,+j+)$! |7[4`ea:!UfzR v A `rvrTv%;U6K\ M6 lH.ؾ e H4߯GOv~vFpw;$!sQ1$& q48]`2N+ Hy! ! " 3## !  c4\'6{ m11Jxpzf.P :, Yx!  m e"*:+&f+LAQ" EoaBUp2ҮP0:C-:*O+?d ya;?|v@0 ! ^ }N M c t   %Xh* ,}-0*p2z(2 20P +dQ']#^QX)P3$Y,ZH<*  B `we->36IةV ' `φ<IҋqؽeܺPߩSuf"$/';).*W *Kg)J';%wD!r9` ] + @lL $(H(j+-L-+`'E""גj 'Ե MnC6t=j}-Qjpr Q@F]qq,I"_YUNhK a(uyn_<ڀ Xگ!]1"Oܣ!߉ ;|&/zpX(FSS#6j%( xbFxO"|X -0G $Lx ,7u#[\* 6"$'5&;%4"}k)/r3ZZX!2#%&'&$ 5"CH6SN t G HF` S,YM! eE%1ٟ<ݗ 3 j c @7vA/xF $0w lS8kdfys -|O{%*)_S**v`*)'p$,o Ca6F $!2g%'*)B)(g&"#]g j{S 35Ee(~m .sRnzJMذ֐jk 3 .ث?n݋W F |i"#O#~"P 20 JBhe`=-j$u r* ./ 1 a2:1.A*%s <uc$(-ߋ>7 !) fW Oub Qa `y|Gl $EtPw|ݠ/g l; .&uHF!n##"9_!RMvC"uv.= L:P fF2L7gTJH j%чD% gg<#j z m~l%;M}Ut -j"$;%&b HiL!P e J[ @ J̓ ̬ f Ќ[ڮJmei5-FJq!MV [ 7[@  *& SE; i"c "8ڧ!R!U 770߶CGO8j' 1޹"ޏFu A"?D ~jx #&)(+.#/~.,*'X%VO#M!޶!"5ݥ$&k)+N-%.L/m/-*'.}#v z/T Ro-=RDx#H [a r Ք6z϶XxC- <;[ 8 zڈ ۦݢ߰2R`7sjeG &wO4P@ ``11?%) h-g/1221HB/_x,)`& z$R"!"[#M&]( +R-6-V--,&)&"F#d ZJR{"fG[ ەOdqּu4e Rd!%f&:\ %l\ |4 eW! !i"!o La t5I  Vud sC!6 n'GD]5=׹ق Npj 4Qqq35 6V+[V~=!wT@ #2iI / vryw"<"jUڛcmb7TF۶۸1 [݃  ߍ`za^: }8eB3m@ I|Pbvsdw3-i"}&)fT,.X/{0s0/~,C<*X'87%Y" ` `<&O W6 ~smFPr3\|d>ި۞Wm׊צZK!S}y=ھ?Lݩkܾgp 62:&!N>k F .UL/_|B\.hޑܬ?wmbj #&,f) N*KE++ O)&$I!DE VOD=w 5 [J  a1=zo!e7<Dt^ y0^ (f<&F `Uj3Vq|0 2-+VAt.w  ;g 0 &Q 5 ߄\a(>(R` $lgC=5=`^XMa{F b\ 0+D>{elv2"K.a! $t%R';''$5|o  5uFuX   O4,) V A _$ܢگڛ:nsJh?,sb= A  k 7H k+;G\@}bBOb  4&O2Pj|29gf(GN=!"#$"'Hh[=Zc Rut':F7 52$;Wl(R)J4BEߌ߹m#rߢNys b 0 ?#nV)ks]S<g &}s <j'7"4NqXt V , tZg}F H [zaa(J]=I aAY)d5*ei^tY4 ,]K?=g' K  7"Q%.  ( +K.Z@iYZsD)96k*r"("#$%%9%9$!,$kC zOD3h/5r  } Sj*&U?-X jHJ4ܰ:EP"jY70xqtT5@FNpd x & 'W 1v% }eYD@Ud"P@)E  vx!M$ &I'(@)=('~%!g3 b3] luCnrM%d݀aڝtwa ڨ XD9<lP-|&tE$ t   p_"<IaAq/uZVyT?V,7# D<8Mj z<< / I OM8`hT:pO!XOa@e X  4 9m `  7?L)E7,)X~-s:)Hp6% ^ ~;!Qnyzht,*_kr2Ty8A kt N}H+ a6S7JGq^+rSiB f ' !B7%`Y=-#k(*? * hG ,m Xjgv!u7|sQzQcoNpw(gI*r (Ge;{CPX$!@_X$XIH p  4%A(=o$ k SM 8 D W y T_ M 3 ZVO"1*:Tq0*{ W W(  ftIC+ObC bCgL{Du;kPBT5NK ? hP`c ;m U Qg(  +EA^ + fA ICp'=JYuxs>\~p`&2F ) c eGA(iqfrREu Jh4$ 6X 4+  E  dMfOP`>2,F2h WPf ~b  q#|p\ 1 3  [+ 5 5 *S = u| , m  w QK9ZUvC~JQRP3sCFC2O(F^3}% \ }F<%ac5s v?/}jO/;l   Z7 b^zWM3e < lM h ; > !  Sb |&W`&Om{|{|:O5[ j9wFY%n z # O 9Ry> -  j s_$1 \_Q,bTf[fR9e|2t7[uc \ 4 kS 6  1+ ! E6 %@ m  0 ) H B 9 = p T`uCKi6"1  a k $ & DX? j >  ^) J [+7MbL R:8 KjF\i_N{ 0G|9 @ M iF a  S l"AD*/q]^DG~IM+-[jEB'9fF2 t * l  g Y 6/FoI.p ]k#`;uH+|J{34!Y@sq"n`,Uv% T f Y%  ^ j i "uM['4|1c< ;@:iY $"W)V{ 9 @ z   i o \K 0 @sE{a/" V  G =Y % k` >  /YGt< N&  H " G| + A~_5B qKGf<kdVxl<TULk>.*bU { P  z ss f Bd~+  ; e" T .M7%vM4c j ngQqJ P sL 7 $ X uw  H r w~  Na V N ( \jk{ vMmM=rav-2s7 Q- [i  VOLw#jF B~jibfC{^ % E?er5r&pNb>%1x]K/}MGpioep_fIjQ]@,~ xOXH#|s3vO@VR.V|t# Ws.!,ub? Ccuyx?dS>FSv+ ex%)Z?|9 7 E~ vfjy&`~w-NconS Vws)d0u6:W$[ jX 4X2b]UX0, 615.=OID!u%FDB o}Gfo7KOUN ^{+L 8E1k.} '26&Qe4;; z +BhnRBA~:K+^ds8 OK|Kgo5 rO1WTbnQ.w$Da$MlfWWthOwma|W6r_vXqZ*dxHjW*!UX(@3>3bR zv1f;< q!iXx1q~hEi<zSR4b  kR:-:dwVOmzMyj[:WRY@0*qQh"YCux&fA_<E-pof*#Ftofa$pPFj/d^v= Cq35nI](At$6V2+@KWaK%8 8. *d5.D+1|d|6 v5) b>cP%WI]uj35uaGvIU1Z @L2itl<K?`lF;DLq !Nz@mLS-5  zIlW1*. F6HO8A2Bq/qK7 pN .]$.^RR x1} I|-"\H>4be3Nze]>j6@SIqq|7mO?Bbml$J$s:/  la3!wzVZ2+7kC- Sl) B0WHf~ 2uL7_W[vlP8($RQMVKS[LK@KS[}pG2#/{;B l21zs~KY1F$jw^rtQ 5nd.^PF!8L_ X0a'E_wH[0L-Kg#r%h)Q>[U2%|@~GpQ~ ArOO`;UZcTix\d45(77*p(bSesIj(sjv3u 4gNCSs:.BX7fBf:]0u7 Mg[!zn^R%`f?%AD +72A"z"|,!>_ue^ VTV^[x^v&\`Yz'Q8cknih[m>'+_wqD-a[ 2*T]S%+ Ke`KHbo<:M9kFp@~[?%$R $Y8xHrl"BW}'^xONC"*GL7K*]+Jd :\vI`/QrI- c $QvVBDx00"'x *Y} CgGOx=%CJ4x  '*=9V/["ujlJ:`t4*i~F&p%rmCY,Y3iz>l38i,UMNbZqm0s\t{ot~OBXmyMW~4Q}\`AY_v@{&,e7ozyV6EWB x&Dj-9|?80 #i7l?'&{KzPakOpTzVXF#/9"*$)P^  D$60{=8T#B0h`vgh_cmxgrjHX9@#pi%jg3\+~T=0_av|~OBT!].yzz}w[? +?KY\s/8MQ)YMgeOdBSHLc]qu||ws(+mi &,WX2y8R')33RNuomRq:b!I"'8hTK]^umvu}"P^r)5JNTGA!06AXi2O(M@~.0 qbr3>.-   tTr9 o}qLRGOLU^|6Kdj(X7MHdwwwwshE:#2444O2W3y u|o]y>4 vbvfvetmf}xabW8;# q^wWfzd|TmaeKsLoE39:6B9RRpRqo5&A6=p>}=r;wD~8[_v YctugND fQ=v1a4jAXBSZWhCuO9/2x!n+f8 >('>:;F.H<_SdFFWSaBXFExJH^McSZ*I0C@6CDVET< :6:5a.t4'*52EGDYH]_rzyu hXVM8#Y3 } 1&q@HcLSfPwMi>r?rv|xop"" )K_/5* EO#_dtv%DgjA7 t`>- !h-O<:E#T#_`kjc\L= zg"OF*7$J=HLmV~6I hm~}p|pkgfgn^iR`QWW"q3iN?{KF69(')".3v8pHJI9Y&Z^VVJ8/ohLDF3N;WXvoQ:!Ob8;YYX\Q.t9czz0<FLRJUISR\ioy~b^QL@C,&'1:UUf]`r]aDMAEB@A169'HB&""24</.)dm;D" GK6MYltsowu`vQnQbOVIYIZNXwN~^sOxVxSID3)":AKTYT^FN:=7~/M/F$+ %!"4$F&g+~(  `MH=GHbn~vas^)c!jOhMoavjvfqf`gLVMCXE`a}hd~etQ8H&@`]ieuici}t_/)vfd@&@H'E]_d]UR@=6/66EOjzy_W7>49H,V#q1q@MZxw}jqViTf]gvo}qokqrOB* , HLTXbObGPID?I2Qs7qENGE<4C'="5%8 *++<;T ` \j~uopgf`_Xhcn|r~^na^m b83uZ>eH`GV?WBMJRWphyzuxTN@" 8%& ymw(a-~k6{-}yo o cb `Wa_`jex & &+#zSzEznp~}yjNL)+ {{cXPADA>K <VK^Qc\_cag`i_rhv{vhivsov$5xEGXK]BP9K0I.MD^V}fww]+H'?<3I>O<O6M7DD;M7HA5:z&k+Z_@ K=@:G:JLPW^ abqjw|pv}vho`csf~ q%8{A]Wo_m_eZ`~VbyXuzjtt}fW>-  jkR C 8/"%.!:,.D2?J-6F@EAM2[8K$f(R_^ObWcjx'@<TONOEJ?K?UGjd}|]J?# ldTL@=6@7 I*JS!kc +,C<D?=68,*,',6-ACJRQSWKP8<$ +3;=GDMBE<7/! y}qgzdv|oqckqoy,5MB\KVPPGK>F>D=I@SD[HYGPO8D% -"# 4 "=2<@8?796$+ pqfY`IXOOdWoz~ump[`[Z^iet{(-A;HECG9?7.8-46;8KDP E])B.X+B4@8=.$>*,C1A/;->$6% ' {t`YQHFMHYUotucbthhg`oe{l )'*1)"4%)3"!5%#,#7'!0D.(=E>1FGJ:RBK>T:N9M2H*?*76' |yl{oonueyfnv`xdkmb~eo (.-56,?:< QF RX]TqYcdY\UFO8=),{w}vmjygptl~'% 0 .3*8&.&#+'= 1SI_henrtr\eMK51 ( oqU_KIS9TCMVP^Td^`hVjYd\nbwv   %&(5 :':D;EGASIXTadftjtrmloRg:U'@/~mvfiZiUk\ZvP{bpyosms2B*;65=3C9?@;A=?>3=)2-( qkq|sttz  ! ("%$"                          ! #*#$1",*%    "        qTox/audio/original/ToxOutgoingCall.wav000066400000000000000000012700601415623743500205370ustar00rootroot00000000000000RIFF(pWAVEfmt Ddatap               96)'puZR;4VY }.!ge/0 xm% ' sp 6,  l u 4 0 SW0)}?4%0oe $ + 1* H?MVsphjOT] Z t"z"$|$(( + +(-'-6/>/14.46 6u2p21,9,))&&##) * dg;E (#loWZ89vs߮UOSS?<֯ۯۓݐ}o05  AIs r hh=>.(HNWS**[TȢ¥WQ mh.,荇~xҬج <@TR4:έԸԬڢ}ecXTTYyq  dc5636 !PU8< zwHD~|3/|uúdz^\.2ܘ7= e m ZR##{*u*00s7s7==`DfDJJQ"QbWfW]]bb^^UUOOLLIJDJ^FlFAA<<884400++'}'""tu35  "$Y d OUQO##n'm'++//c4e488<< AA!E)E7I8I5M8M4Q;Q!U%UXX\\``-\6\QQHHBB==9888J1S1B*B*##ot w~ 0,\\ ga۵Թ otǘ†Ɖ%aaӗՒKKabSJeWpx43&% " V Z ee (d\Z`Y_&$ʛė.,ֹѹ=6ĕސאd`|}x54KLÂõȷΪӪhiTQ/2 ~ RUQR54::߸߇܆IA ҥϯχ~GH+&tnx{zu۽mr86]a!!)(.(..44; ;?AOA{GtGMMSSYY__F]B]\UiUNNfKlK,I/IEEAA<<884400,,m(l($ $RVaZ 5 & on  s s ""@BtvTU+"/"y&u&**..2266;;>?BBFFJJ|NxN#R3RUUYY4],]Z [bRaRHH-B.BN=T=8811++$$FI+-l v 5'[Ujj׊ы ˑė7>/$F<ثڲ@8D@=::>yt_UNW. 5 FCn t LOid ֽsw̫Ʊoe54 @=rmb]Ϯͮٲ%"OOvp͹Һ4/طݷ:8ZYoo**  mk  2+8=STEF?8'-1#  []ɳЭЪګڲAASSDA+  2=}{%%,,!2&2/818;>?>-&^K!;MG G   V94 V ZpUZv "P߾ڿҏս!ПʣÕŧoZPQ٪Bs=˝nv6xUvƧʘd0v֟ٙ@+^}h !wX >, @s-%D*\!aބٍۄP؝իѯ̘̙~n,?Gjgx _5."t$(*/.184J7:p=@C!GIMOSUY%\[^VYPSL(OILGJDFGAB=>>:&;66320-,=)($8% !K5|  g@ j$ V "&* O-$0)3T-87k1j:i5=f9@j=D[AH@ELIOLRPhVsTtXzVSQ KHI2CA=r<9742I/,J)&&# D| o 6]G4|<hЎֹ&@Ձɑ=( OuRAYqn0"m*9/[V;  JL \">Bc4+,~4|ܺ9PΨ5X-9Tۨ @̛nr˛âE9qYZRē 6hѨRfgXo^B`7Z HES mp1%OhKԟG\3j܇Koe<J WD# #)q(/ .54;!:A@GEeMpK!SQHYUw]+T[N+V+JZQoGNELCIAF=B:W?7;471R3./V++'&$}" t/l k  K%nH~!!% ) "Q,%b/)2W-508n4;7>o;B>?F3BIE%MHgPLTOWSSXUPM3I3FA@;<7>83!3-u-''."R"lv2/ `EޜusBҽՖ 5ȭk̀V[-oUG[1Lnޞ)|X?q?&  Z ` 5 ,8!OXoN-.Jݿݖ] oEl4߼=)1mЩDwC )T?ڲrnĺŽȭ&ϝ,`^P v  4P }]hW$JKGW&PdaT e = ֳ-TPt!Z or _!$&)U,/j2#68<>BDGZJNxPSHVTSVO R'KKM8HcJFHDFNB3D\?;Ao<#>9:6u73300-~,t*(& %" ]AtV Sx a '%US!%7(B"%+%.!)0i,3~/ 72c:5>P9A%={E@H`DVLGOMKSiOVSNMVIFTB@<F;A2LrGRM2TOQLLHlIEGCE]BC@AC>c>;; 999$662,4/1,H.)*m&& #"pE l    < s#&)+"w.%0(U3+56M.n91<4@7_C:F>IxBL2Fu񲱷sYx+ȡ"3Nr@Ղَݶ1hgnr ~c2R:+a=2g=9!l;zݮ߮۵!ًFۨڻIi92Okr L by# )#.(L4.9R3^?8D>*JhDPlJR8MPKMHdIDF9BDo@B>j@}<=:8;795634@0a2-/*x,Z()%h%G"!y t o { t }: &0 "k% |("+%%,'. * 1,3/x5l1;83;6>8@;C>]F1B(ItE-LHMJJBHDBu><974v3 0X/J++d&&f!"x1g U>K\D@OڜA[~[N M)Wz*LXb2NO),;)R7٢hj̘<͜uimK9RѦ3BwJ#VOj`lrȤ`@b<>7:L<8x:5836 14g.s2+/M),&)#& "ir$iY d  2f"YQ$ &#(%p*!(+*-\-8//#1*2Q3{4568r9:f<E6<914~-D01) ,$') #EZR  pV &U֔չְ:݅޾@zY3cMS CuBsD/Oo XOf3] /ljKE\媉3JԩTO±!@s [I{6`ɜ̛ϝjӂ+eo2c0d^,:*iZzb/i';&iT6>[Zz9E p=yx*E:4+*; XeZ> Y&kA %,%}*)i/.g43d98_>^>C"CvIGMNKgO{L^MIVJtFG0CE@'D>yB*=@;>9<7;5937/14.#2,@/>*+'($$F!o!26hb7% H7#= r% "f'#)%+s'-/)/*2i,64<.L6]0 82:529>A1< D>aF B|HdEJEIRAyE;p?6a9 24./*+$&'!S#2/d_  /q%e 5)٣j P ޜ]?S,٧|b^ݕ>$VxpF7Bn1b=p<ޮuw ѴЃ ||)%ƜϿ/kGt{]Ҭ٨wsGF9@:FAI=E^K FKCH> D;9>49/5Z+Z1-'."Z*g&^") / $E*Gcن,Ӗ ,бѨӿiaC0r׍I\ۮ"VVߺ#h@$x?Y+%i7ޯxS կڷE[˗+ˠ|-P~ͱ&ϭz-S|]ȫ4$It üX<àYˁ 2ϰkh\\S?YQJb+VSy 27^A@CCF-GIH+JGHDDR?R@o:;5W703I,0't-#R*&>^"  r/ BL7(no'IAԦ $ۓܑ ޘ<߻L`hQn8Tn%S۪uؾjڮyrc˿0 a8Ħcո\Ĵy̳ ^>>F t߬Gfpиκʼ@.ĪijEf+,Ә>90n:&="@?i|Qq"n^Vޢj{|ܽݓq^^Y{q  y"S@r"H'/#:,'"1,61Y<7HB;]H@LCNqDMACNL!AnJ>xH=F;D:yC:AA9#@74>5:<3A:18/`5,2H*h/'.,$%(""$) pnj!m#EP!#!O%##&$B(&!*U(`,).+0[-3x/5173z9;6;8=>;@?CBDE7FE4EBA>=z:85(410Q-,t)3)%%~!0"@,j| @xQAW,@_\} ֤( ڸ Lݠ$ޫ~PjMyf+y0"Q=z0uڪ׈ص ոѶΠJDƷ (˹pWeǨZJMqd ܺ/,&bqʜ4ЦԬzٱo1# upB!)rf'3Se=dmp$3 fEU1T0?$CtJ_(2 # -5 Y%9")(&j.w*3j/I94?9D8>IAnLyCL&CKACJ@H3>F= ET 3<0:.73-4Z+11)y-&)"u$,I[ W#z L%!&"'#($*%+&S-['.(0\*2;,X4.50 7285:9E=^<; 87314/0N,&-))%&"6#\SwO 4}C&ݒqN3RA_[RF5=ڦ_ 7ہZv>: 8a&pZ0=SxX1ZٓPVH&#ЃO΄˭Î]Bi )˱ҫ۳­%üHMֻ#ȎTzΒœ>ջ̧ظ۱&ޢw9vI_lM|tZ6R'O7G{cme+`p!FQAVe.f : Eh ]!!$;&(N+K,006h5x<9)B=AF+@{H@I?H1>Gp<{F:fEa9DH8Cp76Cr6LB5fA^3c@1B?/j=-;*8(5&3$Y/u"*+&xN"kGM u !!#"k%#&$s(b%H*&&*,&4.'l0T)2+4,u6.70a93o;6=9@< B>B?A=>::7}64F2L0U.,*)C'&$[#C  Hu& D&XDݯdm3]2sKO"wzf܂ݾeI[4Rcbat>wkN) ۿgԂьҽ̆Yû5RǾٹV򵟲IL@Ŭ>!duδT踄ݺ,)pïTpPJZ5ս)Uߣ6ips6C*vV0AzTT# YS!_qb[OWt^[tU |  J##'<(+-4035v99B?%>C6AFBbH8BH@aH{>G$)<&-9$n5"81,(8$4!PF2 &P!+" #"$$ %&z%<(%*Q&,&&.'[0(2C)4*6,O8-.91;4> 71@9BBKΪܵBIڋqs6zsor*,)KSu}&r x#p8_9b|p w_M ? pft!|"}%[&) *z.1.`32Y8 7q=q;A>D@E@F?E>D4<_C:JB`99A.8f@7V?5>4>_2=s0<..;+9)7'4%1#q.!*#'1#If   V"!##&%%/&''`)'&+;(-G(#/U(?1)3+*5\+7,J9 .x:0;2=b4?6ZA8Bd9lB8@6F=3%904,0>),%%)v"%0!to^ m~jobM rGz`ލ݇vOG[ܬo09[dm D)a(,O(h߀+Z!՚/ X6@YuJ|xy45^?6uBwZ  1A )$#&('`++.02/56 ::V>^>A@VCcAD@DI?TC=PBT<3A;F@9o? 9>7=n6g=4=!3Z<1:.>9,r7*35)2'/&r,#({!7%]" fA i!!\"M$#O&%(&)& +',(2. (0(1(4\)5e*7_+69,:\.:-0;42a=_4>c6p@Z7@7?5<+3\9'0d5,1?)-%w*" 'm#@ j) ( g%P*mfߕ ލ޲]ݰ܆du+[/k`j\'UxgtO vnݨ)c-sЩЧɼcƝY[f~'췰ݮ񴛮+=ѱ 63ľ4T(ŹƹćNʅ(ϑώҕBoܤ$h-S$|F :d :,.q1_x5 a_|?#gy  I4= b)Z<!%S#0)'l,,00 4<5;89f<2=??A@DA4A@@?V@>?=>M=0><=;< ;<{9<7J<5W;3:1|8/6-_4,Y14+-5)v*&&)$#!M!x  !# %y!@'T"($#)#*#,0$t-#9/#1#2$4$]67%74&8'f9)P:+;.<=0>1{?1>)1^1 CBYe(&p E e .`aH#? &`$*(h-p-15256#9:<=~>?3?@>@2>?@d=?<?2<>; >;=t:I=98=7$=5x<3i;29J07.5N-1,R.l**o(&% ## ! !">$%D?'(K)*+,t.B%013O5'`6c j7%"7/$8r&9(:+&<-,=-=-; -8=+5(d2%/", (o%",p{g ` U- 2Fv\ ;;mBW ڵ%ifS֑x]؟ٻۯA#!Rjix.h޼3l۔oځѰd˃FkѳʽWĵ1RMQ>$뷎n GVº ȁY?ͬΏ>Lsէ#ٜd"t2}Qo`gNQn[d%$HO{fOHUsK@OB4 \+>> s sb1j="5&")1#,'/+206 59!9=O@ @?@A?A@w>?=?=>T<>;w=:=9<7<6_<4];29807V.5,2+?/*+@)(c'V$'%C!y#"\#t$&(n).*n+,--/402145l6s8 8"8$8&h9(Y:*+;+-;@,9+P7\*64(0n%-"_* 'j $ NMY61 2 E~Ia)Q9ALܖGnځ%2سMDgg"ؘfZ"$K]&-5 VHxڻMC".EU|Lؔ hSn͜];V-E;:ٵվLR {3Ö˨r͹_ϼѸѴۤ3ݨ7gv::%t@PZ-=]=]"| -&PN  M "_9("q&!!5* %-P)#0-31 85<79!?;Az=As>A~>QA)>A=@R=@<@q<*@;<;>;:99088565`4F32L01-A0)-)&* #( '&C'Ss()*xf+,,O-].h/0)2@3k456X!#7 #T7$8&X9{(:)z;%*;)z9(6&3\$0!-*'-$!"(q D7  v<DtkN@)^MtݪW0ێUbزObF׳]cB <L}t|xֽ(.e"XĤš9j8~ض{w»ɏS˨'ǤʩҾ'Pԏ9Y߻dt܅i>&zA(k ?bV!-tw2e[7(+nU(qv<   !-_iT x$!0(%)+(-},Y1A0b53b97@1:=:<:;;b:99h886644~32{2Q/=1j,Q/N),i&>*$|(F"'!'w ( * *W!+!$,w!,!--b -/0?1XJ2,83[s4U555Y!/6"67#$8%_9%:9&%8@$5"2) 0/-*v']$_!6 _ 5 ~jDN|D xߊ/!Ii۴0@"WՖԴ2լpm^<_߷+H Oߩ2oYН`>Mr"ãӂ¿ZX͌0@ȕqE jÀƢWɕR0R*=Ĥ̓RA-Ё9q֘ڂT`ݱv@! U6oe?%#B U  2 5 [UbC Ss Z 7N  $XGV" & (c#+'.*E2. 6295T;6>;><>O<7><.>0=#>==>=b?<7@:@9n@j8?6|>X5<3:3H82I5}02B..++ *) ) '(%y)$z*\$>+#+#?,",! -j - y.ws/k0i1m2B-3^44|44QB5 R6!:7"S7-#6#40",2 h/, **5'$@2"$&\[a i JlJmT > '!g}ބ܃`۠kׂtk"~8}к 0Ѣ(Oe(iܶܣ==p܌L@ٻ6ыنϻ6́ȩ׌gcԡb &9ܻȺƊĝtĘD68!SǠ>·$&ƆwGʄ[͇϶`kBՙآٱ&13q:$q#y-.d YAz+q I o ?!uGc$`5#To I L   .Qdx!/$K &#f)'I,+/M/22k5s59777!9797:7:8a;8<9<.9=8>D8?7+@6@5{?4}>3*=2w;1>9T1~6h03.k0,h-L+*D*( *&*%+"%,$6- $F-L#;-*"T- -s+..V|/0+0e0g1 2=a2&21 2e2n3 3!2!j1 !/W,H)p'z%Sl# &!zC2  y,1L(LiO]28<ނ @$.0~-3ռi]"+o]Ovڞێ'1܅qOB##QYِӗٍ٣٧_|gzw~Rw5ÅcˎKRֽD:dƭ\/r5HƑɫsF.J3ςCцϒםa=9߶1@CwJ/e7>R32a  dH v ( nm d%| (    JP%K%n' d"iC$!-&0%`((*k,-//2k15(262718+149%191t:2Y;2M<28=A22>2>1&?h1>1=e0M y   { 7 e`m.pAdsva!x#f% &#(&*=*,P-.0p0S2+14&1D506(06/7V070i8T129=190:0;0?W_ 7yQ!*HuIx\w"@Gr}4OfcZ F?ҷkrցإkپig؁JqO!בM*׻˒L,ɖK ҄8Ъ|͖ uoˎZǀ8οʅΥlϚϖ)`k.(ցg؎-ۙV۰$ߐ\I1Ͼ{=&C݋֊Uӫ֜6=׈طD4 ڻYڍ]ڏF#J ׭dO*5,X͙b I.@̂ηЎ̌ѭ/WC΄ՅϐAѤ%Ӂt]#S۫D@ީSKq%1E^qO_   nb[4 w ? Ze %S == BHwuo M"y#$%as'^!()y$*f'g,)|-+-.-/k-12-2V-3-H5.6.d8.9.c;.<.N=.=/=T/M=/<-0;02:181=603y0z1U0/0-g1+2H*3({4'4&4Y$O4g"l4_ 474#4Y4]4321p0</LR.u-q,5+x*(g<&I# L m^{zz1=  _ { & V]/zrEfh}ݍC &1{%Ћ]ЃSt/Ӝ7Ԝ)i>dַ֣֭ԴNT}B- uUk'ʻ!tȢJrǿ4 TjƟŘmũ nșɎˇϳm+̾ \Ί;8Iwϧ6ةx-ܕ߉30mJG_< FeX*s^> 6 _ ^4  .Th*q C ` R"3-i1`[ !~J#x$&9'"`)$*k'S,)-+Z--F-H/8-0z-1:.E3 /4/x6/8/9/:A/;A/CD%ؿ4CԐW~Ȗзɠ8Б˾̀Q'fuЩ6&l8eoَّ_$g{A[V*$P\ X9 O pV43fa f  =3w z # 1/UXSX~ !ti#H$e&(w!)S$+&j-.)Z.N+.v-/ /6/*0/$1W0H2Q133252g62728^291?:1{:1H:191.9 2)8261412 1p0p0h. 0v,V0*0(1-'1&1$B1R#0!/u/b/i//T/)/i.-O,+*)(8(:W'%#d!^U=gIY<  6    ?wOA )<(0Kp7%zAهYԴj'M<ҏTf{ӻۍJD/ h64>ӛ،.&ѨЛ5Kͼ9܁ܞʁ]D.(<؜I3ȷLLҤ<ҧdIK7-ҁϪ]$MѩaZզؠ %ۺ=7ݟ=/XZV%:""Gd$I G1 Za ZO89)% $  PiH$esc>S1wE !"K0$e%&!($)s&*(+*#+,`+-,.,10-1.3/|405@0607/8/8/8,08x070&7051c40m20>0C0"./4,:0G*0H(1&1[%1$1"Q0 /..)W.Zi.F.y-X-n, +.)(' &%D$#!@T ^sj1@P  m  b .w ULH2Bf9<@tdݭb+_Ԕq,ռ g{ڰO8s.ԡn/TZ_jQ%̽cߩފށ0e4LʿmI'=ͶӨΔ_kҫҮҪ 7}Ԙ?AԿwճ֖ٴؐڶچ ݰI3hz#A-vj1 h b 1 > m  0   N  T V9H/Qv |ojG !#l)$%Z& 4(L#()%)')))Q+z*,>+-8,q/)-1.2.3/4.5.p6.6.6z/6/l6k05041s3Z11D1/)1-0+91)1'22&2$2y#r2!1> 1<00/I//V}/..?G-q ,*`) =(/'% $}Z#!)$I/m wur' b c F  # (@:S?rߑiݵk)؜.O԰ԙ/ܥ2=)ר*{oߓɨ]߃ɵߩɰK5Mʀ^Vڪxق˔ع̰=+} όdз5ՄsӨ`վ׷q؏طבFڒ sܩ rJ>c+3h+]1o*qHZ  ;h QhYgZ1vRN(} J\ M 4:F~#F8IBQ !"'#$f&H'/(!(#(%(x'a)(**+++-,/-X1-2-3y-4S-5-5?.26.6/5 0G5o0;4020v00w.0,0*|1(;2&2 %2#,2!1V030v/ep///p/Z.W-, * ) P( ' % $ x#N!@^s1Fc*7 F   Sd B<n#5=E~]'quBCݚo&<֛i?׀ڔsfwGԞѬaAM.͙'͵ Ͱy.̽Pޥ"ͼΠڲe(i6٧7QM!֡`֜؎$يٹٝ"zkIi(߿k&/I0aP] Q P^+#U{Oeqe  j F !]sEF]y ?!&"$Q%3l&E'g!U'#'B% (&(())*++.j,/,0,1,2R,_3t,3,4-4g.3.L3R/k2/0/./,/+!0>)0H'N1a%1#12"1 S1O090/M/Z/_/u/j.*-d,&#+\ ) ( W' & $#t " 8;h8G "   o /j  K#MVB=_ {`4nT>ؖ׹33ՙ֐,xۧ٩نڢnoԀs1݇Qѩ%c2#}2 "_2 1b10/OF/.c.p.w-T,*5)J'C&=z%H$V$p#f" ! ,=^  "  3 + ~  dX3xl4;+n0#^r'p6܏:9g`ur٪߰esܹۀݝ$u؃x֨D֘Nh3\VӬ6ҍ5ӻiӐHo3Tg03M_0юm |Ѯ~A?њЭЏ,Veҍڼڽҝۘnf҈wүJWic.w*u܋|y߸Awpal3 zm z "d " P w   + b' & d M fubR;uCM.96D- !q!#"b$#%6%]'&('*J) +i*+V+;,+,,-}->..n.00y.1W.2.d3N-@3,2*2)2'2#&2$)3 #@3! 3 21M;1a0Y/.i..ys-'_,*8)'V&R%$$##3!R g,MqA-SU@   l v &p 1D 5 d ;C0'gCtR*>&4X8XCTކ8wےd 8ݥiۊRߐzֽ6FԨ"yӐK-[#"p?ӝoҜѼߡޡ6 ҭv۪ъxFXXgTۜp']`,ӅJz36?agx5߂Vd3CgPo  = o ^  T 6 W  4 O  m  ] #ox/LL(c:A +lI>1  >!G!w"q"##6%'%&S&',' ) ( *(*)Y+*$,+U-&,.,0,O1,-2f,52+1*1P)1'(1&1% 2p$A2]#!2W"12!>10j/8/"n.--A,,*rM)k'~@&;%2$"$)#>8#+"# j}A]* d n l  { * ' wp   cCc y ?wsy0vsXc\qޒݪA6?w߹߇QZubڈߣپ9ؚ!?5֕ZԂ+2h=JB+PрdfQkoч[`ЉkMvC܎abJmg܈Ҏ[(P"M, %ݩpP z=SKcb5]iw9nX B Vk \ {   2 EP H vC e r I[Cu=o8ih ~!" -$!%#.'h$(g%)+&*&+'+,y(,.)K-)1.B*E/*u0*1*_23*w2o)2_(1'L1%91$<1#S1"A1Z!1{ 0m/M0/ j.-#y-&%-,e,+*}{)(&%8%%$$&#"y :^ U   P p} #hF;qs  { >eH%bH4Yz=DhaSsp,ߐ>eci6ItڡںVG=8U_L%t6ԝ9ӟoҗҥ҅?"auҬQһv/hѕݴ}~!k?ԣcԂnzԠգק3.ZY*( ߡO!cI2 eSFqXO~,V,"ukj e, G - s <i59f8aHwfN-/ k! "y!#"7$"$$%(%&%'%u(&5)X&)&F*&*&+&,&-&.%#/v$.,#[.!. 6..s/HA/\/1/.--Dv,,+[L+*_*)Ej('-%$$##J##" 8y)Zo } <  n ( G bzMpeYQ AW>u#F~z B5tS{&S(n=PT69>L܎ۑFKڠSuH/>y39֦lae@E'|ӑ!ӧ ԁԧݝ#h\L߶y1ԍ=8?ֹכ٥+ݓVFT xx/99tYWbN}yE Zb a u V 9 0 F `G '2@~w p? b # !Z!"""$Y#&#/'$(C$ )$)$)*$*%+%,$-$e.#$.k#.L". --)-;.o.n.U.-Y - ,Hd+*u*~))s)_)2("'&#%$$$HG$#s"Q!7 jU!ix     q!/W\ ~ VY &nEh+,^9$w>vIW`J;pV>_hk'>߃ސ ?ݪ+x=4][غI؆0V" ס-%)'NJެӠވӷdqYjMq.aԑԒԘR֍ejb7||$ߑ nU "!k^ Zc3[ ;^g5  /n @  a   TB'{-C10I:_-VX] Q! #Q!$!%!&"'>"(l"/)")"N*"3+"5,"-7"-!- $-,o,', - Y-j-,U-W,E,_\+*2*)E)(k({('j&%N$O$'$4$$ ~#>v"f!Q eH~t  R R I ,2n AC Vn-jrP+ RFHQnG~8=zi{=ߝ5,u݄3 tXٶL[ՠtFժb%T}ԧ.`kլL`9wֻ֭uTB|߯9}Fn GQ^&S 4I3G{`LU5'E#o6 L ~  + px 8 $;MLO;P|H-+*^tY !"#R$'$C5%_%&<(q)T***;p*3r**l+6+k\,,,HP,+*Jx*!*)&)h)<)m('$&$ $ #C#"a@"+!|y:G7z9  Y    {/X=AbZ T b Tmsq{4$#V& EM._:D)r Y=qN4!}ܼnoKښt6lؕٔv٦x9Fٴ"د׻؄6ّ1ٗ]ZI$6`Dw x[vج׸׊)| ړq2ۭE%ܖޢMu2uu#V u?@= 8pQ[(B8c>A ` b , 2 *X   tEM@^MUQ   e2Q,!n@"u@# $$^%|&>']O)`w**+W+d++,,i--,.".-,+t*Le*)I)(=|('1&&%#"9!!F!]  ]|>cm},  @ }  V Lzl d J Y%r&pA4gqS,*x; DR(yJ^#PhG{y݇in]ve*]q+GێvO?Eywsa_VymU _ث}F+3(+S*pP*)Z))((}(G(<'c&% $-$G#*t#E'#"!S f7O3: s   SQ"xy<  )N G~ESp?ScQSC` 2+PGE~j1Ln==w߃7ܻ۸O ;D%Zٙ8pוS׉،`c׬hג@ دأ؜جmKdjb$D{,D߻G N@jLrmjaytALb-s|<%CZ6V) sR < [ O J d 2-K__Rd)I05k  <!5""V#"$h:%?&" 'Lc'tv'Kq'p'A'.((B)))\)')"(}(P(V2( ('(#('#'M&_%$\$m$_$#P#-"v!/ox MA   bt2!B{%=;n*d}U / JLZ|jbB/c qwNDUIAx>1/tt _,4S#" ߙܯUژ٤ ًBת$ նՍա2_aNx#xz.׵׸eq49J۬޵UoCYX3Dn(xP5tu+H=Krp3 ]GhFQ?E    o  ))oR!J3#W >]! "N"Q#a##H$$|%[&&8''&& &&&,'''K''@q'-'j$''?'Q'x'''&N'& &%Gf%l%l%~M%$*$=(#9"! M* Gx 0 /yAoa>); @ q   u3(5 cP8Y75dU/7{h=OPB(LAf?}S \ !g  = M vyTA*FTiCp'b<k Bm!5"R""l5# p#S#n$*%B%Z%t%%M]%%`,%V%%%&%x%Y% %A$$r$$wD%|%%|n% %u$$#?#$$#\#"E!! x8 b ~+ x"a 0c0k \ } Z?' pDV5fMgDXj?@nw'^|Mx!d$TPnE,߷Pݰx}_I!v٭r%׬$ec֠]ՌhQXBGjT3BsP؍؟tڂ(*1\ެߝu2xM")2=uU!P`VL~!M:NGo5kcA  |K ; - n j S puRb+.Kn1} 1`!!1"/"PU"W"##$'o$<a$Q4$"##=|######s#I##";"$#s~##O$$##_T#"""^"b"z""!l$!~ 9 CG7 y . L Oc+`!/R3 j eD'2j")A]i.7[t&2F2{I1 Y!,I0jFK&cqdNJJ08޺Tމ]!x۶p/١>{+%sVB,1% Pض66ms3۪CE݂,gߠp)62\lg5dkNWHu3uJU G:^s;SzRZN  * ^   5 \  ; 7*0p4}remIw aN (    a  / T! !0 !} !{ !Z ! ! ! " a" T"X s" u"m"S"]"Wp""5#o#y#}#a##"Mi"T"R"h"U" "o! 'km" @ a yv FJ4_-~hd_Jl  e$ zKB3/rHZsE~HZwdaYJ%|p7 z_4FC޼b݅[9۲8Y ڹ/فtU=0۹)D܍:ݑݫ19k0/=. Sk'QX1K9sE;DII2>v{k!M+z>f U t    c _ aE    > - ,   e  E o &KS">nG80 k8 M# 8 m n @!!l! ! ! ! ! ! "."""h"""<"!^!0!2!*!^ I @\+he B  k .Ozayfj{ueO ` 8|ft%0_k3@8#g0oI)W2mx^r:4h5$Eި98nݲ܉gE7HZS(?5 O*ކi޳N=ߨ+,o >PZ1{!1s wV| 3s[/,!)4YR/"  R 1 x . B  8 {j >@ &! Z p kF l F%wC8 RjkgW)b/  l ~ x Qf @ M W 8 u , h 9 c  6  } Q  @Jk0%'nQ  .  ^ * {w:: S# ^ =4P5Lp_R!@QN.r /" ?HKK'pI(fvHWv^mFsCxTmij ichJߛ CSޢ!ݼݍM; QKt-3K4 =sU/3 fE,_>xkshBc{qL0~ n E O uVbGYWMPx{oa:U  f .      >  } |y)uD$  } x  3o cy}WJ<NZdq !b  + Xsq_{y-*S!YWA8!+]pCIZZ5 }g"(e|E}x%SvzD-=Slg5y߹d`1Q@ya&MejUh>  Y n vq9?xI_-];Z3v}hSETMgUD<B@]`J }}| <'P_%,0_x3RvJ"U|ir~)0IYvsmLw`bN5jt0< mbT9$ 7]cP)7\rIlV <W   9 r[ h< x>I83; Ig.L*M [A#toeiS~mzb Q RU <,   p; %q    /AJ%  :}   k Y)k" <b 8   R-`_Q*<)E ;EVSY8LvmG=5M+<Q`6;k%:URj0 hb#r`c~ 6bl*6sRXp :k -6~EqF4cw0V~L)h]W&(rHNEjDbJ ^@:_"fz # ? ^ Pw%&~=rsN ,n>/Z!u0yL6  <(7D]r7YKNe { r   u0 K{  C   : )- q   pH r Yy n4ZE3?IZ1 ( ? P U {9 a13RYa^@k$B}bKOM[c5|s;D~ARUWQDB0ws}Z4 dR08q _#p:V>Q[T(Q W#"R,O5+q3WinFj q o  q C+o$*j\9HDKtNvvDyyV  24]   Q 1 w  B   b9 4 u  ! o  gZ IG  & N lU j ^ HEUo `y ;P79oogn>. >iVS~'3pkR=A]StLtk$i*N(3e9>=hZj\+ =@X $1K ZD\$^#w@"XVt.xj?!aw;jL@D>2 @|l W      :a.1 +Sn.~9pJ }HiOYJ'\fYT[ bUWE A N c L a   o   ;  6|3Sf/p    A kO7#5m/V4Fpliv%\9_u d&mM 9Yu t>hR&(@eqVz?o<QNW(_fw6}VXt Fo` XI^rp6Gr[X84Q;?7H2p +]   bZ 3   j @q$dXyz0[a4G+%!U AtWA>{2A0auE8P   J  " E / : J6 1r y ' 9 R&5=:K6ljkPN`:NEKhF$~cL 8W#/,1 n,9 gO3 'yrg88gzP`c8^#zNWprf< +3O r?m PvTOPo3H-#=lU3k50t*rIKwj/5p10 ;  $ j h \ 3 ~re ]8r@[fdqyup8@_& 89| ew[33-=>4$WRzT(  S   N i h ,  3 n  % M   > o O8Gf9w~cDf@M@U{y<NVu{e -x .PA]72Mw*X2}:lnu}3t{L-.b<:r+m,x~^:UH7Uy>lrd$Yd6z R+ uL# kg^U3M?'>8LF28z6  < W  c q  S m ^> >o  V  =}RV x>Mc6^lz"t.WuXGZ[)QP $ I  =   N  { +  j | -P B/j4|d\EQySF Pd|MePayW%jRj1+!am,sSwYQB!aDNNf bnO%'NEg/|^ q6VFQSt9Uog<q#:U1xh#j``y_[0 %j$#%9RKe X3Ay L C . Xr   JG {    c     :WUjMZHI=vgaiskCwRr~,C& }  r K T e  2  X bC(4WTTC*y:Pp& o_|m6(Wp+ 1~ \  bv  <%S8vr&[J( rm } ? G ?  f NI P Ix#p`/4|bDw0"J=jGN'CvA0 SL p k _l q 4| V  ,D[Y]GDOv"_;fao)_cXt`<:$"|$4|QvEa\W ~9^cFI?Y g: aO?Sl5SM_9  3 < A 4 a 1 xW     _;  CdCr6fmv8 w"4(PLk r5 o"9oYcR2s_ D   W J o  T uB}<\mNQ>0 UX+w`~UyNs G?p t1  ,7 w) > SM J A kU k$e@:GE_q,7y-e,%"7Q\ILf#UaMS N;g ;fKj{>nXG:}y\4d?1[#X6eH fP S#gbp)`a*K\f ^ @# wn  /R ;s *  + x 8s ?   sE u ) x X 5 0.ZpEP]*t4kQZ>9Nlb[D: [A[D Rq  \   y y   DK[zU,X6HB d7k&h.4UF=O ,V | o - R G k  A+U1u cz9,$|Gt/R>pHyNP?U4wVUba9B3:>71c?q9mXbi?_K_?6Q)iqDz1V)5 K y c b , u K N s .Q R ( f$gOQpbLrS  3u < C  O  , Y C  jt ;  ? +6$V/qN@wGA2waL" e:ROc  6%% z , k  A G  J S F : |Hi5H\0p.X  &?S]MB6.8 ^#st*&.ING&`|4HSu#mD8!"#jJS87P p&k_J |-&@2Yw<o[ I i ( _ x / 2 C g \  3x9rT~DeMF(CTCaG < Y    9  > J (   (    %:  b >eQvk_krF6CwV 7@}$Wf S j > ] 5 & - lb  c+ lGj]TS~sRQ156j]{ :!XE\I2[\V03?qL+(J TA2-znf]I>G hK\2(hiAuqr] ?u  $t. T`fvb Q~ ^ K2 "Y 8 L  6K _> f ZW 'C.q f]]1))# #5(W H ^l #  K  J m K E  Y 4!2WV Z^P'fVt6D~v k$W_>EMF_LH` 0ASj~l . s) ^ S m} S /  Dw  M19-#2. G<`V:g&pnSVhH5t4GijKVIQ[Sh.}S\n=,6+v\XSu2y*z(o4v)!EHd.0h-"i 6o B @  1 ~ f   9 l( S  w Q/(-K6$  l F @ r (  = cG  s% -  >R3xE)gF ;& rZ?H(Ins6B'!*JKaIC:;v% x/  b  kW u Z Y= 4 &  _  ; ir;tM+ bbAB;LP ks7BU3~%e Wk(Yj^=B~A'[:/m?ojSK;R mbDo[D MZoIrMy,ag 5 f  D | r * P _  ; ? P c l S O = ; `N @  ]C ` p p h  WT $2>'# DDrsxVp Pg=+Ge}J.R4%O>6r[/q[[H.: SdX! O fz s c kX -L J # rR 8@4 [V+Su`T.% 7g;5ZA1dI HB" :i$UJ"i cPx:}w&utqvv of3osS!, +m JG+%-`ql){N|^  0gIB E : uR ' o o 3 O H K O K 32 c v D 3 E i  a,WG;|5*Qga h[yDa[v_aPN-e&eKx'3$X[h*7d l:V  _, 2   c>u+C;Gp]nw"67ZWz{_NL-F! CM W  9p mu?uF+%mN|-\EiZ^TEP~-cRX:T@0<rF"i}%\M t>~ Mz   @ b5  iE k = 76  / g ,  K e n "v O  V L9,/TcX_0V vx2~]4Kb|Xkhi9XDQGvQwgfncXf]r1S9. Aqf>ZQ#O4IFH-[C^>&x9zS8_S{ ) };t%'\V # I F L 3  l [ t & < ( + -@ ~> = 1 (% 3 Z 4 Z k S A2 hO   Z 3q955<ONJ\%5\`'cG8) #'XC}.jJ`piF/l*x Pacyu=8 t.a03$(.>d]qy5{aQXSq65ORsg91"7C7 j3EZ>IsQ@v8Yo+{&0ZsP6eNrFs}X/C;c~D>w XadHte; : SL H )g \ ?  +k  9 V 0 P ` | u EH V #,  l  1  Rg 8e*8B |&]6Z1T-~w8R&f[*u@MZG?Xs^? 2?c :OOOYdQ=^g QoDR}TY@GKT-m;~C'X-+v xK?t#^($D=H4D:a~MnA=6[jM$ <?5R0Y@H^G oN&w)I6yLx%35~Wh7nZ=^h/ Q"u:I KVj7r`>| n 4 ` = ~q I `   (   $ { @ <4 w"  w  S t ; q  x QD    C  l O|: Gw$)u|[{ 6rZ%5]?3/Rmd\>ie/mO \hHFo- 892h/g`gN,@IhULQ }xZ6~zWLfG27[A=3{j@xQSUocN<kDUE);Mc0yn(g I]wmUEdR i I! c 7 j Q Z  ) ^a .b Y &H F ~  D= H + s E m s Q * < K +  T h $Y ]0 1 T(@ Y[aExKS3Ut^$e"BfM!]QkXFSXk|4Eu\DgS_X](=vhQJA/'UaNf+~fsgS,0BguOFHb=;kil;SlxZNCC/c)|tfG\.a-iE3$GP7eY^s9,g Ge  X ! Z y 0 y R V u U $F \ #` @  O ] , L p v Y  >g'Xw*XbF* 7f [Zr]m:_wqO<EUk bf% MTy ?RN(m xc5$n-u^0N%IG1 (YRXm.voL;tt;Jl2.c0 ;7$,4vbK30`eC@'+HjsZaMn&iI}>"&'k@j\H_^/SbM+de-yo~?SmzAt yY9 | ) o 5$ 3 = : "  + n V , g \ x| _S :$ FO{$_6yz]Q;TT~*b +fQKh&qjFG;79%Rp1RMOW Korv*<VpWD`"1<7V/b `\t$"ye- )CB{Hs9 hUXX3fGxRY"*.9&4,AtC= N\i}m%. nC4W[b%|[ wKM OMw%-j0 ,WXmogdMA'I2iZ6t#vcodP,6r.D/xUGKLLF8   XE l  y# 0!   + ^ T  }  9 g % K  A      ' ki  ! u + K 4 /  3u    c* lSngz'\>!-@$E  a& h  kH]Q e    Pi  g~2& w" `TcG1#n(@v e_LV6qNgW#`Z7S>UH +,"Akq%tz(a~)}_):s\xy՛'j͢DrT`C_P#//X'zG"0   nk ! ECz=hCrq) )ihC @/jfa7q ~K G or  >Y( " $#h'=&)(U,* .(",%(]"% s$1#!g0@Utr]< 4}  B (_ C  ]S- K /T  UoA r"J7$%') ^*+)9%!IK4 : P  F`pP@k8] }e-ߡ&I%9lZ.y(T02\(0v(ON_OY7;h:W.HP.W@Xe>&aݻօۆԮِ׬C|6ٯFz߫69s( LG.::N  /)g\ p x B 6X @?>L0 q:9 AG8kOQTcj) ^  ) 7 &<I:O L#!%$(r&*(,'+e$5(!f%#"A6!Ay>N.z_WGYH I$  j  tfIC }n T 2 '7   Zk E\7 0""x#=%"&(@)ii*&)%'U!?8\4*/ 5  m] #p_N z-+e5ZyyT, \hGG|R`eWw`M2>;9V&Fz[ rY+Q=){+. 2Pْ+Aml׿ՆЃ֦ӿ:?ݿ߹۠p+Ql 4Vy26 e zDs ! 1 l +zG? P:Za;;F~d= v w  `i/`NAb5" $5#&s%)'8+['*$;(!=%B#!. u tm!3EA&4 > D * > !/m18xp^  Mk A}W   O2 #h8 !"c$"%l&()!(%! eX S N;6R  S&N4W+aQ"7\_߇#v""&0-O6"""6IAE31$hu> KT[-3&_2r2Bޝa#^ڂ\rXܫԶ\рTјDױڈ%ܙ_(Euy|1u 9S|E ~ P '8 W/E]urn}L 1avKh%MSc| , \M a /Fy~ {pvm "%"$k$ '&*)'),%'p"$C v" et[JS%,6/O R 6 }Xc_p    T L ({ !4#$ %?&x'((B%!VW k L  T"UOANbb+?'mjao ,``!YFz M),r$F90h";TB>IqDN*[q܄ج9պqъ8K"Z4ۍ85-1T[?z1^hW  G  { [X,_etfBE:H1;vG15 & S v  >_zI"* "I"#$h$>&v&P('m)3&(#@%2!"s }4]Hh '  v $FM'D  O,u q"9 k >  2U~H {p!^"[#r$~%&'%c"@5,kA P Y'^n { }/kGLz g|3 ]fO 'rd3h6YsqbZBO7QQmulP-{ކN<٪6*ݕP)щ99_1ۻݞN!87mW|u;hjvNzhw2  m0,  =v"8`*8=F(cV|:ovv;T8UQ 9p ]  Am^-D ""$$&{&N(%'z# % X"= 6Qi_@{  E N |\&A x v' $ U ^ "Vw !"#$m%M9&o%"s*3  z \ g_A|ONlrdS*JYv+~[q?9- H{g<;^ZgZ K4AL@,_!߽tf*c|ݛU{p֫hܵ81-Co4=n.+VPR c [ L {w<t).]~^ aM}8#[vHn @* U j  3{e4/">!.!##%$&$&"$ "s:%\-^Sn J <  FhIxi[Iz   [ps m  8( liuHSe .m!5Z"0@#K$r$Q$W!\ !  / ; Xq&"h,m%oaDKEYeKf*lr.wFqD %@"Ee}'!8=oZ#"ۉ#5?W۵r ݣD1 e G SB8j,}7v f+K4eF s J O e3 :!kpgNpRx@xjoPP/-WUr   +H{~0z! d#"O%#%!Q$!"]:4&{wS%!g 5 E dE!ewV;u&  x[:N n P   /F`Lj0 ^ !`!i""  3!U ( ]K kn M U+gq8b&R7-H-qKU}?o"Z60wQ8oaZ&Zc6ow5ݨVRتީB>ڼs݀~tjte'SnxUQ&B.HZa    ) nRL% oSQ(~4zU:d;5pq oO_  B  j p5R '! #!$ #!A7x9TX\k1  b z O r /  0|xdL H  P' 8 < O 1 XrzCg- w!!fu ba  3vd q XRz5*$[t( Z-vb x]; , |1NuxbTV1.rU+Y.*ߍ\OۻtVE݈ OپhyA)p(;sC);d7J`P+ji4B : c un%EG p_" #I g#! RI.(wfHW&  x o @~dNT 4  <B&   c! M P$g-F  u (u   _s ! Wg>9@BM][=AQ,vWPqNqudS@K4 A"j"/!>]Tmy3P\F):{-  p   #) $^Z  ui lJC   ! %1$W}=$7jAVz: \( ]   3 !\?8Q=xOj-zKd;67.W/nc] X7TZ0*phvR' rG Ah C:{G ZD.ݴsH$*tpޖ ZjK4M2`#bh+XZ K OO^1';bWw7 dh! Dl) fip/jf W M $ U T LvzkuJM  :K=l - &A I^ _ ;'[(yI3$N ;9 j %  q~ L& U;>O:le~5r  U1xDb@t5pCCjDlV\b=#;fLZy4oF0ߓދ]^dp?:q8U*~C+_,~Du5LBf}j  fDO33eGF}o:7oD g9yAv+pN[=$R{ G EfbR[a[An/x|8]"&  Q c  9sRX xIX  _7I6 < p  r NN dZuh~ TCc0  O L " kA5P#{G$r]}T>Q4+sh|(*}kqJ}v]g(b @+f)D-|K$-7WxL$LߐxY+܅%xC4Z'LR,7ntI[ =/?&X]ddL u_f\+t kXQz=F$ / 2 + Bj&,z[V8ObJGGt {u:eKvKa m x G 3"XU  ?y E z3Sp ~C (  O  2OkZ5Kw-u Xq  ] l5 _4\~/:~o A,-ErtH%o_4X/7)lljxH8$mEoE$4ISMF {WzLAP)cpo$h56M%0 F|W |Zp,t|-E~pgj5tN e1hZw>!?s:Bj\goQ 3]?iO e y ]  <W31FJY8=4q&\,mH4[DR  E 3 YlrbP  P> K .bY"{ DC ^ L  X s |'L\M   : H  Ej_$JtXj"L_c&|JpSL +D',&kh#3FmY;+!+;roX)G*IJEJ!c{QA%+MCqP~߈މ݇o6~;gJ6N|Rb)^/gryQ&w[~^U|Eb/N_ b~`9b\=[;Q)Go6 #l,| ? c _%#4`2J9+Gt=Nd4qnn^S C N e S g _ W "K\Z r 3 It @(  $  P  gx  o 1 01M)h\i} w w+  _ -l*`r({}O5@N:,5 q%,\a=h6 Ye?3AT/Oc+NDsy2|gD=f7Fs3_߄ޟb\y3YAh }dd'd_K\H lSxnPepW S8UnMIY\8}\#yjF>!9!s    T irS1KlF(hhxM _c$E;WfwPh  { U  PO:@&    .} f t~  t "  Y ] ` #c~Y6m"w  2S % v# A&aa*tJ& M]=. %h}4x ^:yU'5edCc}~fko2|);dW7#hߗYL-Iav4<&5_9.s_/OXZ')8es4ES+jB7~J2_+o[I | 1  H#1z? F!7B$ WQH/.$H   ( " c Y  c   #  t  F,\< ^ d7 Q %3  I5  / > p ! b+u @ > \)7   *1PqKL^V6%u),kKT=%uNm.Q1 LVVR~UO[S6V`%@|i5(7vcf,4T]SDGk|2e8[pNP8\`*ziM[~?^T,IRs4L#]asfb[S#[8@WP6 / m  {0/:s eIe%kcPhT ( j V f L ^   h V _W O - Lo x r  q ^ f  ] ~  ? ` C& S  j  `  ]FE 27k?i"F^lDm4@ q5$9 aVhr9P!Z!)u4) 2ieWB[p=\$Qs6%#Bj/CGXZW9!5>REv?/|1*NWio S\   I N TcPKLS9|}!(Ac|v_u6 W - [ u  q  X L  q S ii ( <    +  f  Y V u h   1{   2! 8 k]6<7#t"{OY7 B(BU&h'#= -v_4el1n#T!kUS^A &?O]5.`#R4j6Y:K4DVn)OMPzmck'H`tLtpy)sLRt#xtz%O{@:> d v D_6z&{| zHr^^@F xa5 " !  q I  W K   x  n Q J & }  A  {! m   Fc z u      ?t ; ? ~  V !" N|&Zf41 ~dX~4-m[y,EKULI1FQ`MH3'1J-q !N.Vk.k]$%rV*lv*;2 >J!0tna&V!jV > r(A>2-owOCJ)An|:`:&;n$@p3 kV! c   O fsttLP+h b&6B@MFPW lR g Q q <   Q e r 1 R_ E n E = K : ^~ T d R & ! Ph   30 sI U j      O E  U 1 Z +^3)jbEK@ I+$ZvEF+?|v a(Qd [ ]h4EiK/0M0x{@+>ya5\C\  6I!#Zx.*.d]wB p)mn;JsV6`s9h+Nv\-VNLRRh z r - p Z b ljmIs\8sH2\7R%B8kuc> ~5  j } [ B 0  b  '  7S "   Q   , u  /" X  Y    / D d   [l    bv aK Q G 8` 6qMP,6$/?N?+.<2Mk@M !B e>'xZ'uu]ekF.X29)zG1-4vOGY)2sf w 5Md:#+V$KzQz K   V qJe,b&:5DK-tcn0{   } \    ~  * , t d j  q K  7 m o F k   q 7 R c "R O I O i k A  N [ / f  b {4P"h +7aj.|8o[C-z"b8RSOEyh}lI(3 | ,-s;0Rs=p v0czx-?C>.*&>NiHq,J1"rn)_ UgR0LtY#2(I07qS#+-8[m,xgcCHXR XY UNe8Ky}:\w6Z^? *'EVC[T6 $+~*;!! :`^75h<7u>z8 x7Q,qTau.T,y"#h`3j0so9o8&8j?H1O'F"VXr'%wk+}HOPOY20 5  D 3 `  _ N7fEVUq8Z4mU8 Ih :  B D   = | 6 %  = w   ; " P S   b F '    p L  n N O    i = %?\]>I(|x'j@VY21^ttsnQi&LXKlZ-&j.#m"%*UY`?g>8 e.mag^[BB85i9Y KNlg47sOf$t?U1*:PV_DdD,G0eCSPCzxB } n 8 A  5 m 1  6 ; lfc)YjhYK8*] x%g Hl <  { S W 3   0 = f  f   c c  E   @ $ * 4  t T ( h @!  \ 8 qu  ! | CW,+97LXQ5d(NpuU/{Agf/@e0AxX<@m?7qc,}yXS!]l8l3{zg :-a5uI|TjKy:L^Jk% p\DFO23`iu.d%#*93l M . )    z 5 ~ 2  )6{ob., o j P p  J L / :  D Y h yT F D P :S Q< ^ Q 1 Q  O  4 ^ = a  > ,qWvA#6vFAr17,]+vj@s|||_EAA(PO` m:/u.f^.9 \z6XX:/R ;* >(T44~~'co` vCg ~3~#fT'EBz1~r/s6vd$.~d'qR2 &xv:! gY ; % 2 J  ' D S U .     `(2 ,  Z X x @ s I W ; D J @ H 7 # + m  / (   9  q  ]~ l S U P 4   ~u V&  p ) r u L3 ! ] >  0H q=<YY7 `!Q WN'<GK*^J V"Z_EY ,]kO%AMV+Us1uV?-7z$G6 ,'^j#3Z2c b]#^E8Wy+CMDqt89w,HdaDu`8":oyH9!A$}J    l z 5 :  C , ' : [ i   c Q 7 -  + W  J (  * S Yb WF NJ 2E   | h( 4 o   d ) V r  #` +DPxf}K=X[C0FsspYZ;8u'@WV%UF}O=dZyZ]TY }nw`i]bEN2YXdI<#?]/tM*)rM([JZ,A1&Lj%:i7%Q?_<=`FA+qs)wMM6gLcN- l  ^ Y o K w % h   2 X z k \ K + - J  U # e N C % O ? h n : | D    s T2   - C u    bD   , R0TI/r: zj6$F,CP8h?Oqw%wx+LMYc>}23;q/ D~Qf67pDKEG-d?OGHBP\ZZYDQ.q6Gy?c[~Q=.yv&z;yUm~^\SnPjbVTL|6:~FZ-s +"fq6sRf|bT5h;   \ 2  4 u  W  ; W z  &  H ! l > o [ S u I - - &   F  j c  t [ 4  ( { 7  N c ' S  0 7  o i' $  v1 A2%X }J JWp#iHmAy"\ lh(m]1!p"oMF4@YuO(3-X?PQhO*4 O1u_E*c@i]G],`nK-|p;66#gJ-28OMRn:%1VUyL*h1< 8? l  R  v $ ( U _ z  ( 4 | ] # M  _  BK L \ D S Q `V q, u q p! \ Kq 6  ~ 2 < _  6   y= >  ; Wr ~L *o^*GXw5(B<3JUhifh#fi,n*g=)"W#| B " duV9v`#cX$vpf^;?W*@U=| L!X?h-!\$r>KP#qaTt\Kc8\U68,Qu=HlO+/1OgaFP"hnEc/|Zmlyx?9`?6XMdG])h J2R@f5jU M K i Q  v 5  e V  R -  s     | O b ? 7  o t . "   z  7 [  A ; Tp8m t;v }15j>it4Cz1GQ#LytJyEt DpgKgYk:V7b :i}]mdk}zfdXmomkO= A Su 1]6<0LJbe{cMP}5bT%w2591R:eye s2_A5(x 6Mx|h2m.[!gLXz* B]   6  1 _ < W y ^ R  c _ u @ ~ m O 9 *   _ ) g W ; #  c   [l   C \)} NTU*13i1W0!n)cX?yr+m_Dm8y8)!f'' T|P.]4tR1t[B?s(B)0IVgXD(pa7SeN|B6A NhWUMS+\qguh2[TM/"A{ )ou/p$. C7g UI+uL|b]$&q B%I.*Zb.> i g  j P A P o | i f P V > 2 )     U   Z Ag %;   Y   BN -Kn$e:AG[+Owz.d_ ^~DWLQE@$-t % f*k~C + QwUR4=%!xY8mrpyw[D& L@ ,Mrme(v~z|vtW}*4.s< e\vZ(mS5M5HA[X(` S0w)n5{l("(oK#Lj fj{?D 8 e ~ @ Y + y  c   2 R 4 E 2        qb X+ @ 1  Q    T  Z 8L QtK tM0IYpDPfbCXu J:,a+1}b]M@;Cqb5!~\nS '&\ rl^QS%6/"\(3R]\eSy^Xj5 Z)"  U? ` ) j d D * ? z / 2 < - .  J       b Q (t 6    } L    n " h Mc # g!o4{tVUB(cle*sTQ:%Gi5bIUNkY%[yjvEN3Ib:_;eBgIm0N-ZL0`5}6kiG+ $]Evd w?(9Ng<m\Es[/aTL3RtgM,VqL<-TqgTi J   y   V      } y gf YT T U Q F ( v [ )   V  vb7R#h7wyTG8$ E^:TOs0K #@4<~2>FTUs^(veq|`1|QtB1t_=%xR/tz[o7vmpybo5u&dSSe+:zH, ]r&3lG]IoXi@"i0 n?oQ 9r=,cDvYmCkN(MhZ.&u  , /: G 3 ( * D &M A $8 $      w H ;   | p J5 * m-fLofEX;m1~Wc<9( g1@L'P*.^$Daz<%yE-t0c>jYP?:F*=+NL n`LKg=6{ WKI oI,}9"TpH&VK,o-T >t :W9<s(,a8~:pB< k oCU03M   a   ! - )      z _ |. s# ] > .  } <  v'[;g? mA0R7 ~_K z`<u^|bJL`N H)xT!ZI#~^Nj)O$(!rE/B +v="0k}H#5w;}5h i3I{[& Vq'.\yF D m?`#D<lAx*7Izpy1nlAp3xEuot@=_}Y e:   . o      c y] j Y U K 5 "z V 9  duM:r5N>58 A=Uetnq?]Q2&W"v>yR] &M{j9{_@ aM -;T+*6q`*n|_TA0F83 }k@$~we^;`RUKVxzB:wK(EOQot2e`  0Z^M'r>k}+Z>] dZ i s         i R G 0   wnXzH;+p<j<"pU' mOADQ'FO3*D >vB~3`6fi2<mR#aYpTn. sElCz`b9>e`;-Q%m,dL+Z&@e Ns(\NQ(IvgyCu^"h 7r#FhJwr?!r\U2+f8rCap'z1Z[| Y X32Kbjpyp {]       a D   Ay<sWAk9^*W\xBQ }S+$[jD!g 9@j;qe98,c';wM{,~d=/y? l-Bj43BUCc|"X5CK/c<h*DeveR5U]v; (,6+=\XuF'%;(t00CTT^ =7ZxI.R`^} xG- K Tp~sFB t%7#)g2+LE'f~\&W*i:C]&3HQ~mV5 ;mp_/\ayEj/zDo2lqe4Q _m{]Hv0Y-7$,8C,*/5FwHw]~[apm3ujlwJki&FEs(3xHt#,CQWt I Tq G7WvEfD!`6D%Ps`s?. a*R_%&!-FUtn[)v5ojE0n U%%81u=B7NbsM.l3IpZ+4hExW,$o+Fx:~G l]f)K3l,` ^&>0a-&d  |-D= ;WSse8UaSO^RcZjUh#WAbvBTYsVJ(%Ts>8d0[ia 5n+AfH|w~5~Av` c #5K5C J,W-z8OdpAMF^q`qwSV90wjGL 9$hE cI+(4[p{Zp^E5?-'|,XGrWdc'\}DpnK4 _M#ufL;#0v/z[q^eoX[bOSEP??u;3J9+e+HM\+ljdaZ'OAVjEDShN<|sGjX#u2Lw&I^,g(Tu*#( ,>Q]qzbx#TCns8zGk~h# K?lr}CO}Xt[hvUs?+QvoU{Jg>Y0V*_v{-|Hk]TnAy*  ,>D0$=1hM`:ne;! c8\~NwT0&r_4-yXJ;0&"  w<j5qAogTI>(i,2NAw:} $iElKN&Ol%5W^*nOesdq~ &Sj<-GQ0cjc%'RHZ mU!NNwd0F}  +;w>cG.67#yH 6 -/UenkgcZ E:;3Zc yAJtQ>/b~]# B`2U%Us|oQ\9T H3E8an~izHg+YXJpO8SG@?$ |*u58u`E8+l'n~ L V* 9Q\lO)D^ 0Hcr %/@R g1c>dWO[?q;t<\w)i } 6Nc[O1A} C"Qi4Ngr#}>kgcgURYQ[RS4zUC3-)7NzbZsP//* ,$%39AKZ").% L \qVe@5ity@$UIG {[o)`^4,~jE5% HHanZPBz>R/ *ro<nx^YD8 .!b09=J.7GO}DVoYuY"*9inWafhaV/)(Twp:cjQ2u=hI_sW3F|N-Z4uUO1#& "%Hs;fAI$8S~2w9_azpf}acjQ[IXXEb2q :\xD{#$Qq2S>s 0= Z.[\w)"q+;,]?>OwdzT b%2:EUx[aQN@JF*TYQtRA$ujfdx!} (YjBueqtih]Cy/L&-n%-v'bi 8aL:,gB+ u T3) ^B'sIr*dHPpG:NJ2I# [)c ~An\GJtXNYRA/2N`+#I;O\pXSX`y +Bw  ?Ec~x3;> i&+5NdL2b%Uu 4JUilrFv2{ 9Xy'H^qyzVZLFINq@Q<M+l @  HbJ Cgvt].LAA)oiNRI}MiMVBH9)  l,TEI943+ S jxw!c\fm^!o][$ED~#A#8Y 5w]iZ]Zt^nr&ay lr%mIy^nvVJ(3Be0P`7mqp@vdSsJ.Bgi{-X %EEv`wSbrp}bbS1 !G<U 9)@g(5:>;YDAyQ-/! %4$t)/65*}H4H +pl_DL(|ExkfUS.}}|oXA0&?Amg|ji^:;*h!N8C3;6J']!Bj~ IBu'=OQRUVmxf_kt:H5\tZXbkhc@< $?Uax@o  ( C5F9Qdxn`LLFIUg/V&Wd)_)>tnUMA'S ]+ iC/bcNbLXPchQO)J v1Y pZiTCKXYMD 0: Ny# C }"4,HbUdf_bVhs&+Z1%$6)~;]{f!rx 5Pal3]jz{l racb`ia#V-6'()  EBQ'SH?wB4B W.kXw0Mzn? Pq4roR]B%Q p`TTLvYeSqbtRT|<w;g#]4_6[8`7_6o-i.y aM#\C~=?Rb  }%.x8:6%srZ\Tl6b JS.s4AXh} 'Jbrxqs]BQsi^fQ-D(f 2i4tv}dzm__N=,  i8vAxI[/~AuQ8b: 3HVs%u.2>GWP7j^obQ G <KJZI0{n`GA7M`t {8~eztw/e'\'@Qp`)nPv55.4`Hk3Su,6Oi} ')z e/}e>4&Q3 uG W1xkY M;F!9'<(0;0:0O)HA vC XQ4^(GnV53+m@]@B:@8GKbl!@Xcjjn w#Hc} c!K)J+A%DO 7)xsXaI=4':Mg u9z_|zzK #*=bJWq|XV 3DObW^^]hks~ynvdaSXYer',6B=hY)5H-_Md`yy|st\.d< ]B!m4uJ/FN\QzU`pYnebXTfYhFc:_ 6e1{sNF%_- cTdDGE*U]`]QG6*C=PWwO`N`_uq#[)K*???3{p bVSG!<0-).1RHW_xsy~|'~b 8V=?G_ e7k$Hoq v `6rAwUuZui{wz~~t|2b.Z$ / 72<FE]MnXJyG|Ee6b2gc~{bi3R/!}T ; ZE"    (ASSREMqGFN4P V^`_Iw3b1  N nwJ[(VDD,"j_>8( {!d>KEBqI~TnivcXOAHEPIVOQ5($z&e4cOLeDDRX`UCH@> E?hzBhD N-q/0/<Oi||veaat~<M_k~;^Ea!,!(&$,( 1= 7:@Re|]+t]XDuTqYGf;efZWLH6}5k5OI4E(M83 #".#G\ bxYfNVkWDV4` _mjpTH_;86;?7S0! {\>,|gO<$ sga_otqfil`qgmX J43 25<0+FLvm]tXKY_]]IN{D@@+]cn<]w  /La)AS`]RB=2GP_pfw'm9rBxYkdzFj1G`fkz%EaJ0qXkAI1dD[F9[0[ ^ ZVSHE5kEiHCZ3W+X%G)>&?-J'^)mprj`H{%yrYk2mtti\X','n\A3 [UNK;5| {y*y"w$e)Y5:-73(.,# #/LXtq}`R:EBBEM(v)"R.x99?-(2Ng:7XQtqs-l=tMz[P]NRN\b}! "**4'M5YVk]z{}&Nf{  !CVtxoL7 %d*T5=K2c| }|~ZL-(#hP5!~~vbw#~m_[%LB6D;LIo?c)H5 |dY T#R,J545(673C1DOYq=`otr=tzl|XV"8F6c(@;MH]V4\QElRSrzwoihqt"%)64KY&v4Bb|}~'>O_dhqmsm r m(z3cHpP\NaWS]=gH# .;^^f^oZgRs9q*znntPwGqk[mPq+w}`R\+SITU_daIF$}$nnaXWIZgsvqv\Q4t`\^u]hZMS:3(-"1 B W r .LanmDfjsjsceFF&'-2=3ErOJM:>9T\nxoskw~ $B`|.%:EGXE{IO\~+/ &3GNVPats`?45B1HLYfyrfLH8@3;;;5"cG!t\@|R9wttsxj]D2)"     hT0/"&||{wstnb[TDR(CPTf"OX}k]:QhOV^Y]SI< 0-((9=8:GH?V8BBER=>&#"!0-2%4&. iJ }mQm9qx5H}o~{y+zBsxm{zm[TV"Y%e.T3SB6C0cs:Xx1Co#(;P_z/Diuj`D=-#&1325/8pDs`rz|r$n<A`Gz#' )29<y(V?0-MP_\xRkGP2F)6% ~bS=@4H@RL]t`\bl[aXpHu>w*&y%{!d+e,\:c?iAd0>2$*1K[|2ZxAO#)@Hgs FQss}yqcG< +(9h9aIWTVu^io}*HqwdSG;630-&-0RQcTOG\F5K YZXM}3t-J V%&{fF<75CFP\`qqvqh]WHRHX[gztrvofYbF>)Z1xxliGf/fg+S{ 3Rt~xx !n h$M8' /<QW^f[m]rn|">p- Wp#6CUs)CXecljq{^?+sW"H/.30L.a<KQdqz  @fy]`TROSNU/>>@OUMJ?s>L=LMWL~<b4J6!  \K/'+0CG^i|}ylVBhK3Y5 coT|(q!,^}9LdzjkOU9MOD`Hm>o,o]SL93$16~;~G?K?>;DUm.S #9d%?Fgx BGeorswyWy: ~sZJ%&7DIcg%6F'Q2[YZv[PI@A:ADCP0851EFA1,r9,13i<0X0&,MTtxj`SC*hL.|hEj_@6 ?]2Rk/a~nwq]N*odQJ6501=BJSJWKEIG\i *L z'(1Xl "'<Hav $1MPlcdth]d7q"u_R8 $/BMo(: MS'_8beY[GC>87;2 A%"#539&%h7%+&bM/ w^93##/AIcjigR<v[;(V8o^E3  H k)Fep.I[i~rpm]i>Y-TC; *# " 38C@NAI@LRl} E ]%/Ak#(IOj#6FUVyZUWBW!bk}gdTE1&AOu 11H(GGHwG;43(-(0-*)2e=^C lK,!4GYhzp cE7q]3$vQ&b]C3*+=&S(# <[@]}w[G6~!p!_$O@!>059??>A67'.3Je'--nfW&QNC]MgUlVa%EHOTz$0AKfFh]iYfq %-X]v1xChWVV7n%j{iMC*(3Rlga\de}!" 9OwV9  *bA'" N 7 %9Rd|suygL0+3; 4&tjjjSbAH2/' nJ;%*K}"Nm a#XD>rNGPUKO>4- zq^'a U\[c]eYUH. 81L_z"'=P>JdOBap #.Pm+DNgYfNw}iM=-.5eguyduVTIa] "<Wz F5~W8  m:% )>Pk~zqzxvN?#2 CH B;$k|miZaF:A 441),_J<"-!` +Mvz'e;SiRSYYZPK9."x x%WhIbQ\]Z[S B7 ,4@Uq&*@ M!NNJpGUb 8Ckt qv.`2NC7ERNf{vVC*" FUk{kgrL~IDMUo{'+DVt ^HtR8! */.f%<7 (#/OUz g%N+3T2`"{%{{ tdL4lxilKx4gi^^LC-k"]8YXJL=<?c{b.ROF}@BB76 idE:1'!"&  "I-fERQPDL)VGsZht&*" ,&7~TXoD!'JjXKf&e!w+8_`#4Jf{~ e)WV41 "%(#u h _]`l  ,:@BFFW[mx^SCX9BpE7yeF'}gZA@FhSbcO}MA<82<d<RhraZ``YF6"o\@3-Yx + &1!E9gJ_jv~u"}&e(9A#[wY$N%C /FMm$=Sj(23ZpmA$~iB< . !A+?s#mnr{&(%( %>JWWV[_p{{[S8A+!{_A#s et\_8$s_V^`}uwi]PC>d&,aPGn0& pgC?! "$ &1#=<TdV|gm`j T(e<pO{JabnzY<*Ocxk"LJ4c{0;4ZLWdd~Vlmj='h\J?).%"| |"(.(1".:]amrweK& uaA5n T!*xqxehdPS(9[. "APfzJ:*, .Pw .itJ*SK~B;0qrIXR3yz=GAvr|$U\V vzBFQ>\Gvn8m1 ~7lp  s ]  E d , ~9U  ' C O  ~gSW(qC)QJ}9`ML$ZaY7S}{@dCa,{oc1i~-''u)omH0eG6hoKPd|i?kA 1S][_8s3;_V<~ms0v(Jmfw oVUS(G 2 Y 1 \AP25@tRJ $r & j n8 ''FS3E 02l1?S73 Q g A YV=P5X~Jp3;YOha  P*|@h-KYi7oHmmn7^GAE}CG46DUKGzAqFoLnZawq~av$]B%|4 >eW2d}s\xaz[s+-dXTSCzP;uj|=VUM } DzjU[@Mh1.93M@Y<DDV\5V;rwVq5) a  Y ' a*r9p>F_:^C: 4 E  } nN0LO }wA~'DRZ 2   * *lE?W-TWky/]]ji#`h  L9o_.h1T h9tmyIqY%.hv9_7 Ez`"(!eP/ `xs:mdi#s|QwU~njNT8!(<B;`A.gjZ!TDR *9WCv_]{cwVUi7&Vt@J J N QT Hnhzz&}5[B; G : 9 k  S ! D}BXr[ H]*<3j  - Y 9^z76I`rn;Kx  Q l##qoRG2;Lazsb=/BVe.JrL,^E,&)\=TH,1RfPT=LVzI9X &n9LyeX@E;+Q"F0q mT(h R*Cj2o F9zW)^lXK gDu D I I @V8Ox]aIxHR46 = L T F J i' \ D% Fd1dIEO+T[n  ! > r l ]H; &.,b2ln8}  / kF>kCZS&&XpChlt]Z1n/w.Ka'iD%wZNBi9hc?6-?^16Z_V;"F|J]b3}'Zt,c2"qH4pn>r%/6.Es-#_6)a;TdOMG[5z2Kdp 9>- : q J Ap |3H4at +* ' @ R V V I D /]5EQ.Tvd-MK r   $=b1C9!)*%)$tD PPG l & U b\-,Rf:hxmS/LVNoQAd}FS$Z7MHPf dZ#tS$7|&eom6zBjIZj d^Im ':RbHVE9]_]u+"rz,OradTBZ[Y% F  \ g s9 K_]H` ) I e }   ]K8y<tLu9qUN n Y 6 qeX1F 3( YO.AGb t x c v LTOP:K,D.bis 4VFm:6:RzJ-.I8j$w?t?vNJt^MSr;0u+nAM_oH On:] sU e:7}Y(6.NjS&kC!e?wW1&u p_A XR>LwW_C~,<1$9 7t / ^ Q? 7Y@Hhz , Q t  - G b nf}-I<(tl;|C' 1 9 a U 4 M L 7'e*SyW.A{Dc\x _ p   #  &74EEm#N"Sf#xmqZE!5pL'Np;qM2oRgw @'T E*fS-IB 2}{l+nk7;y:fBGC Ig 5E[,h@Q;<P >i  $ 4  w EX3i~9 8 [ ! ? h  " )/# $= S$"^|HO!    9 Ig@OCx pb u x 7 - hk  J9a]hz"Z6EK>%]](KN%-p@ebHQg0b-A^2o RB%qT] d537eF|J!K)u#1i.\R[8`;h1*}Eb.R!b:q7w Ns#k\F7 ^  e " E9?nr}h[ N  E q ' U }  $Sqxj(O sdc&  x 9 ex%NU g$E'g%4  l C "  Z#f VBd@yw{T 4nhO+m3 8HGwf'JNG2b ?Oh)ymdS2E;% |x\VLa=T"gf  ~ 1 } t A \ %[m(qNzX0  w v O L !  g(qn |V6.2P2:@, LkQg7*!.)@CNw^hn:| eDmRO@<8417Qfj6YFM.3+ EP6eBYjwE~!SF<JQ'1Sii6/!9/;23UpFH)+ip!|I( ]  N 3 tP5}4?82z  d( o G : | < jG!P[[1,S / Z 7  J r*/nD]82}&i9\)) | k X H A )  dU}C:rD4?X&3B!@{_B`$g{lueu^g0uh0TW,).9NLPnVr $ m  B Nv$[a 5HPt  3G M 4 3 ({ z CScYAh Z   @ r { 4 KJq J+/Tx7Y j L a 5 D ! " b<%kU0X_ ZS|"$!mP]2x#P/v!g*g?3??{\Mc%Aa{s PqwV+nQ6.!$39Xa KT=?t)2;4>zK[Q6Drliq{MZ_ EyBi0d M m  K o I`g7@ @"  < n  | + ) t K  UO,tIp{r_5 ; R h } % ? $ \ft9`1sy$6 { r V H C # 8  r~IAjz'4esJj]^+2w[.-jcB6% q_ 5x%T~% #:\j-AZt2Jy:j{Zn*< n]MD78!2M9d9 xFT 7pTwc G_ %Zg >f<-qD;A<h#Ij 6 V t  F YG#h!    + m  p M 1   9 W cN~ ZP%I2sh   | ( 3 u P _ \ ]|Z S%1 N5j . p J B 0 3 &   pF_G& j9~TH(FQSTJjo'v28lbZ4!S\I5(]LM:UAaznuHxIeSCBP&+' pE{T/ V9xt"7A"_?v"zX+S@VQQNv,+N92Wcl$9Z0 i= P X h w  v#A| EE  MP K ] - [ J `  1 *y=(}}X3,H  Y R r # 5 eAKJ*V w O W / T * C L @?-!yXD gQSkTnLQkBp4v<0dv0Na]R(6(%Md& :;Rk}YWb(oH ^\4Ih6Ph~OgcRZP0xN4Th}9\ VXlCth53f4c3 39@NLd[o]|r|     #s7`FU &  4 - V - r  t  k` ^1E9lKePg!/ ? 1 l * ! 8  { 4 I'' } W m A _ 7 ` :a 6m3q)nm p[gWYJK.x,a Uv@&,zqZ"mpLE;0=7%^LA[7bJgo2 J:uqWM@5{VZ,~M(W i2u9~{^L;\H"T*E|E3Lo?v9EB}WC kb#Za~}+5:>B B&M F*O I C E 6 < 0 5 'qd l  d   J / v 1 #   .z)Di2aglB7 ru y < d G N M 5 G  _ 5 %~GUh*;  s j f r d d g lospmiY`OTF=8$Z%\s9FX@lL?H`Jk^!2(08Bq=$k]JVB4v[TWDc)d0j o#mwMG}Gw Z8)`r4 9^t/`+TyGYxs40ht{q` L B  { f Z H" 'R  ?o  c C !  1 E 4  m0 Vf{]=#\  z X n - K 9  z OI9Bw k z r |  )=Sbw*ml,]I"1Sr:"wZc*fUV( -%4QOo1yGSYE?`C$,+SDV]HV3=y>d z&oO;@ g#j!s0i :^5kIxL|OS\CLOPP?@.#teE/   W B ~ , 6 Z (r  v O D = t ? ' 7 3 D nI&D'(\|so S i f g H 4  } P :  W O D jVvLg 7 b r  % @#o/4KLa m%}?U_iN% 3[q =8ou C&r^q"S5fP` C?Txx" bSK&G+pGMJ:{'Rkd0ma4 Md3?x;]+[ Hh*l- P4 {U*oJVxND95(dJ)gyJ h" 2  i )  "   3 - ~  B O V (  J d e Nw+43"!|GuL \ 2 + x T = X ? * _ >     U k p  & U >Hd3wKy5(]]y\{w''LGs2c.Ow$W~>}LmF[q WN8!G, vChf^=3X.&Pql*}0IN#yr2_C NeV!xBWsI mE"dG,|Y >2P{G8ti J; _uMS. e 1 Q 2 ` H M 0 I  J D B / 9 \  R  " UIs"^mGrn<u=V/ . u _ ,  _ B N ; z + Y br z5 S ) J k 2 [ ,Ih ?|%'KZkj|2PAkfWt2Vl;]<'7wu G98-T@ju={5%Ik0pluyEU7Ko \;oHCbYLo6kE"yTB~cVU>2>qLz=I1`@a2e$f- n ( ~ J  %  "  P  U x   t / E  w  6 ` \@r> 0x[Q7  s J A  V , p  ? u 0 q c Y A 4 w  (K W_-c OL2X5zoU[QdTqp/Up!;Se4ZC 7a7O1dW/.nL A$8IM,*rx1hxIo|]G?u6j:nJ!k<2{nUJ@3+&%'K p;o MVP i&e ] | , P s ]  %  x C ! ;  i m  G  . ] |  6 J)qP//CP[K  } G ( k L  G  E  C |  D  U ? E 5  k T  ;O o:O{!_#^2nY(kd< $@Qe ,G-}UTxy9Dj \W2Za`ow . A1 Jam L>OrVg-q-a+6-zgB9u}vvusloUcXoy$D Ye/5i'b@ p + * - q    b 3 y x : Y 2  f  | y L -  IaWF@g+m#w x  m D E }  E z ) M   9 ] E . B .  6R  t@{+w;ytE`!<| Hp!D :( $  1/RVc~lKM.`o6ZWRYvX +9ctP.\L=Ufj62Of m3hsgW;5 'D+xfar'Z+L<Y+; p y  ] | 0 l x t m Y t E G -  B  g 8 :  l0R{@~&h `  E - k  E  J s ! E Q c # : g I M ! z   y  >O %iI=[`n!c,#3t\cuSbeRdDx2/k'FA:.x a_j?5l}-:DUKy]} j;&) --SIfRnWiMjD[4e(S ` Tkc|q:JZZ,WSNq#NF2J&5YS  ,j  E 0 n  & . s # Z ' , l . ~ ~ R G B  c)qO,4I|D; { I 5 j , [   S g   K B X c  "U 83{&}K v(LWq,Caz9 ~cV9sZa<(9@$78B Z9ub ME'j("f K0dXvB1sgp`pZ0VZ<0-na*1>7JIR`h 7Nj:Ju Wg5tY4JtBFCwC1g6  ,  M     u X  ~ k b P % A   p7 y7\ A } 0 o  N o I d  0 < R Y v g s e G , & '  9 |<E& b c9C'QbNcmz;B"qP4pbjRV6J*,wd#D.X>jUj?(pje}Bo68h@-umHa&i yPO7k3l ")?Fag -8`m ?_}DvV]Uy--a]? nH d#v! }  ~$ i  M c x  f J /  h ] r K @ 9 " &  _NKKe}2 `  + [ $ 9 p  # 5 M F  O  A  A ' 7 1  "  P {  w ; tF^M:M h?="pi[Qi4h]v0vM ^: ,%59KzoJ CR T]J1D``3QPm 9Dfx#9Qi:\,aCGm:qRu'<;kRgmmra  J * N W  j % [ : d J I X A W $ g  ` l \ X L ] M = 6  7   ~vmx  H r  8 U  * ? d s     n B +  }  < ZmYn5ddvfhXr_RC gg5E*|O,j[M14ba '8`o =?~-7<_%j{epj@iO Mh,%gKW rP)p'V*x:az3No!BY$O8]3qW/p)h8dTlxm{ t}a]H h  B |      3 : L N Y P u [ R Q < W $ M  G : '     ' B _    1 L n ( = N e o z v s u u W R 7   co  !P+ 2 @yImXnbrf4wZ8'sA| _?%n DrTN,6 vF\Wk=^/Ht%9vIFb8 I(8"l7ask ?b{bEd`sQN=W 6SqAc%Ad.`%_hw7?*k"SK&J-K(:!c}+yE 8 )_ ]x      ) 8 H  M y Z c X P c B a / ` % c  T O F D 7 P F n u  ! ; F i m   : 6 L F O D G < j 5 U  &    v 0$ q~/2s;3>PAhF[z 9*fj'e(h7acn6;e6L$j?pBp/i ,v+4)DKW|K!W }vkc^cu]$Ob:0te1L KSz-G)KzAd ;%`U I1yDKJI'qhOG:.$ xg<?Oq%4ooB { 3 6 E  C  G ( D D B J ? _ : d 9 p 8 x ; { , y . |  }       2 7 J P \ i  n  ~  s  ~  m  m P  B    n mQ ' P71Bb*`;*hZ3f;B/ZM[l'$lb)1r:e)O['Qe(qH?p+l!h.[+iYD53  $3Up7M&tj.X[L@X3sCf3h' bE~u!LW JOdq^k8qF[$lc;5\m.Z?N&i<a/l   & ( = ' Q + h A o : J D G G 8 L 3 M @ Q Q a T g [ p  _  j S X : 0    s Q S. -sQ& ~v8-}:J c\A;v(P {X>m~zC1 gd3R D O [0Ht;d,j-U#hU{El#QvhlWdE^?n@qCOW^n8l/@R}UCy L!Q5ac,[N0Hg 1Yy9R|"B}0MQoCf.GHZ^L] XlY7\}1c 3 " K  g 3 z 9 = H B G N H P I W  L  ` U f  Z k W c L F 2   ` O " k F HqP@3=sO]LHS5H089FZK\= gl,x-l.Wr@DIq )JxC Ti5 aK%xH; 3e-;Flf ^)H4vOS4!eHPp %WyEWX7~>9MA?%cg&@Y05]m#c~ % X  g ! : ; E P M \ O ^ W _  X ( b  Z  a Y X P C 5 % g N " z S&g2TMCL^=15L5W=H?IRKv3>uB8K{GZv03u4jA]HwBS!lBx[G{/b: , ;VtHAoQxKzHjH 8~O&m:TzIB0~i`K[OTM Wg)P QPWMA;7V{ T=r G.8,\[ew ? \  w    / ) 4 9 ; J A  R K W N [ E O 6 7  |  ^ B !  cG e*y1Fgyer}>eSsi$v7c#Sz-~;#}0NGTOGt2>[BP Nv8 ]5gF|fPu:^1D!*!' #.;[i *="ZLeyC#sU: k@t7iX 2Yj-pV[8=q*yZ%h("p>t"Sx\`4Ww?>L Tx>{2;C^Yl|  > X g       ( ! 0 + 0 2 * ,    l Z 5  _< j6|;e3|G;RaA?XP`Vp dDm01I>SJQo0O["cG$sy->Wz%[*gybfWVQEOAT6X>m-q1 ++;S6Ycw 4`;`'C HlFzo8%kf 9?mVV5}JoBv:&,A)[uT\0Iw+4dj.g3X}*b! O,9R^ v- }C b v                e U 1  qP)j9w? p-ApXd D\u'p<)ME^t*uhh,+P:l%Oqo41|\FZP`M*Yl3P  pja]SV>KBV*_Ex~&Nr"9a+ZjL  V~ (_fh|/r.JCXh {iy7OW'fs?4UY fC/KF ^S- Y[<5pwnigbw&{DzDb&?rJq-Y9e&W,?}2 5Eu#1hpTEF"v;e+w26%>s,R<D Q>uG"Xa%Uy+Yx$1@ B W3 PF fS ]f ku pw o| xy sr  s  m ` M u9 o T :#bA]7wFZ_;#;L@;W g o%{.=Oy)etwH5 _r?\L1aa,HB Ucec52 u\MP,# ?_ H%f9_x4k,Xu!J~C !YrV%Cj3Bx-qUk(diUx]/D4#h_:5d CNv&k4C)SGj)Uo  %  6( 4? BK CW LW L^ T\ Tc hc je n^ rS f: f$ U @,vS"h8 h(g1X^]O')WODm%m OM @h]@/Z Tjj/?{]F!D` "hm":ML5 mi_^>;71! *9\t $-'5NLtXz (U'Mp(a3`%H7ur O;{k<}N IA1-|%fa" \9,1~ENXO @Uv0f/IA\`%Cf  ! +$ :% <+ B( R2 X. j6 n0 n# t igOE \:yT"W,Sg)BkQ,i&<Mq;9a$o"Lke,I`$` Ar,KJqQ2dd).F.Qc9>nsakTQGRA>Hk?Yv ""23AOVaWdOU6z)d K1e@s PB n>4OQ0mx+E_t>Bl0~.LphC!Hf9XVp)bT ~jB.U@ wHDm?gM|baMJDrHtH]U__X}[`kfui1{Q{o2+d=f.XKn BF}hN;oO2q/{LVo0m ^]n+jc9NvF\5Atz%f4Y}0a+S8zKbw%6fm $16<@<;-\W 9(rSr S g3Gf%oB@ hg,-Tm#Nt)DU wY9 ]Sy#l,M{<7nT0lM.ux=M.{M1t]7' 'K_ ,8>\V~u B?lj*)]Ewe Fzd9{5J ^$c#oaMZYQ'cr))_fKLt>m9Rn5e(Cn.=Vk})?Nedvs o kPF+)lCr B(rAP5O_p(/_TU\*Sc5VU s?AupA9Sl2^P,wVA# }S?d2d9!cO+x\S8-2Om .$;>Xbbv69K_H|Ki;xC~&rd3 CrsD3u}5HCcVm(cT?TxBu.i>Xz?Lo?BRN$b|;r>q_,tfJL%s.eN?,$ B;c q!3'F8_^fd#8Ebc4eHhLG.o B={ X?+vKjXb9Sz.on OA|A*IkKyGg=]u:^} 9EVjr  gI(hI,p<e.^ #wWG f:@QZ%bd/!_P %Yi;=P4_z!2px95 F_&UV+~\3dA{w`lgc[K\=k9m-20&80&3A#MFcJym{!6Ak^~|&<)Z}S&\#eP&kP g t/ ~ZCHvEDR.c4r6`]ANIyx~ +Y#p6Xn=Sny#9D\ertoVM9wJ5a<uDv?gO5{r/  \K(~^@#is+Bt._k#s8n 2Ib*{s66 YOnDa0 ~]_GB)&utjnffa``5e0sLvZo~&!DRe_-j(Tq,U =i%h"L+`FT[,m/' eC) l.~.\M~F;fYw ><lS 1O h/@dl$9F^f,>CIF?:',wcM-{d8kBVowFEvLGtnOEbj#.r8xN6 T?s6\ MW% wrC3NX{Ru%O nqV_AB-0!)?BTWjs(9#bFqXx 9\&Pw>|=]A Fw1i+k!THe] KTM 8p7i?qW8RMxy?i(Xl5c4I[u)9HLVNQEIF @C8+qU3}T>h#y_\.; }z9G EM,W!^0q2[z+7{T>fP9[Q0%yX5%M]9 tQp/;{qXqAY.ZSYdkz{,F'bM}Vs>c 0UFr8p-m @xJr*c!X?CFz!g,^(X3c4p%S~XO5~]yA`$Jc8Wm2A^togG:q]C*b.YsGKeq+@tJ` "^!pF#= qI= o`='ub6#UO3 nY0*`y5U +bbNG0$x^VJ74# ,+7 <@-[4cP[r'Jp +Xq7h#N FvI?o&gP~;j+]*ZE8e/d0\F~7*YZy$N>R,@gGRx(5K\j}1M_m|}ovYY@8w xcVC/M)|ybX>& _d87F^+Z-Y@v IXR'qYH]J$}cK9_l6RS{8R #|kP\:?3{r [UB 7 4# ,)5;%DP6N*bQdFnl4Zx6[z6b!D-R7c 5j~:AIKZar"D[x 9PyLg -]!T-SJkO|0Y2aK"}O*Z3k'd'R{J}$MPog7 J%~RkCTkzAMf|  vdK5}WlPU(-}`x/I 'V(S/R+nDTZ~LQ%^!c9r IwBb=fAwI$qbgBM0*lUH*$ z!>Obx&.Qe2Qt6j 7b5P8 h8n!F:td/ l<\>m:bH=WJ{75aP!F,^Xl BL5eLs]~przvq`iPN>9.(}iVhBJ)5}Wv/^8r4w O! qTpDN*S#b5a,~<^.tNtI!uM)|VBb(9o}P]%U63~t]PB58+4.0<0;sCwC!].d3xJ x?h6uK"nGkDzX1 |ZnDQ/.pO6$hwcbVMD>4+!ys {r xq,.=HO^`pw/Je{/Ut*H u(Mj <X2i-T<^4_8U@oL g7g/=Ldt  2'QOzr8S(\AWf#.1HDXU\_S]LUTQNB|2\&P/pWs6f!F1|V(qQ,dElDc;g= q1c6f7pG!]5}`:n"HmuKn0L7 wtSO/)xob^XVTN^]po~$=Kf{ 'Gh5=v(Rd,U"~oM,j^ "=EIi{5"TH|r<@^Pth| 3DW&^$n5{.<32/'*  rf^KDx'vdXQ71 vY=|_@+}_;c=zU)fKlCeE"d.oHo3O (yim.e+D=xaI9%zkaOD6% zrnphr,7Eeo6Ed|4Sk%?a1Ip0_q&9\ j=W W2hb  .1CRgy*,CQdr<%E;fLx_u)6BOYchqlseplqmjZXBA+,yv^bCJ0.qbM9_P)uS? g@hG&Z<mE~U5o> tYh>Q%$ kR4i$S6 {n]I7/}td^JD3*& ,(=4GIP_by +9HY kZ@s=^^yv  #'2#/* s s__MI:5 }tWO2'pT7$aW,YBmH, lM,`;_Kt(S -}nwNO=8#|fI|-nMC%!kXI6'#yogc^Wa\nlrz~ 3=N\ey+A[i +CSx(I^|5Np&@O tBe@2Zbyz MCg] 5-=Jj_xz '<JZ&i2k8?DMLLPIRILNJWOVLNEA:--!}\T6){pI<~#a U&%mJt2e >, eJ"p T6jM4Z?!nihKP'>2 lR1(sST43! krbk_^WLPGUOZX_\bans )4Vd~ %,ISh(.MUt &@]|& > NLc`z$H4e[p.)OGgb3T@aJwdv.7E\b{z|\e>; xgPi7S&:&dH `P0%z^>xZD+ j_?4qQ=aCu&h XL<*xgM@.&xrXI<## ,6SWz +C K_*n5NVjx'C]v4 :4]TZp&C4YKxl86AVgnn/F(H.eHcQ~k}.?FSQZ]`gqiwdn]ZPRBG.+raMf7S)= &yfA|&^ S5( uH<t[>. |eXB}*b"I.udF3sfP=(nlUI:,woX S<0%%%56KZc y "6N^h|7Na|5 //QASgpvs &80XHkb &0>@_Rvfz' )?2B:PVUde{dtru{}}|yxvwg#u#p'u2~2s47x'-}~}|~zxutnlhdWQqDh/I$> &zXA&bZA4 ug>* kd<0 rjLzFm.H&3 geD2#y_R@*wl]SO@G~8o;`0W%I$A?0@ 3?B AQO_bn x#+.?KZ1h?~NUkl);Yg* 6(>@XW\nu 5$JM]Ww{(&M1`Ozcz 2!E!Z8h.}@<@CFBHB@A<9= 8AB EHE RETGKOIPDL@H<B59t(i&T H1'  }fS:qeHA,aS2}oW9)zjZ{Jl=L05" pWL*"xw\S>4sufk_aQMH|:uBs5w9#&' w_R5+vwbTK7'! ~vxssppqchb_i^nisws"x9D\d#9U[,5=ML([<]Pmd{u}//<DQYcq{1F#b7oH`o )8AO[mr~xpphdYL@7/'  zeP@#pcR:6lQ9&dU9+~pjYvOmIP7A:"4# lbPDA/-~z`XL93 "1AV_{(1IWp}$*4cRq`sr.*6JPX[pz )3R2b<kS`i 01B7HBHMLWT\WXTIMBFA>94*&z{_V@1ee E;'nUB&|kYH:%~mj]zRdORA>?)5; '.onV]>B/v!d^ MB4$ --EEWcq$3:AQSk v "#35BES[\%h8nBqU]u')B?OIcj}n &D-H3ZI]Kt\jivv !+"3'2(+'#$$!}uf^HCz+s] Z?8{dO;#|fbJC/sqwdoaRXET1P#JE @53 uy^^MA9+~&{"dcLM45) ($$$.05D>VO^nl "43H@ZN gfw !(.7[LXR_gjn`phrtpzozmvboZeZ_Yc`eljrws u}~tlbRHg3g+WM>4}dR<+~lZW=<wit_oHn;c/j W`GP58teaSMKBOS`ei y p)%.64B@GRR] `e1l9A/C8CBGKITMXRZS]T`Xd^i_e^WUzCr@g1Z$VN>=% jbE9!kcSH:,uucVtOAm8|'h"k ^YHH22#{vsid^RTFxMtCsIrDuGuCt=|B}8B:EHJSJ]Le^pv|#1->>EMOU^cj kr'x)yDJ}Yzep|!.!E.L>bRj`tm )*1'C0G1V5Y6[9j7k:x3}6,&"!%#**,54{BAxF|DkFqDXAb3H2S6C+/tbH9)lhUF?%$ pkY\=J&,sv`cPK?00 {v|mzgrijhffnlyp~y} $%:DJWS`]herj~ pu 39HU\lp /*=4FJ[T\isxq   $%38:FGTV_ hbqcxl~qx}|~{w|vu}m ugf!d"`+c)U0W(H)I#=:0'! eVD)%w leLO5/!nv\`JK45$}$qh`SGA6. yz"9BM``mnmzt{$#51HEWbhu  (/8>JKXVi`vqy  $, 7 1= <GKRV[X`Yb\gbdebe`\ZU_Z^[WXM R JKLD!D<84-*! {pVJ4 }lbPL0/mw]hMU@?5+~,yo_^M G@.-  !+8KSeh|sz #.6;APUbnt #!,#B?G;aSePjg!-&099GIJSNOSMTRQRMPIK?K:M?EC8?242/ 5+ -$# tfSL+)naUH:+y{llfYYHO9D-~3w'k%c"_LV5A')#,?FXcn !,8AJLWVkh|} (+-.>BGFYVa_{l} "!&&6(D+L2;;4=807/20+.&-*./),#&$+% yr`[G0* oc\@F!* yvoid[[RMJ>>90s(|fjaU WC=*$  %)44KUgt 00AEQV\dcru~!&/02?BBK\Tfctl} "+&'-/#}     su[_H?%yn\VD;.#wuhf_YTNIC;<'+{uk j XSC/.  ,7)??GVZs|#(35CDUV_bfnwy '%+/>7@EPNVeepl}   wtkrfuerjqsuxvz{  zpi_SI?/#uo_WOA<-% |ymk\`OOC>;61-#~| kjQO:9-('#&%,&3.?;FFQUdcyz $2)98CEWViaul} $ 3(>2CAPBS^beguwz~js^ hW d Z ]b ]cgbm hi nr qu {rt_gQSE=5'{r pY]HF?/4# ztjh_VU@K:9:,+$!~l i VRMFFCIEOGSW]cgfvp !/%96>BGWVj]wm #001)BBC8OVVV\mgtp{|}yihXYRNULUOU P_TcV_^^becss{zsjg\WKL@<2+|}kkcQU@E62' tyhf^SUDK@@54++&'}z!jdbWV[Me Sb _eno {t }"##'.)7-<;AFGKUUdeiyn !-$4.=4C:HKPLTc\fjyd|xp~||t^kM[GOEOCMFLQMWMVSN\MaVlhtvw|wvrohjX`HOE><z0u-o)hj\ZPJBB0:%(u|ij\\RVHI@<7551.v+}(i)i'i%\*h"^&f%c%h)h(r(p(z'y)z+3*5+563=8>CCDMISSV]]mlo~s  .():)@3=:OFHRW_^g]skzjsx}~z~phf\UQNLNNKMPPTVXR_PaUc\lmvu~x{{nviffWSO@Lx4w;r-d'k Z^ PS@D832$&wshjUgKVJBC<>;6|3y2m-s3[*q.Z,j+j)e-t(g2|/w/3.83=9>:HJMQMTVX\bhruwutxncjQ]AH<y6u0n'd!fZ WUFE<27&# {tnhc]\RQLFLMHUL\Qb[ffeuhxtww "+'1185ACDLOXRc]naonym{u}{|}vyqrnnhj`bTPD@4=(:&5255>6K=JCMHNRT^binnsqqnibcSTL>B16u$q+k`a TTEC902% vsji]^TTKMCFW`K{Su<g4~A0UQWYfi 9+y|(#  X _ *;d ":fCt3+5  B g  1 L iCJdq#/>^~7'8$yN &  z G # z ?r=Rx(N9h1 H  g II eqm"q$4}!8|)ymcz3,>&qGl\lj 3ALc!5IbI, nmYVVP]\lWo(4iv DX#?kHoWuQeb@OWW1z*7X<w^?"zyGD&'Vfg|:Z'<bw<]  - \ r 50v`"fW" D Z  < l 7[{-@at&E\TxeC g 3  ~ 1  c '}9]iv SOv X ! X  N  s$T$Z"U A}5v,V dt1,1tz1"|2tEk" `WRb[o& D.W:-(x h \XRT[\ky 4qZn;>jp&G[v 8K4\w}n7BFg Laa<"pQP(-bb?CPiQotu%U}8Gk % . W d #T<zfMb7n 1" t 9 e  M  <mQy3Vv BqDOM%d<4U P p 7 H \^ r"k [ D  * ;  L=i/omJo~xzsqqpz 0PdPh=hF]gonmt5GRz{ 3PrZ J-$fh"!jaN:+x~biBl~q8ODKyp !  M )wVh;Ds ] 4 ]  Q ) X"R,g"Sw4VI""*TvHB M p  $  %-v%t_dU ?  %  v=kF_ T!g8MKK9*jQ4e%y;_7)\R"I]<c&]nVD<=IMR_p{&DYy9Q nThO__YRBOJZYeo 0h0^t$R@@ ' zorseu\lXhh.Iz|i?;k^' O / j E a v M'~{ <Q B  X h ! w 70t?wY:bk e3EzALK \ Z a  Z L7hN7 ;   gEzS: sLk,DuiE*P,?$%P W2n>mK|]r &#HHu)/=K\?(+#)BGfn 3Pz,IHA#QTDK20~|zx {!*<<i*h1f>~XP9@8G?J@NHWWbdhrCV b||nt7(SDkXr  1EO:tT    [ " x 6 $ m'vSF M~5p0n@f8FfEV a  S U A ( z ^9,[P [    tZE~3cD! ~ZX>8&8$iIS3J%S&pJRF>>!>'G4bIMBpl \Qdhpu)Wk$FpD})^2 q.aC)&tVE/' pv2p=C^'iizfv'A.[Ksm%)CKIevper5@_ _ & | * x . 2 2333`?m N ` h G * { O . X4Pe.n*e{bL*D[fkw( b  d 7 1 h Q n* t   ~ylXxOa9M#4]9\/RZ J .i] T}LSX>u/f #)Nl&HlAx0`bY2LB,hN) i[I80z"c_MHNF>c~0VEul($PTvxq8I7g}^ZvEoTt`~ x{*06R " . : 9 # _ ? #ua/j4Q#h+s_!5VKz4=" v  Q 4 t R  f n r>~5   wzdoWh\'"iHd:pPY0?$~r mW2c>A~"Jb@Z#\V?M Pv7-q&GrR,zY;sT4ysEV=Y6/VN~%HZ4YrFW KeQpFg)M#D!K2V5^7b:f-d)h!^ a R  Q B @ 3(8f ;   z   ' f [ /,l]%f; [-6Zeh+YFlcA( h E }  Z  l  l ^ N3 $  *5B<4610. *&(lm>'$g6uu1Mc4\y/P |(`'6je0g >]SUD `[-u=,:A :dl4V1tHgI*o:4VZe9rL"^3i" fGH;1+{w*}}44MXAO"0   w U D ! j  w( l O d  w  r  c P@2 sX?oUcH?B:L@j] t $    z  l z QWu z =  )Le*h2xAoGS~mii*3 ]"p;5TX?Sk @3 E*y:j Fx3oN L&Z+ }%HOQ$b|-` g0u=m9m EtY/!_;e6oL N9H*?1HC -:)2 lL<xd H -  | P > D ? , 0 T h ~ x y mcZSA6!qk627|48&@DT( U * M  ;  w T ] ,  Y 3  'VAsSy 7.H9_AnSm_9}P!e#|74*5AFBGam3F#3 4g HG]T"q3 d8rU, i`7 YBHj#4 zU2M6|d mg(y,.;P]l(&&kP5t_I* {F * c =  x = ^ .   / I i r  z w}{mu\];3#B;p * z p d M i & E  e v / B B  " )"N^0FG]a{&t?wA [ L"v slffbpj'R @H=Z$j/ Ye8O3aB$hS"csK+L[ ?ha$>_q"7~KKg[08N*zc5#LAik)%Qai fAvI/|S#yR( h 0 f B 5 " *  t s  / V o    #uf;Yj tMm,T 8 l r 2 ; @ V / |    (/V_):Ady 6+aVgR"GiS9o4UwbK>0zgwiEZgwG[H]H dJ%wnJ=* t0=W_$Jcv :T=Q_rEG* :V4woC/vo+2gm-/r08 |Qu > yN^1G f , Z   0 g ] S L j  3 V s  2?E[Sf[fRi=L@D\p.?c2 U  e u  ) k {  $ { d 3   5/_p!F^{'MGFiGt5aQHr&iW&r%XA,{hhV.m~6T~%{ 0 n   >Bd/Sk EAr+_Noh2 +g!m$h8gRR =}M7{X XfSuHIot=? zqVY=H2<47Wd L^# C$oME-u<cDH;f__ =1wiB? wkAF{AO)q8i 4,p2n$w>Pp y # I } L @ o   1 H , O 6ByDAse, gf5~X"KT^ju  m l   a U 3 , v u q # h {  @Co$Dy  IISDxX/g?i0XBEvBh$L-bV =2j^^je*9|bbLO9A3>?GS=!3_r .ATAfR+i.~F**-!>7zgBB!ksA2hqCK#HGlZmC M=mo{9BC_ B S O X , X k + _  B 5J5<vuQAaD~M 1w4H,7" ( r h C ;  | H  S " O  IT Eb78"oGG,_2p/%_V3J{!NpBm+U.m bm>f;=XlAU,>.+24HN`m_ 1:Ed+Hj@v2!hdv'/ioED!_dC;nrSUE=yqx2Ks#Tu&hz#,|+_ }  H ? v \ > l  E  x 1 y6ZKSQR=~K 9ZA{ ^'owWP) " q c 2  ^ T } r  S`,[vr{xYeE g2n0>>Z}"|/wq oGT} , ) s o I  G y ' W " f*T[jw!p |lk8`?ru8s JmC]" j`&  _ W 7 $ o @ 9 2 i  Us=|@IbHkT(T7EW] u/D`}!0ew?a > -^f16]&H+F6NCcW|w@PmsUEC3D!9,n'`([7bFn`y:29X'/ci:Ffh^_6;/4EBdmxAe H<&pmC6jn % * |  X Q K  P,Nn +PPeOtCk0W%5pw*(l]  H 9 o X  ( & 1 {  Et0T`l,3cFaUo08Zr7Kcr#;Vv.LqoGr>[KvQh.!NKy!Nici)vrKa,H u&S6}!wy|Re4D$uuvYrTYDT8?,1;Bwzpx !_|'7k jF%}KAlc  y 0 .   T  g , t@Wk9X},B\S =[t&J#O];6hd"  N 6 p Z z ~ R  K ;c5\ )^Z6)YS**F ;c?CZc%0NPnl .:Ue}*Ra}WLr9o'l!* D:mr;?~?hfQBuoGGm=jJ_1I*71%.r`EQ!D$zt &Jk|@7qL,fN}k  } * ; 2  n ?  d' ~KpLn5fs-`%U1 ,%Zn;r2?if4%^Rp 7  V  a D  _!^A^-:gfED!.b=p6--?F\bm{ !)>FZh&y=`/1t6CI`F#S\?O6a WBZN(LH k L\)wh^LP+Ibmf[sEZ,I+(0H21TY~u: Qcv j5 c ` V +  f<e?yN? LX*iBg8kWvz 5dzCF{v# G:oLt z,`PsPfE\;E2+% y[937sxx )4B .q,GaK,`VKS?fYQ-6O cphgsy:3_T5$u[W <';0K2/  0gq)D(H5a?uIJD8& n0ku wkR4lR%}K*Z'zFW n(1bp,-SNx(>"^CjJgbg0p*0`FeRd^dd^gRkOkKs>y2Vi=$  nYT/j%ZJGCD[dz8P)&2jK."y)xe+ tA&~b?*Wf   &+(<4@BH]lGM,@#JQMI~Ch8P$0  tk8n yUE% }wD7\7qFX4Cog=1cSt(8@VB.f89WYkt/;J*c/w3)Gx$kQGC,3 yW?^<=-~-'(;Oc#3f8&J`C0^$sKvC%?KCZr  )$9/KCVShbtz \n,F4#kU 6fyLH%e.z#:@9:z_X2&f>'jRW+ ,1KLyv!L9ZNewp/k #GRp (B@jVj}Ac=LUJ&| m \L>*}}vnZa/+e>TNVZn''='TEagt,I/]<lPUdmiympjN*m(U 1Sj*;^d:gEk5g]t?qf<1vWI1xegLPh0@ TBe]#6B`#A r:o7s`Peq 3X+`)nfk2}7|`W[!=MUVtqx{ 09?Femq~(e`?]!l4T+Yc/?PLhGAa#*$' !zd]F9`N{EsClQ|^w "0HM1a#]=l.l>l4k3m+e$_ N9p EY9K+qPE"K.s'GmChJ|/gM,s`HuBe#D(KDwk12hp.a,P c1qK0+*8Mk 5] Dj.jL&jD-SY"9;QSjm| 2;FNkv(ETv[j!\~:k)g&:BBPDKU9>VZdf``ZPH<1'|kry|*36@4C -8%$TtF<SGSM6sB-k4F },>m;m A~O;pN.qP5:0/*  % sU|"N l3=QXnS v\%~N(E7Ogm5c7gAp!B!uPs4V1]c+0GRa8K!vFv$X5nnl+Gl$Sy+dCn$[X -:Zq *RVkq2DUk}'6Yu~5P{*N Ex1t}ukGC9u\}&Wy'Far$!    }]GlEHk#PR  {l2~P.q?H=_z6H ]~&CxId!AZy.O,zWV$L^(Vr$NbHt#l F8'$*$>F`t,Kl)W4d0'aY+ARg%HUhz!4Mb{2Qo&H\4R|,dz EQ/stF4mM!X~5]s$I`n   ]Sv.I@jy2OTX {X<X<p4S\ Lm%q7Z vG]4ey/X%h~GM Tf&;zJz)R3n]?.v#sbre#y({AD\t *F7kK/3NS$:Wis#KYn~#=_n *?[q4Ux5RRb&ff8!wl; p\'\#Kx!>dzDUy%'+ *" + ) $) r]u@_%C uBY"|PE jH-oD3zp65cv$1v}.N K_2pLQ+uKZ,j{?C Yq,E'lN|0a G&yykjjnw| 0#I5gYt)6HWt5@Wq1GYv+B`{(;[x+E`~ B\HN}S3|zBuZ+Y-`(N{<l,Me8Zm  * ,155<79 .)f]u2V <aRZ3so8/ v\B^d4)mv;A{1H O\ Zm,-z}ER fs7<y;C#[GT=wOFz#bUB2- + 2J4UIh\$:CXj &=Sl0Jt &Rb=Rp *Cd;J36pch@~;sA C l9h%FtDi0>$g8{_q#02;";?@86+|jEc2M " |fn4Ke`@9fN++UJll/ApBIJc`j,u:C`c 9av1AX}Ca@ dU>z3gZQ>=,2'"!%+0>C1V7aWvb/=Nfv'7Qw;Je#G]{+Fq4[nJY==}l0_L AqH~ :r6aA e:Y6P.mG`~ #( !a|Um/G- n}GY0|vVN'~rN; vr;: Na!.l~*D @YY_0qy@L hz(/mr;QpHj0J7 |jSB7h fW OJ?==4>1C 9 H K%S7eOlVw/EFUr3<`s0PkAYo"DXw(Ihy$2NV  A?zs.eQ}>iC},p2W+d-{Sv CT2tKg|xZ|O`(C1 qRl-3kl40 kS;-bX./EP[s$4w &y]k5D!vvWH-vVSNVh~0KBc5b3G K[$o@Ew{?Mat.E!zV:\N)}`bEH6{8z2o2q2f4m9j:lFnEv_{` 29HUsr$*HJq $.Vn 7KZ#?fnDP{.'ba J/g+ _5k9Y/^"Rp>p<e0bv( :(\2_Oz\i}w`]o?_9S,  ^pHL$eWP/gm>;lr9H^r"; B\'bv.CV_)uHW"yW]1lGV<uLy6\G'mk^^XXW^\^lp} %3;IYos:Hbg>Njw,:fs 6Bu}M_((ob I5yb U3gN*Y 2 ^8a%B!hHh $ 8F2`9_T}Xykp|~|om\wUiJ_5G*7$ `eUK'"~eU?/\W(*ee-.\e(}@N !]t*:MY8uMb.]n%@y[v4S ,lVw9^!J .}xz  08LGdsy 5>Xc 18]`!MO MS|$!aX9)i[ G(tdE({hHjO~L'rOx / O-lJl0 :E,X3X@mInTqXz\t_|Uo`qNeKbHU4P9?0 igOD0&xdK>#uiHKNV# KV$p>O[z-6 Uo3 Rn#D r6W-sNl,M 'vaBd.N;%##@:QWko37HW{{IGo,-c_.#VZ+-aX6%d[<&oZ<&jT/ YFf /]=}_3F6uH{n~42I(C0O5Q4M:W/C8S&9/B/*{j^LF1'pXQ2( `a03 vvFFxDOsv8N `u09Zs+: ^z6MLj/EaRl$G )cXn=X,H8"   $(/.LMU[tz  +IXkr.@cfADyk:/d[7'e\5 fQ7aR,VKuB1lXB,^Oz -K:`G}ps(  3#+"}j`TM73]d?;lOQ&kp6; s>Btx6F jv4; lx5IuI^0mAV; tGo5E 4 eafB`=D+?!,% "$ +9>BJR_iry -3?Jwo*&NXyx"$]L~{ J9h]6'i[/]F|% VCqJ7o`&R>xn D3dZv;&QEkOrwpo[YGF+,`gFN!(ap4:Ub4.jsA?u{;C u|:HrMd& `r6O#_CW9vUp9S3imgOiSN,?/+7(#,&%!+')2627AKEPU_dro{ +1DPjrAD_h 74bPD9xd 2'hN#RGfI6ta.'\L7.ZL~H9[Vy,=)U<hTw_p~lp_^JP:: +loGX/0xNY(.zYO&gh;<  tvCGON,Zo2FyWd%Ia{D[4~erGZ27#xyrh`h`VS[SNHMSHHOQFUYVSdcglpv .9PSdl 2+Z[sv I+ej=<w~MLac25xNb+ vJ]*9pM\%=wyZsIB-= ~{{t{~i{u"*;=WThu &&FMgf! PErh E9{e.MDo>"fO K?wm,MIzq?:fb  *RFY]{"2*?-GCVDUOeT]Xt^a[w_f]m[ha_P]\VG@KD;)1&* msI\8@& jKV";\lBC]k9>IY2'vs@Sos2P#jE\2gKb*L " ptXjAC/> ':9QIXdy<@Zc{4)\Os)M ' ~Qj8K#S_/> go4> cc-4 Sg15Tm4> !mxEb 5r]v8M8z|\fWQ3B=.%  #-1?:NJ[cyh"46[]rsF9eW}.P8|h T/r_#K5v_"F8od@0VV{3-R[ro40OB\V{rw   `uXe5H.> cw?Z%2y}HW)2Xg?A hb@IkyDV$yOd-9qH_*>j{P^/B !qpghTLIKA204.!#   !&"2,54GDMHc]cgu '<6V]sm.UEud0Y>ygG8pGD,eQz.&[K{83a`| &&IL^d %"@9PBa`tg~vc{Sg@U,B2a|K]<^h7K  ~c^1BqtFM'(Ud2J lM_&:zLg4M$ glLN27{vkdtY]QaHR>V@J6R=E>R:FFXCKKcU[Zofto|{00?C_Wur'@0eMi7WG/?ooXE?@*~zwu{rwxnt.Mect.,DARZngy  "%+!5%1;'79"910'wjQo>Q$@fuT].6$lvZ^/0jpCN',|_qG' vpUW?6% y~ljW[FO=9!8" $  * )B3A<^M`Ztnx &9"V=kTn. M?eF|&Q^.E,u{N\@H# npJQ4* ls[_*5 |Ud8A%boDM-; qpTFD<&t|wWrbb>]FQ2O5B&E08;*8"0$?'0+A-<3@4NBG=[OYNgcndw&#I/QAn_p<(W9m^t1@3pZz!.JCkn #1?HWu+2FN\boy  -'4?FFVVZ`kj{wxut\Zr?k6W%?6n{QX=H$ d^L@,(~`X@E$%rvU]5:%sqK_== rf\ILA0' {~okebydv[w`t`u\vkvaxmk}wr&!?0I?hWq_,L1cP~_.F2c[r=9NW|v&%7T_bh!4/9K]Zbv !%35;<GKNOXaZdjmhxp}vuv~qotld}c}WpPsKd6`8PI6(~[yV]1@$7  nwbX?A)!jlMG07fsTO1:{}TcJH*-wjn\PGK62%, /*80MOZLoj}s(B7YFncz)O8cWv(2D;chu{/6CKacz#ANIEboto+/*'<<:4D@I=ODSAPAVGQ8R?N3M2G-@%B37 $b|\a=\422 glVJ46'pkTR>; adRP06ggTV83(( {|kiXcLI7G/3-!  %$+<)8APCVVc\xkz /;3[Ci_k0(N>dY}76OThk ,'>EZZhn59>BRXfenu     rg~TfGW/G*+# im[[B;+0 qnX[<B*( ybhGR29#txecP?9;( aycpL`E];O-L.?=44 &-'! ! % $(-5$2-E2=:NBRG[WhYnh~j{%6J3_>oZd #:#XBe^v 1#8J]_ps7CRLWcw~-2:>BG\_W\swrskeYvH`?W1?7|uoP^OF,2 jwT]<M$- xNdMX%3-~zl[XT>96/  {kh}`xXsVjPcKeKXGaDQFYCTBSHVATHWK]NYUfShdn`uqym}~# 3H!T?lAx^k{-F+]Mmaw !!1?QLYr~"4)8OWMapx{+*-0<?IGOX[[fhivu{{|wpidSPgAk>U.J:+ |~eq\V<I73 |fqMc?L&5'|sO`JS)=xtekSUAF4;)! |twy~tw1 :F"Y9c@{V_u '=%M8aUwh  /0@JRcpp}(0-3GWQVcqp{ ''01:3D@KCWG[NiSmRs`|SzfVeZaZ[VV|RKqGtBf8`6Z&L%H03wvzZdNZ?>)9oiuE[BQ6$zhtVbDR/5) vzkmUbMP6J/7/"  13F L.]>iCwZ^r| $ ;B5]?l`}m$+8?KV]fr #,./EENM]_okx~ #,/*=!=4F%G4N+I3T*L4R'X0F#\'A!W;I0:$$ ngiT\LJ7?%& p|YkJ`8E&<#t^nUh7J8<)}mw]nQ\EY4F0G44 &    )3#:!H-H1`?^CrV}Vnm% @E.WClQtqx !(2@EOSbgtu#13@<IP[Uefmm|#$!*"$)# tkrenPPLT:9+.$vjtTgIS-H/7 ( ~t~[vU\CP.:,)|wcxXqUkGY=_:N*J/H3&?'!2&!%  "# '(($4-//B3:;Q:KC]A[RqJk^aix}&"E<2b;^Uw]y"(9AQFU^rhxw  +0'7<KCNLa[catnxrrm}bhUgSP>I?<*, "xs{]jQdCL.I'. + vs|\rZV@T@4&2$ }wkfZw[pNoN^IeBTI[:LFR@ZS\\rr~v  #3!*4E7E@RLZOc_iasm|t{{ttahtSpQ[M_9F>J,0'0quhuZaHXAJ(>'0 & tnzbpR]KN9@00'(w~oqlk~kfuhwarepcq`kfm`hjjbillknjjtzmguypx}55L&N3d?hPv_n|#.0@:JG]Qgcsg|  $22.@5G?MAVNYOe`icioxmmzz~wxuqliad~[vWnQeH^DU@M2=6@!'(.~lr[d[`:LEM!0"9 wpxemVYPUC?884(!!!3 5IK2\6_HuWr]q|/&29G<LM[Yf`ns~x   ($8'4,A8E6EDPKKNXZO\``TlbeYvdn[{fvZze|Z{_[y\}Z{UvTyPoOrIeIhC[CY:M7L6<%>.-, "  lzp{ZdOeJS2J2E3- mvYmiXLWSB>8>42!*#  %*6<F(P+Y;cDnQrYks "**;.=AQHRPd`ehvry  ,$#&5/*6793@<G8L=M:RAX?R<bBQ4dFX2\C`2X>]0S5V2O*N3I!@->0"3$$  |rscr_jL]J[8I5D#</'}spjja]^PRME7M@0"B,%.  1,>=%J"R:X7eEkRtS~emy  $ $*87=;FETRTVfidhr||ww*2"*2%66#; 8!> ?!;@"9>6<.7)( %kxpyYg]kJXJ[9F4J$: 5& &suugfd`R\SPEO>D5D-7)5'+ ! $1-4@ >$J.P0P;^@bKhKt]z\mr #&,)-7C7@KPHTZY[gdinrww}~  "  !#  ~p{lselZlVWHbGL9O7C(8$5(&  }{ypfyi`ZkVWM[DOAO7@7C,8'9)11"%,!!           .#5*? 4I->'U5H2b=SAcDgSmQub~_~ou| '#)3.'<B;8KNIIT\ZXXkjffxtxu|ynvmyihYpZeO]G[GP4K8H(5%<%.  |vmuhu_i[gP`S]DSGT9M?I4J3?.C06+;*6-1"4(+', .(%. ! -!*%!',%(."#!4%%;)':"6!7(D"7,I'A+Q1F3\6KBh9WKiDkUhR|]paetm~ "&/1-34:AHCALSSIV\^Zfdgdvonpxwwr~owcsdmUgZhIVLd?M=P7M-=-A!.5 ) {qqxfzbl`lWfUbP`KXI^FLCYADBQ=F>I?I6?CK27=O547M652J;;1G6A4C5F9J3?9R5>8Y7B9^5F@`0TBU9f@UDjFaEjSkHo[vUu\|haun}| %!!.0,386>=ABKFKNTNXU[\d[ckoeoutp|z{zoxhtkw\ncmRhZbF`MY<XAN4Q6@*J*57$.%%zqqvl|etfv\p_hWq[]Qm[YNcY\N\Q^RUJ]QUJZGRQZBQNXCVOU?[TU>^QUC\K_GZIhH\KhCeMjJhHrTpJtVwYzV`_filsw{ $#(),13/7;>6DAC?QLIH\STTa[a^jggduqpnz{ww~vle{q|coduag[iUdP`LYD\CO=X6J3J/F'>&<4.+!! }{tw~jqwgjuixdujj`zic`yb`au]`]sa_UnadXlYfbiTo_d[pYg]p]p]r\pZ{\s[zY|d|Ui`jhnjzu|~   &$$-&+16,;4<7C9GCJ?SPTHUV_X_Xdggbiipnnpwuoxyz~}}xn~~gxqyhpeq`h[kX_QcZY@]QV:Q@R9K2L3H'<)B36,' # }y}yv~tzw|w{rxrxsxistzkpl{ovfvr}jtjouinulmzhresfrmvryy|  " ",#-+2*8/96<0D@F;LGPGPNUO^YTQfc\Ydhhfejpoirpuq}rzuuxtvy}}~~|{}yu~qymqynyiwkocvdiYk`cPfZ]K`PUFTFU?K>R4F6J*F/>(A8!41&+   ~~|yx{ss{zyx|~sw{ $ . ')7%/(>68,@;?;I=EFSGIIVSSNVUY\YU^f[Zhj^efjitcki}iuhphshxe{jwqoypyrtrqvowj~tzp~iso{gofugpbobl`jZeWiU^R_M^LSH[AMBQ;M6D9M.A-C*A%9#=52+)!#    '$ '1!,$3+5$85:.>:A8@AJ;CMKAJTPGKVVNL[YUT^Z[Vc[f[hZm]l\scoZxisZxgwfv^xoy\ymye}htleyf|j|`uland|il_vej`n[g_nWeZeUcVaRcL]N^GVFXBV>N?P;K6F7K-@1B(A%9$?39/, -(    ' !.%#/0%.'7*8.35A43;D;@>@CAEEJBHLPGJJVNPM[MUNcQYLiYbKj^dJp^`Or^fWkVm]jVj`nYf`mXm]fWp[g]kWjZfUh[^Ri\WJgWULfLUOcDRLaAOE]AP7W>M4Q5G3N2A+I,<)B>);9<04,&* !     &!&&!*.".&1#3/3,649769;=9?=@9GD@:OHAAQ@HMU<PNVBWI\JUHcLTHbRYF[T_KXO^PZM]S]RYP`NWQ_KZV^FYWZHYPSNYIRJRFVGNDWGL?S?Q<O<O5K:L.I2J.@0J&9.D 9$:#:3901)- #$     !!&#*&!,.(+*3(,94,/:654=797C:89I@>8HAI<I=PBM@OCVAPHT@QJUBMH\DDG_HCF[IKESHQHMAUGMAVDIGP?IFM<I?G=I<G:G7G:G3I;E,G4D-F0@&E0>"@+<#5%@ / ;-8+4'+&         "%%*!$))((&-00)-+<1-/A4./A7;1::H386M:C8J;G>L8C?R=@7OBG:H>G@H=E<ICI6ACP:@8L>D6G=B;I5;:H1;6B3=0E29-F1<*A0@$=)=(@#8'=5#:2 5.5)4&2" )!          ! ##$#)')$/(-'3)0.4)505+9.:0;.B0<2B1D4<5J2;8G.<7C0?5@0C562G037J120G47+C79,<2;-919-8(9,5(<+0"@+4#:-<0'=2#9 6".4 -1+/'-)&&! "         $(( %'-!"4%%!6,)!6-/#7+3*<(7-7*A-5/@1<-8.=-9,8.9/8'954'903+;-3*;-3(9)4,5&3+3#3)3%1 5%2 1 7'-8%21$5/(30%,2#2#+%% )" !        (!+')) 0 '3&+0'51#3&9!,+<#0,5!3(2&2!2-- 6-/$.)2#,*3 4)*8)* 1'."-!-!0'"3%(9'+5) 2/,/++,$)(. ) #       56EFbawx|pl\vuk_aTq^U1#!^S2)^X$& A3=2q:(SO7# `JV@8,n_}/soa]/$AA/;::3;mkOPln!!WT.'{ OZ+'_p&HKLR )Bch 9F'Yr.;SFY[p$QY #]m&/elDO'/bl<@#)$06F1  s S R   x 9 0 + ) {naE< |EAXamh  unvl/'oe{"F0b A v T { a y j ~ a 1  9 ) y!A4eZ{! OKS\y !HH{ROBI o`VKA=10'wqnm #md:;z8N.i&cl.KFU[r*<=S||1Mmv,1~CC{~?GWc"+ESCSs}2KV{(7NevrvkwDO/5*mtLF(  z R H - & Z V  -  lj^[>4~{MD he?<~`] E;XJogz  w  x  v  n ~ ` w C # .  {]"=.\KC;__5"KXSHgmPS:?|o\J8#rh\QJ}t-(&,mqKP.=*::I~GZay@N[cs#3GP zCEHQLR RY`h#=Sm  a a 4   ' ,  Q N }|xn}swpeaSO27~rj`NjTtq|iucc N O 2 . p ^ > 2  V7P6rf?1wa5.i`%!VV+-.-rr2/YXuj?7n`:4<5HK(5/5Z>s/o] LD-#`e/*GBMR2,EMWP rg2#NE7> so ab r{6> ix?K%l`n$C-ILaJY/A4>DJF_ M_HM1I02Ax-B4Et\u%AO^_RX D 5 Y Y m Z p h gUt2(<3=582,&GCngE?WU,$, !tiSA-  | ] D $  a D R ? cDH9! RB[M VP MItr&&sq6.:89:>0G=c`}| !KP"&=6QY%-]b=L:)aq,?H`Pg4HrL[FSEJ Xes~7BQYPY\cf$%;Kuk|}Ze "dp+ei!;Bnj~ y . )    d _ P=x(J8\Vlp|xtYK,,^Y&yj]<2r[8 ! Z @ G < e I bUE;>"|r Q<)g]1,t2$>6.,~~gi Y]k^ JQ./<:dd++nh)%mtEO(5& G\c{5Cg}^w(G^#Yc>H.>\iaqm{*0^h`s%&5Ogs(?S"&z_f]nGQ T[ k~-/HGQV dZ [ V v Y I }s&wn >7h_3)@AJNMG OKyoWT>6 bY* p< * O A l +  c Q s]B2TH6%x&ibHG;0vr91<3 PM"#olJK1,}yll C4Xdz>6.#Wk1H -}| t=> }m}w 3;]_,SX6>'-rxsz &dq!$BGl{-8^p#'YpMgNL*BIV?DUU\UKO; : X T E D ? @ ]Sk_ C@zt @6f`~ ("WWD8 l_6*m`)$r=*> + 3  Y L {`>/l]iVXUcOJMprhc=@**dh[b1/uvKH+ sx;:yxOHK@6, }hq`leioty+@Zy '2DU`w3Afqcsp{$ow)0Zi$]k&*cy*1Zl2:&6Au~ ho\c@D/!e Z  . , <8aWOG4!j^0(WQ|x93lh,*XP|s:5K5H3; " } p > 4 }aC2{n2,?7ZH[T d]IK HHupYOTQ"W`2&ux82ll[_SU@A58.?4IGW^mwjx[l &kr'/>PVsz ae|pu.<w?F W^.ryol+/re3&MR%)|ns~z{xt{y .DTfp|"4xev_vlzz~+.Cuv?Pt"gq6>]`1T]-OgDQ#6kw 0>Zd<Ifv7(\Q|x W X K ? yk3)ZY bRQNF.mvq{H>>30/r) fc;.t (  bQV6)  zhG<wgD7vl;9=CK5v{( kl" MD%"VP;1to?; (%/2;>OSiu ,6Wh*-@+,B(~YjI\AV;O=M@UGb,")v%dl5G y~9Q ~Qj&7 18Ma,9H`h#$D?c\w  to`R"`Nhh#to&vf_\qnqj6-&g\TJ-we5"x\J;^>0s_K8#e[<2 }9-wy&(SD{G4hqA7pszTO "'+?IV_ux!>Ol+6|o/?8H (|Mf4A 1# ~ow)am7EYg9Cdw0D&5P;Ks(5;LTfm{~  'TJOB }PPl6)BAJJS>6tzRXn O=)jW/r]![?xk,eQ<%- ~m_T?:% tq LD,/YU~v(!RL||5.tlhpem?ACEbe!'HSsHX4DMc@[8Mi;oZaBO4B/5`n^p4A"wSd)4hZe8#\l{&7\jny~ $"6+:29/-`c_Q&w;5xM<H8jV'(5'WOXHLIjU.'o4)|l-bS.@!tjME4=+5&* y vofec[ '&c_#5@dRt 36cV(-e^mkQLyq``fj&)V]&Nj KQ8O-.10)" }hfMJPE_`<%`K)hd,[QIJ@==2xt R=vq ;)_ItrB/ZHQ>R:gJ|^p ?jp;;  Za:BltWc8J.7 ")10  rx\bAH&* }xB<rgvm\N(~YG&lS5*{SI YN OI:)}z%\OH3yj9.[8GAR2X;eI{i/A&NA^[tnw& '0TN^c~&)G@[`}rbcFF15{0.';IXfx&;%9UhNhRhBZJc Zv5P^}Sb5< sweuHXIS#4!3 CR z`mCQ)(plJ?y 5+ rgC5o`?.rc9-{OE1%[W5%e^B7k\ >.gUt@ Z@n[}&I6ha :-RLzs61 /.GDaasw(=BTOmm}QD,4bjTXZa o|$*L^]rf|$WvXj;W1E:DBY]agqNV,9ontXkQ`BN7LEKh+tbkGH}QX!)[O{o[W/ uaA3![P* ~WNA9VJqj(_V% R>wv 2NAqY}mW% V?vb 7*gX/UHw<8[PBCIARRca|x;4@@]Ukjxqbc745:}qv),>Lki-]r!:As&_g/J .bwci_nM`npsmnR_=H&+ k{xi,4 foEL*QT!|@G fYjav]Q<1naH="yrICpfvo_M7-XOt4&U9cR~horaD-M8mL@~% XK.#]S,&xt!9*>>MI@:=;=:KRkt(2`j1jz4I&;Xo+]x(DuP]/A$%u;:?QEO=?!4?R `h55`d"$FF\Pb`A1ONZFHG:%nXED/ n`F?/!KA~D0_Vn! >0G$5/u;*~n% dOK7z$a`P=}-0!A>TN fp),Wc)is%; YpJ\F]dv0Ojx)?l.A|S`9=|$  ![^Sb#@=OU_\ ja fU  iYWV5!zj`;10!LEp (F0[T|`sZ%uc+s_ iRYTJ?71D=x|=?iiNR P^#s8Q 'H[{'Dft4^l$8mzCN $4;mXa ho)yr# |&%woqo  SDrkb\?A?* ucXA5 ='UQ~i)A*VMiT{kvwU6ta%j+w'{w:&|*&.!^b94#$ fx=0l~.+tu#9IU*Kh*> ->DU -EZy$AT=J~BQz #&+77C\iYg W[VUGPA8 l`:3fYu|txfg\aHG>?''xeRJ=* 0%H8dS{p(:"G:YCfQn[hkw{q|kY?*lY# x9'L@ZUhfzx*~\`KE6<((## ]h('rABT] ~K`%mHZ,H+<%7A]\q7L_s*Hpy&N^@Hct} )'6F>@L]`h$jmIL/3$gcLFgY 0&UBPGeQWO_JVEK>E17-/~ssbP@D6</;*N@WItbx % $<"<0G1Q?Q=YBXHW>F8*\OA0kZ>5aJux.(loYXGN:6)-  ioIL!FKo;< Od+;yfy`vgwv $>Yjz&6Qe)6[h)8NZdtx &98@JV\ajw0,n{8;\`A4w QBRI"%'",%!#$ ~xf`SbQUKmWi]p" #!/ - ymG3 |H?m=5]e5GL xBA {|d\TT:7.0 zvsM]>>NX+_h,@ ^pRfx!#:Yu~2Abo!.FMPWrnv &),CPPUhu{HDp|89vzFI RMLK?9j^zzkr|kzzbN+R<[NRF  PDVT5.v]bOI36  vroN^IF-T_&6|H^18tSt<@;  *4AK]ct:N[i;Lfn/5;?[\an#0>JQ\ky'ZT~17vn('kk'#]SH?} K>ha~wfR@2 ~xL6g\,%}t97:C nlNL!trQR?9"$ lt`]FM.5O^:?iwD\,3r_iA]5B%=&2!61;-EM[T`k1@R_|,#tcA=vwE3 gd/+{KO c`B<{t[_@>% roQX@B!( Wf1?ivFV&=oj{_lav`ki~r"3=IYqx*3Bbq *9@M\nz)(3Mcft#JSmr+3mg9=od 5.xr6/\S,!KBWMkgu}u{gfYUD8*$eV94}~^MWX%]O )ws][,*{`c?Ao{TQ3<$%xZe6E'fuF]2@/,2=NY`q#&)>del+(2P`gs'3;Xh{.1PY95a\)VV~ =9vh)@;vg"2)MEZPsh~q}~kr^ZFK<5yt[H%&i[.+ wKN!e`87kjC<"d^AEwvU`<<w^e7M!)fpRm=N'>- $+<=HK[htu"49FYow}#//Aemq (5PXq}1@_`6,_f><odD4oc)$MCn\ 2!37]Dbath}hk[WEC0+{e`G8h\C?lpRF uqJH wSV/&bdA>[eDG ~[kGN6n`pObCR5F'9%3%"    *&02;:JMSWafs{'3DJ[pz-5@as !7Mpm>@Z_)/e\}#"ZQwr >;fT++TFgc +55VH_^}m~{x~^YPR</$%}v_T>8 tfNO*'abGEzlJO$zWU.-e\DTdnz$6U\iy)=WSpJMdk;?h[))`R{yA6`[y&%N@e_|2/F7VUo_yozztrYXOR9,&(~b]LG'  d[:={V[4,  me>CwvLN)$__:?Z_AL |dpH]-7( odjZmKYNX9QAG1E7?1B2<2@8@1DFJ>MOYV[Ypmmu$4ETVgt $>Iag=Ia_ %XZuo CBne25bP{~@5\VwF>_Pxx=-JD\JmkczsencSAL@0$"rjRN4,rvPJ-(qjOO#&b_88jmNP"Zc?> bj@I- oO\>O.oo^k^kP`P]LTFXFPJTCQRVH[X]Xd^fs|kv#2@JRbs~ 23Pcls%+IOlp :3TZPHpj34`O 54^N}xB1HLzd ):0MB\Mgbj|y}nnbfWPAF9-'yx^P<>$d]??!mf?>WZ4/ jkJL"`h9A`tMR"1y[nFW->,nqzevbnbjZl^bXmcd^ojodpu~u"3>FPZk{3?Ean 02V`z{;6ef2*F@w :+\Wt1+[Iqo *#M>^^n  )!>-G>[Lf\q_}vn|xpoc[ZgLG=D3+}|f[EG.&  wnUQ/2 XY<3ytVX,$liJLbjAG m}MZ2? m{Qg?M&;%s{m{u}t~qxzw} 0,4@R\eq1BMVvz$:?dh"@Fpf1&TLuq  6.YN~y -.I;sp>=XIkl{%=&B;WA\RlVmchtky}r~x|xzq{p~fmakY\LSDC78*) }|phKM=2giF;!* uqWR(+vnJR0#qf?Q+cnJN& w_g4I 0 zf}PZ:R'0+##&CJIRkor!-COaf{-'ESqi,$DCvp.TQrg3%RLynA=dZ}u 1BB`Wuk !1<1I3QF[FbWjPnbv[rc{gpZ}kkZq\hZaK[OK<L=7(1$hm^P;@% v|YT<9tnHF*)poNN'"roEL,%exUR(8 joH^(7 Y~ZX2Q18)   ,-@GMPcpxz +@XWiz47OTyu0*KJvk5&GAs "!M@qd :,QLsg 83F;g^{n &2"7)E2G:R?XCUIaFYNbIVG]JP?TAG6?4:%*$'qt]SDG*%mhIP-' kj?C(pvOH+(tuOQ*1 z\^2Cv]k7J'4~anYj#3  !!-9CGQWal|-3:Gfnw=>Z] 1%PN{p &UFla!@3e\x !!F5`b{m51JBbYwr(06&8"C4=&G7?*A3=(<+5!."$#tpb\CE30vs[dA;$ Z^F@juUF$1 ~~RS:? gkBP&1 g|Xc-D!.zp}WmOZ9J(;(+' #.269JQV\fn| +<;Kcto-D@fe 4*POyi%K:gex /,_Ivx 1(NEmhy!5)GDaTpi| # & &$*'  mkd^HH81 mmQO05x]Y;;mwWJ*0 [c=@!y{P^5Cd{KY2E)vczY]FZ=G/9%3$    3657EGSX[atuw!14EQ]d}(,ILkj00TGsrB3a[v."EBo]"7/RRsg2!>?^Ggck   vytSPVQ/+&+ rxib??.+  u{YO:B voX[.-onEQ**^lPY /|brO\0D,r}gsXhMVGS4>5?&2$,&'%! "%(+5&0=G4>TZHXgliw|"+=IT^lt,-SZmc/4QBqo 3*[Ptr32^Mno*8:]Klp )92N@\Ro`uyzka[[KA78! ovTN><" smUY77zz[]:7z}Q^=<y`k;G$3dwR^3M+/ zktfsV`T\HVHF:M?:0C652=.8271?29;D6DFKDRTYRcinev~ !*7>KTfk|!$45YXvn+!QLpc$*W=dh &!D7bZvE6SRqh~",A5NAaOmazjv~urma\UTC946||`_LL/-piSZ9/Y`CCboIH2 qS`9F. j}We=R/:*tyzivcpddVhZWS`NXXXE\\WL_\_Ye^fgsnsu~ '48HU\bv{ &-?;[^{t -&QAec~(99fU|t/2M<kgx !<.TRl`|2;3P;RNlShhc|vyzhkpdVVXL<6<2$ dk_Q3B-! knRQ76g`HN*+ nz[Z2@"#mNW4J!$u]nN`8A);"z}pyy{l|vxp|t}y}y!&2=AObbcs65=>nlrr -$D?l]~|/)YIed1-[Hikx51NCa[}o (#2"G7G;^JYQrYfahqistuzwyvuxr{jqmk[ca[NPNH>652)tyZVKI-3koPK17loRP16iqFM.5k|OX5J%l}]lIS6K+/* (11=OUT\pv~ !5=PIjl -F@bU{ *?:gUru "62YLjg *(F6WTk]| %5"9+H9H8SITA\O\LdW[Pi[\NbY^QVRXFMLK9@A=..-+#v|]WJR85$ bgNJ06vrU^=>y{Zd?K&+kyQ_7K$2 hyalLZ@>I[ZYerz{ '1:SF_av#J?UH{tz"E<YKwl@0NEeb|u ";/G?^Ombxq~        sxooUXKQ76&(qxVWHK()}ckSV.3#iuIW:>(uboIX8G&2  xwvduY`PZHPRZqe&!E5VMoi| 0+J>g[wr 53D;h_ok#7*C?WDc_ray|yxms\\VV@B68!&zjiSU<@*. owRU=H$" \lLX07/h{[eDT6>3zq{djcjNXZY=OLG8F=;7>2:2739/.2>1-6B31>I9=IL@HWXKVfk]dtyx!',B;PQcb~t&$E1LNwb}{'<0RKn_~v3(>;^Ulbx . ;5LAUGjbjZxw}|vveh_aQSFG6;/+moagHJ97!* ms`c>I01mchDQ/@$Ysd_4T@=5uxwhhls`d`g\cX^]]R]]^S\_aXaadahgljorxsx-/A;MQbZuu *&B/VWhU 1(=6^Qkiw &#70VI\Wzm~  1$B3B=ZFZSeXuhrc{nz}~}qvuj]kcZOWQM9=9:%( !~]h]V:G6. tzajIN3>" rsZoGR1>,lqmNdJT7?'< }~{~ ""53B?SQc\qo$%#J:QIlcs !9!FG_Pth{ 4"A@ZHi^try .5)>3F<PANKbL[]hThfh\tkkhwjloxjmktnlfhhmdZ[gaPTXTFICF@>.41.#ykkX^GL89%,x}_mPS;E$,l}]iEP7G&#~]sbgJU?N:= 2!%   #$/.>;ID\Ydb{r!4,D7\Qk`~r *9.SJ^Rvst 341QBULrctl*&(4"468)CC=1KE>>R@AKN=EJHDEAEE99DB0199'()-#"rjeUdEI?@ .|~]nV[6E,8iZbJ]6=&>hriuS[JWCH5?,2$+   "(#67A8JHZV^[wozt2!6.PB[Rndv  0$A;YH^[}m#.#@5L@[Rhb{d  " ###% }mtdiSYHN8<*2tgkH[JK'7&}ku_kE[DC ?)! zylfc\iKQIP?F1728$%+    $'4+0/BAE>RTZPieklu!6$>=UDaZrhu '*9%DBYMjawo :6<U=QOj]rb}yv{yr{cc^dML@N:3 ,!!   mzfmJWFO,51 ~ltarIXEM-?%*& ~stdqjgP[W\EIFL9>G>=AE:AEB9EGFBEGMOKJQWXXR[ek^cmwmuzv  !/%;-A8\OXNwpxg 70>7WLWTqgwp &&%>*;;T=NNbMean_yn{r~}|tzipfj_bNYUQ7IC>)4*.uxk}ZZLcCA.>(4 pfmXgLW=L6:%8"}uww|nmmtikhkdeehab]jg^Xfjd]cemgdhpkirwpp~|y{  $.":/E;TF\Tm^vnw ".*8)HFUI`[pjzm /%$C/83O>LHVJbX]Trih^|qsp~u~{wyquoymkdo_^[eNQOT@EAA/;1. +~s}ak]bHV>B1C$)$ ~w~ex\gU_DN=J19"2""  ~{z +!0#@7BJ'5*1  }i{hhUeQWBL=E48$0$'    %/'6.D6IERDa`cUvqym1#50F9LH\Ne[qk{m   #!+!)-;-.2C88;F>DEIBLLIGMSQKNQSSPMQXPKPUNPKONOGNFEGG:CDB1>=8+2+1((( uwq{_j]cJVBM<B*5#-!q~{aqcfV]GVJL8B5<,0$,$  !&.+;(9=K:KF^UaRfkycwxy %"9*<3A=YHSOmbjb|sz $!($&+&-+*.201151028904624:104923-0/1+-)+&,&$&#  }|mqfq\fPZHT=C7A&0"- ~x{n|he[oURR`BFDJ2:56)0!*     & ".!2/;,<

FDaSVPocfg|h}~u '$-3:G0EGYIWSg`neul|{    #$ !"   x|rgjgUi]\CQHO5@/;(.,  }}oqoap^`ZbKRQO;HE>1@60,0%)$'        - *$702,C9E:FBXFRO`Ug_hawmzm{ &&%5)92HAH<YX[Mccscmlx~w ww{dtkmVcX_MV?ICM,47;+"# wwxltemd`UcWSLVJLCE:E?JCQGTQ^K^[i_havquj{| ' 4(2/D6CBOCVRYRh^gapmzp{|}otudtcg\eLZUU;MIE/A46#2")"{wpsnckfj``UYYYKPMPGJ?AFD59;?51.78/%-6.##,*'%$"''%#'&&%#%&)$(,-2&351&01>120?5?8=CZEIK[RVPeZ[[vddg{isut   "!1!..<2@7GBMHSN_VXYrcdixpuq}}}|urshtgpY`^hNRL[HI9J>=,<10 ( .  }vtqnoudgff^dY[_\HU[OGRMFJLACGC>>;:?<936=B20:@39646E704G<74A?B7?AH;GED=SLGCWLQPVLb^TOqaX[tfifumwm~z}x")%1".3E18?QAFGVLURb]`]mfjkunxx}v|t}wqygnkt\aYeY\HVPXAG<F;@09.7'-$% w~wxqtpufgll]`fgXYagTN^`MP\UKUUKLQMJPOJIMLMFJMPDHMRFIJSKOJTPPIZURK]V\PXZfX\\i]hdhapiplwmwupx))&2+6187I:BBPIOGUW^Q^^h`eftlln|tvt~{ytzup}ovepilYka]NfSRNY@MIJ3I?;'=85,(- !  x{tvu{sqltnnhmiigjddahh_ZdeZ[e`Z_][[_[[Zc_WUj`UYj[__dXefeYjjjYlkocih|pnjzr{uzvz} !% ( 1(4*84>2@?I:IHRJSLWX[PcdaWnniaputivyyw}x}|x~|syvxhvmocmcj[bXfSZS[DQNU=I@I9B5@08,5(- +#"|w}ryy{suwtmvvkkuujhpukgnsikomepnmfqnmftqphvotptlws|nvvvwt}}x~ - 6$(*:+55<0FC@4ONHCSPTQWQ^[^[f^ahnbeptimvwsryyu~{|~y|o{{yfxrrirbkfnXc`hQ\V\M[LPEVIG8OBA2C7:*;1.4(&&" ~~w{svvz~t{}}u|zsy~q{v~{|y{|  !#-"*$2)5160@8<;F;CKK>MSQJTTSQ`[WYbbabd`jqg_jxpfqwot{tn{uv{{~~z~~~|}w~v}s{{}kru}mnkwioglcmbd^gVe][QdSQL\FQLK@RAA;G:?3>4;)2.2#,&)%!   %&#%&)0#*.:./3A55;E>A@HDCEWMBL_QFUcRM_eZW]fe[\hkdfkjkomilwrgm|ujrzsstywsv~tsz{px}tp~zst{vvvxqvvxnqpynllvmngphjeoacao\`]bWbW[S^OWOVITKN@REB9N@@7B9@/53>$+16')''!      %$$+#(&3./-639664DB88JF>BLFHLKJSRKLV[SMYbUR[d^X^fb^`fbfkd\jqf_knkhiiolkmmemsnajsreforjkhkjogfdofdbjcc\gbbXd\[YdSUWaORNYQUGQJOBOCIBG8G@<1H;7,<45,5(0(.!+$$(       #' !,+) 040%197*4=@38AA:@DAAJFFKHGNQHLSRPSRRTWWYWYZ]XW]_]_ZZag^X_gcc]\dfb]^ff_]cd_`cc`ZddYVgdWSb_ZP\^[OZWSKZTQJWLMJRDJFJBL=GAD7F:<6@3<0708+3*1).#.%$' #    !$!*#,/!'06)*05055865;>;8>?@CB=CHFAHKIEOQKGRRMJVTNPYSSVXSVX[TTVdZPU`]WRZ^\UYX[[YUYY[SUYYRVRURUOTOSOOITIKJPGFCPI@;OF>8I;@;=1D<4)C5/*8,1)3(.$,&%-&    "%$&$($'&.()03(-386..<=/2>A947FF87HF@=FCGIIEHINHHLNIMOLJUNIRYJHTXGLYSIOVTOOOPRRMLMWQKGPQMFKKOELIF>LOE5GJE6EA>;H96:I224C0/0>-1*3+2&+$1)(.( ,)           !#!# !")"&')%,.)'/5/+0645/38<558<<=6:AG:4CN=9BLF@AJDBEMEAHNIHBINIBKOEFOIEKJHKFFJMEFGFFI?CFJ@<AM>8<J<9;D:89D469>,776*814(2,3&-$-**+''*$%%         "$'+"*,$%++,)'.0),31/.256025;:40:>;57@C65AC:;AA=@@??EA>>EB@DC=AG@;EG>?FB>?EA<=C=>>?8=A>5=:;7:9;158=23/623*4+0,2%-./*+*(*')% %%      " " %%" ),"!*-%&-.&,1-%17,)55/.742035:823=9658:;7;;989<?81;H;08F>55?>:8>;99<678=652A:0.=342824*67/!522).$2,, +%/!'!% ,*           "$%!'$%&'%$(/""/3$$/1*---,33+'68-.70.75+3:3/95+7A1(8?3.487;0.:=0-4;3/3:..4<.-+650#152%0.,#2**$.&. ##1 , (&!           " "#!%"$##!+*"1)"&1*"(/0)$,4.$+3.*.-11-.2.)13--/3/10/.0.33/'072$,32(--,'4,+&1&*+-+.,*,(((%)$#"%#          #!(."-%,++".'(-&"'.*"&0-%()'.-$&11')-),/,''.0+$)..'(*/''(-$,(&$/%# ,&)$%+$#%&#%"""!    )"/-FBSQ  :9PQumyiZ<K*! zoGO "(wx)9ugNR} FGpxFKy*)M[\d!1;}$ku$AJceeeqw|~8@rn9;mlDMmva^ +cdOMoo# F=D=h_^^ }Y] F8qf SS' Y[ _[XW]^XQ`] ed qk .%C9VW79(#igMS  % ) aaDGrv31_\./EDTQ_]_a idS`+1fn+3YW  j t   { 1EJ`ftp(*46x40V_pg^a UV^_ Wb '21utTQB>+"!$#{Z_/d` 1'GFh_@;83  ABfc kg!{}( 47XOieu./74;5qiSU'"\V()T S   UTca"'nl7?qn,*TMrqrqpjzsl=7mq-7OX `_   .y|6;jr-4MK mt-1GI33,/hejkKD:?,1~$$86IN.)jmggb]]]Zehmzvdb+"vu 35POpk 61 yux6(IKjg$HDrogc5*9.mm SMLR{}7@]`O^>Nof17zyKBxr7@W_g_ ltPT}>BY\ iiqw#6prq{BT MQGI~|C>16#IGzzbc=: kjRU?CMPej7;!.%G>dVee,%.){xzzsv ,-I=&,1zu*-A>rh!%VRy5@jdQH,+A;bZ($TO 75YYwuBC%&T]<@*j]7<iq) MRod92Y`qi!z!*~~ $ir 77ktpo]\&% hAU+^X0*;513hb e`-)s{ LL-%iu VVKV$ID;<3;GHRRa^ys H= uj+*JDxzyylfc`fbf\h^rn0,%$.2\UMM |6A |46W_G519;?wt'@N_l{   53x~<7ML>A+<yXX:8fi>8&^l;JJC8D:<hmBBgrU`^i7>" vuVT52|^` H?B@wva\!kj%0}v?F !`eF@&,FHY\\\sn ,%NMpyNB2*fZ u{ol^TH>;3:+7*.,*3xl-6*1{w?>hj)%ll.-wd3@vA?zw/%8:42OTmo,'6<BKOVCJjj26ABBA82}%&j` => |%"k` ch tsjo`[:ACFwJ[^pJX7='*stWX?<@Edj+%\X:2qj$#meNO./ yvnhgbED)&02sjBIv7;QMle1+&=23#=/TFZQ`Wncyp{pzpJRACHKOUKM==?;zjjRRQR?A%ns=G;GLRdmahSVRNGA68'+ qrFD!'_]PL4@up+'jt   TTYY.0NPy| ?AtvPMHEjl13\[>:{ya\LI:8&#:.lm@;MR tw>3 le.)##-$(")(.1200/+0!-w~>69B]Xgiqyofbc ec6<38eZ,5x}>@w.1EO]grzjmdngqojif]bT^CC5:fd ACxxU\?@pukja\36RUuzJJ\_JCdZ|oRCXQ,nlC;#prUTA2hc48 ^]-+a]/+pb.1 5-yupi_XN>>}{TU.2z~?;\cx};7?IHHzW^0/w~\_45rv3>7:R[ OS`c %"**)1QN )MFjw0@kf2;om BH}"#ry>Hrv ==zx_^MN][u^W^Y" TI)\c6;CC[W30 ve=K~UQ%"ohB>D8qh mid_XQH:7*sq6;3.|y!jq32ff!QIgg%'+vnQN%'de<BKP|>Hwz(*WZe` ",6<IKY\catp # U\68WY}|(+``HK}IJtz)%ZV61oh XKCG?;~|c_ ^\ |}>@ed/6 uaJ:A=YZ-$~KU,xuMFomWS_Z^T |tTNB6-*pgNG!! ooWc*!on).a[!9Ffb  dg?CXY!bg VX <8n}vvZUb]$@?YUqp/(DAciE?kw =?nj#NZ}/4^\&@L_W&++rlOO=9--}-5G<tj!^f mc"FDle62[Nfk=0VW*$f_67 vjEI) ZOsogA?" wfPG&)pnFCYf }zKHrt42|u*9vd+IH!`b01MRHDw0-\d,2jiV^[\``/5X`u*%OHnitwHFz'PCjx58ml/2 "),7z{!"opfmgYtw%BEFFsn!oh d]kl$!zy37]Y)H9(*[`"piJP&qhLO,&WTlh`_.5phFG\[#"KTxMCnp6: lp=AHM ML ~;;25hm")C;r#(^[ 75ag ,RRgk:C[ifl15ge */SU 3<^_TW}%A>hl!FMnh9@OQ94=98B=>86@@VT,'}dgKA&*y\]KF)!#dmNRB@ mf0,UZ}~9>XV NT$vl6A vx67  [Y!fk'(`c#)OP7.XhVXprBD2-qh @D|TQ7= 8?jl "+>8V`ts:,ANtsDFG@JW{ 3/5=WZml&*LMcgyz<<oo# IGtn,0j^$na+/#VYC: MZ#hjIM6./-mo}zSO"xU[XK,/ rnuuB?DHYJ [fkblnqlic26cZ9Aif/7 os86Q[FFom<Bmk?Fa`{in2/~y ROKRQL8:+(tt|irrr &0IBKVnhw &(DFXXruc\*71,37em"XZZZ !sEP_UrxFC ISUT*({tUS5Dtdw~faKH}|lfYWE?03 FG{v/2868740.0y(O_|l[lU@%wx]^22tvJHbe00)#KS  /7if" DNmk !,IFRWDHorU\ eb jl ^aca \]%42,ILSTfjyt 87JJJH">6GN @=CF `]!  OTSQNGXV$$vo=:JIUN04~voc_TVB969!VKkh$msui bd LS@7719>( rkMX2) UY53  ]Z')/0Y]#(Y[yz+&FVka # efhe hk'&$+8;?ODI  0-=BKHY\fcqw,( &;<;-)&# `V^fXZUM44$ ]eLD ]fLG"( rm?G$SO6?/.DDbg$ODbt'MQ[Z}R]kg~v*;NKgdsz'%=@ $2%3@KEMR^`fhppIJus43usEEba21ih@>0..*#%<>ggXSHK3.y{ X\0-zzHMvjTc=2 (~vR\3-}[_QOT]vr;@\^  $.A9L\tju}AKql<Dhf <?aY~}CK&%""/0;@C@VZSWjhfhqt{{IO J< ^c,$X]8/}{KN:2,1A=da))TQ*U^;9us&ofyuskhni\[^Y[VJHSPB?EA<:>=5U[~IK45 Yc/-qpBI} CNsxpZfLA)3~ffBF/.ymvqq 6:OMr /;G@R[jlvvV`72el$#RTy9@hm"KLT\OJ2>JBCILNYYZ^hhekvplvxxFI UZxCJ[X45rkbjqg~&.e[$GNTN<3kn||{uvzokmtnechhdeh\^dfWUbiZlt OG!'ooD4~ EE OE<9\[ksuj_lcZ>@:> roUX:7 %  &.QM^e 37<;NSUVdeQWCBek%&WZZTMR<>zq`pj`\dikjdjxukoyzvu}}{{|quCBaf52df=9 ]dA6 ;=gcJLpk 47mf B;mo~tz{o{wpzxzzxu~zzz~x}~| QT\^%'a^$\]SI#95;DF<0:0+{z`bNL,1%+FATayo!&--37./V[+&ag)*mp12wy/1qp8>{}}}}xy{{~za^A:tqED SX2-nc?M, 2#BO}qCAgdPIy{C=edzs~w}x~|z RVKEFH70nrUMee\^C?+0)%6?XUbi|   QUqt89QN WY$&iu4+ uwnrn~smwyouzwfgOM12xZH)vrOS##vvUV<8  /);>c`9;_[31Y^54[Va`uqsnzzxny}xm}wn|{+,ZZH:r3#[k@EzuIL}~{~vyaYQZ?7*6 ('9>ON\cpq{yt=FkvGC gb)3GHif6={{tzxrelosd_ekaaa_PYA7!(_]+, niHJ$  qbKVA;,27145LK]]wz63XYAEhf0'} KDFBnl{~wbic^FLC?)-    "+*8>KNWZikrx|{|\d7/ wzHJzxHMyu<C mxEC(,swrqdkebY^XVOOLPB:,7 fc><jhEA%)~ptf^\dfbpnz:;SR{w*(LJtq 2*;>IAPN\WZXiejiyp|z*#HLsl'2f[)._a+WF| -,YYkixymidhYTKO<@5/$ #(938@MNWV\gqik}v{vxgf@H {GA#)WZ37fj69w\W7A## zhujh[ZTZJHDE5>5+)diOJ"}_jN?+6 "#<5T[zo 8/XXsv &#'=6>PJRT]^habhsklnqotojluqihkjcd_[VZTQCJC=;>55>@77BHEBKQQPY[]^^fjfainpjmjooqddhqda[c[\RUJM==&,nsRN!)Y^72 [c4,|bgLK16#  }lmZ^OOA?-6,# uwUP89ol[ZDD33! $$:=ME_hvn$"EB][{ !3+ACRLcbop~ -)DCWV{y"&NIts:?h`"HLoh/394?DIFTSQSe]W^ibabifccfh^]aaYZVUQUMHKQJFJRNLPTTV\\W\ab^bcd_hg`[leYTe`VMZSNBLC@5<0-% hpND0 qnHH&Z\85 y|\[?I4/{y\bWU;>54ypQ[H? &xqiiVTGG42.1$ !"+4)9@LHYZjj||)&HH[V}} ,*E9KUp_ov!8>NFgi~,!EIoj %MJko@BZY !"05*=>DAJPRNUX\Y^[\gf[_he]\a_^W^]WZ_XW_c[\bd^`bgcaafb`]f`\WbVRSXDIII9<47)) $ }zcc@F##S\?8|g_2=lnTR6?%mtWVGI.1! sp_aFB0/ vsij`[OOJH><<;96:89:@>ADPMSO_elerz+,GD`Wkt/(<;XUif}!%?9XVll!%MB_j 31NV{q)%RTda !#%$64;<EFNFQY\SY`a_bbbchieadlfeiifkjihmiljhgpf^clZ]_^OYTKFQ>::B)%',io\[9;$jmMM*,  daEK#! z{[_EB&/ ~[eVP06'' vp]dOG0<*usopgdda^`aZ\cbYdiicprsq +*H@TZmd1%=C^Sqr +7A`Vqo "!GEb]!7>\Ux{7;UMek~  ) )1A7?FMDU[TSgd_dnlignxrhsvpvvtpsqxukjyicinZ\^^NWMIAI77+/#%  z`^IQ71{\W<E)!~|q@L=9 dlNN)/utV^FB(,~~`^OT@<.* $$,G?NMff}u31A>jctw=>ZPty $F<V^x&*@<[`{u "'@=SQji| 01<;DBLPXS^baanlhktqpwwrp{{rnywsiklqea\gZVKVIF8C81.%~q}^ZFJ12jlPO28kjPP29jjIN03kr[Z<=)* rwifLJAE0( 1*59QL]\sq )6Dmey~  (&AE]Z{z $ ::PQb_~ *!*6C4;FRMMK[`ZUaf`ajjeighilec`gedT]ZXNTGI>E;=%+,.z[dVQ/8+' |vYcJ>$1tljDD)0dfFL,-km_[7B/' z}hdZ\FD66((  ")"<>IE_Xmry3#DMbSx9.MJlk-)KQja &)BDd[qy   63LJ]avn  *7+1:A=EFIMRLOWYUW^YVW_[VRZXXMPKTHC=I<4/;&% % x_p]T9B.,gmXU5:#"sx\gC;,zudkAD.. tvV]HC&-ivbXQU:775 #*$;8GK]Qhov"'>5RQqk} @?VNst35PIhl#IHWYxv1*EJWRkn}!+1/*6;@?>@JIAGPJDONHFOHFCIBD:?:7-5,*%rsY[HK03 x~geBF56qv[T5C# z``DF'/ zvZaII.1xaj`WDK<8/*  qTox/audio/original/notification.wav000066400000000000000000001022601415623743500201360ustar00rootroot00000000000000RIFFWAVEfmt DXdata                                     % " %):   +$"(%    "  "($! (,# .@YC4HklEMp Lj:N3X)A:X[x\tqR%q%a K[< GV,z_; Z@ cwC:E9aA XhOB[q0<|wBf"Ig!V#&Xz{MbS SW`/[Lkim N *  Z TPQpN0"  . Jrn)5eSOs>Tf0mn6ydP @ h "cnf/=U cG{Sz9b)Z7+KhJ 9 %)g2p,7~Y j%-^_b?#1ޯ߿1Ss9hM|: oS >!0""#>####C#2" NQ //d{h*Vvߠ sڗ ێZ(ަ߾Y S3l G*!"#%&'x'z'& &$#v" 'g]= \@z;CN| ߳ ٴF0N.ڦcVߙ([w .u, #$&&s'''i''Q&[%#"s/ L \!}ey܄AlؗEjݳ+A:1 E?# !"$$d%%V%$#"~!/ zP q@#Zpff/<8~z~5 +hLBoJHX 6vp  Ojg_1/  5{\9  (y P[1Z|l&iW0 OTt) Z)7 LK ;O ; HdU>R~I/8 dAvpe/Zj? Q e 37WQyZ ' # ?C$dbAbxMP~c^|X  ~ Zs'mZ,># ? Qce%R޺ۢO،>Oݮ߇/AL- ~!"#$%Y&1'''S'F''&%N$B"q'+~ sOA hQݘ)a٠:Mۺߕ[ @(UA  D=1$(+ 2?_~]j;VRU\-Da+ 0U\SV"S$L%-%$$$$ %$j$2#B!(XW~ 6 %MJ}H=܉ڣ@{л΂͏:]Јѓ=V$%U  =F"]'*-/i1T34567e88B998P63/Z+H'# ; b(qB:pؗԎ t0.;ʥO2He^tD| + $Q("+?-Y/1O46n89::i:8C6>3q0B.7,)&*#c)y $YPd@g!n/QcƂŞNğVk֟ڳB9m.N Z$0(i+/369>=@C+EEEDCB\A:?6=N;(961=-(!/W KiWnqExڷSi\ɴ\N~Ͱ<ޕhas * T!0'-638#={@CFEHIJL\NMMnLI3HF}C?n;6W2,'#m y;AgT)D͗93B ‘ 5_FCUзۮ!T I"g "$%%&(@)K(&x&&%/$K"t/ N['- kUM )c&FqPX6( _m?8 $ +  3 k>5S?aר+cxm֑at߄L 98 gs!#$( +W,-.//&1m1/O.0--/,)'$!9 .#~>8a0,ݪߚJ]PyYBY  3f? 7 6 - 8vo^dg3#eswceN  $(\+-/11235h6555432.*N'#i!/!a B:;W٪8l:ֺFxaZӳ׸ίҹ-bQz`Q n #F(.47R' 2 j b^!+#C%''(l('&&f&&%$<"e8v`Y A@@v[ tm6%##Lm> $ , - #eBQ O<{i݋܎ۦ2ܒP  y" &(,036q9;<=_>?\@@@?>OU4/25G8Aɔ̈Ԓ!o # 3 M\!&k*T.13556g89:;;!:Z87?5531.+'$ 6U eQGk+Sޣ݂ݴܰV}iB Q +$ @ 8jqW^ " x=;؍6ɑnuCj/Ġ5ٴާqy q `&+18%=AFKOXTWY"\]T_S`_^]y[XjVRNTJE>82,& nAʚ9񬉧k(ԏҒ]ꥦ ĢNOO mA'/683,k%[/swpںӲh1d򷙴H)­]!Wڰi޸XeUg̰c,Lo5` :#'{*-/1`3463767\8I8287764231/-V,)&#X!3|; 4 0 kz}kdl^ܽہ'׊FرEٓ )F3 kM N ~V^] {"S$0&t'~()*O+q++p+**-)'F&$B#4!   [ 9r9=VݘڇԶ _byˌ6̬͖ϟ:ցA*=y  XN<$(-<14=79HB@ACtDD_DCiB%A?->;\84Z1)-($( g] @8C\^hϼʝŽ仉ڳc5Ѱ۰a 0]H1˱ϭ߽ @c"i'\,t1f6;?C#G8JLNSPQ^RRR>R#QxOMTK}HDcA=}94/*d%+ d ARi"kpF߾ɺ_SdjBLEzї޵L=e M uqF #%(+-B./w//O00000.L-+.*(&$" fM # ]ne?p"NN4rEea OV{1 Qxn@';.2EIYxvq `  L ) EY/Ju   U ]!f=@.,Eu0k fL5n~'i[+|xNJT3tR!~T B O /gt`6 yC]&K9DD 3 _B/Fh`VD PkX}SCIφ:F?Y-jٻ;Rw(F6x  v'!$)y,]/i2f58:<>?8@ AvAA@M@e?=#; 840,) %  L:ߡgO)Kgė8á Ĵ 4Ω)/֛iue  "$.'Q)*,.k/0~00m00r/.-{,*?)&'$"[ 6`a M**WM<ܰ8ڭڌ*ܘX3X[j<(<ݹfo4EfPm  D ;tn!#e$u%]&m'n(((-('';'t&(%#!l([ C |-:+LܒWA6BѢXҖ-ؖe9,)mJi )2: "%1(*,1._/0H112E21=1?0.-(+Q)A'$!Y< 9kD؟֛҄*s'ѶҤdIN[Qt fY "$!'(`*+++A,,+<+|*U)'&$!^ mtpf3qer'8reU8Ib-$ # \+@H&I62 OX`[s;3E U5dIs  q B ^@f < 0 !0]!0! ! ^ UB]5 #  U   \p3kT9׈'#cPZܱU58]H ( /B$ "%(~*,p. 01234.5W5+54H4q3h2 1/-+?)&#  1b ozV=' ]Xӆ$h MĤ\6DǰHĞږB%!d!fH |K"%')+.0'34^67899:99%87531w/,*'#F &khq k }{Ct(1~%S"jϽп Ӭԕ֗1o; P d l %j&l !"B#####)#"!!L]= f 8m<5K dV((h8Ep SV a q y U +.?uA u l h,]#uy]]q_"e*Mw8U!0=6Xr uDme=J;dIL k OnnYe1Eh08o12"d F~zNYca< E  N~/->e` &\c>( +  o#H%sjgf߮%޴cfqݜޜߟ=E!&QT<7d)X ')/buV ]!!!o! J |?c E A(Q!.hvy~G;}P%Q2Z)y ]  >|i1;*d&Q\a  \ 8^Bw$}SCLRz gE;h  =s}d6dp\Rr  hy Nu-x(xkz8&v5 y ( w=E,wX,KW  [ w&NcH35OCaqgq'+8m o#[ "p5C  N 1 +AD g|qB)"   S an`{&5H?v}HfHxX @/:}f\0 M fMMoOAR x & <fA 2p>#p|U' a$  w 6  rMuxS* ZAySP-7YY @x*m[ & # _8Ml P _cWW"@lJ'!&p4 ocja<0LLJ` e #Ib<8  9n I  }6F]G H` xmZXPs6yDF3Lv@\SIC  o kI%JwJ]i 9  Q Is FR=zm]; "-4gM.&Pa_iCjR}s DCNy/8e4!$O!kR3Bhz8nEzj3c2{m[;o),ve tc26l7#!RxY5c( cn _#x p b==`eB]*O N ~?l^z. e8j'^3,k l\&]jB  Q  T  K  J`#@J\nin-T(`}(A6n6+S/(C(TH QaL=  k $ | JTON' Q  ! ! ` (N@8c R\*ejFE.l/t7VsD%zaVge' )& E  ;>f;lv@" H[9c A oP?]|LY)  9z"j4Zikq"Y+4 M ; d;l< Wwo:z I "K7Ltf |wm1lO8]A~~NQ XVf7. kp  % * Ak_z=~cB  V | +Q^bOsP"X wEAeYuHG00*G*|6h7kI8( B R :];Juj$sAk u Q *yY /um'C \*6mwmZz X)+R$o  J ZdaA[_%9]K)n Kn )1fYXuhnSGhjM481!4IaY/( k(0c L m t+R"r[j$3R f V #SbDqV| SP;E.mN(Y8; Hgr!X;]IBZKj  ( * @ G eo"e@ 3QU3_] R8u (:9i\CS 9J/lIgb @ C A*>: 8!!""O#K#O#""!A!J n%D/u d Ha@8v۩|ـخ֨UײظܓG+Mnf,S\+ b EwF) M O 0  pG s @ w .s+[E)$23V~g*zHEQU%Us3jcc~!4JG* _ .\bL d"$s%&'()5*y***9*)1);(K'&$#,!z @nQ++mL6ӳy{ϥ̀kͦͥpтF:A5lMPq.r<R_ ~ v8| ["#e%&'Z((=)))) )('&%$*#!& }w [ _eP^l( Dn6{up7[Ef<_7.WRIaMl  T  h>Uh !"=##6$|$u$Y$$#;#{"!r P  N9h-\ޫ<ס֑@ԿoC|ӨԈՙ6ܹ޷jBqM  P P 2 !"D#####d#"F"d!p b\CO# / PY9Q* tdI (:m5{dRox14^mAS \  x=e%H  |!"l""""h"!J! t/p$VA!c A 2} { ޶܎ۊڭ؋oqض'ٮuKr܎Y' <1|l r6m:RBGu,->G7" ~)` lBFU\ YLHL&q2aE m&VgG:"FZ!*m90  Of!9#$`&'()*$++++5+**%)(&Q%#!qJ - #p0IjN\گ*սJӧv[ҍxD.XֿYڽN(Sf\  "L %/%-AUU ~ R E : A#H>#o9d."N?l\Od{'ߜ%UkPY >!#&(1*,-2/f012)3N3Y332?2v1Q0.c-+)m'$P"u>Q J KdU-)/ۀ%lOˬYBʈʥ˧̥ϞдcۨleWbKsPCB *j 7"S#U$,%%d&&&&&$&%$##" C{I4!i 4 `XXDgP|kQWRs*9oy`Wb:\64nKM +Mkf}|Nk '"l#$%T& 'l''''I'&&E%K$ #!# E=B4 S z9spTHֈebY4۲E VDqKS / ; m`5[],c#+ X dPGV:kvs De^K&<<'Shr9lNI]c&{7>k~V O 6@<3.[6\3b  p X&L5}0e]!sVG;ZYly2x%Jxe  0  =6n$  i " {  V T#%?H  HA L35@#"T8w )eN7^ +  o 9  s C "  w g u Z \ P ] ` T Q T E : -  7fxD)7Q2)N~*y)swr fP"2g&rH7kT(UA 6 jTz !!"#e###V#"c"! >5k~g*  _TN+*Gf)]e e߾X > 9ZMRSel_N! 7 0;,H[~rLE(; Y w *[9aL-K4hiT`Eh@\BC}/r`G j=RQ A } `0 H9}v < { [&z`;v^B\z,8#jE`;n A  y  = B L 6 n   | / ZL:dcP:y6Pf,\` {*45>bTyr41{+Ytjw~$ ) !  z E'Vn[N I2}   l3e2cM?6N^6FmRS\3-nw^E ~5 U Z  KjV*Z'~[V0K /g0Bz@@Yg+DlwSK`StiB<3Gg"}{ \ l@' [;{ , R C  J A T (  z  $ ( v7/>CFQgv'y&*nTHASp.eq9627z+A+nHwM.~UwDO0IIX)?`zGut0B]O|#MjC.J v 4 e P 2 P x | y ] .    F~I,:_pjpNtK"zgI"Q_ w0 y p 8 7cnX1 _NDQ3i0CU ` K Uk*"bKoA'qkr}.Vg`$xANP;j,<25&HR1?.+Iy2`- -Q l-gN=EHD9"oCt=Un Cq= RC(=ObEogX2[ 7DO^a^]GB f; c,=iA~jWH;@8MKjw.Z?u?h4f@t #62D,0"Z@Y%Y(i+F)_O=$ /C^%Ny=o(K},SbupNK3! dVA5.qt\PI<3& 39_jAbz5Ba|   o5"O4f@sY;!!Ab 5^6w*9m'$& zA:aF _@#zuhi]hbv 'L_ &<`i%!$$ztU=2~b\B>' 12CXVpw!>JZh  a\>2zgYM;+ %*CNet4Bcg{q`UH;/#~mvhmjqjye}gttrz##,)(26;?IQQZNdXbjhqvy{~x~sxruag^SS?F40(}aYJDD+3 $ &5 # This script's purpose is to ease compiling qTox for users. # # NO AUTOMATED BUILDS SHOULD DEPEND ON IT. # # This script is and will be a subject to breaking changes, and at no time one # should expect it to work - it's something that you could try to use but # don't expect that it will work for sure. # # If script doesn't work, you should use instructions provided in INSTALL.md # before reporting issues like “qTox doesn't compile”. # # With that being said, reporting that this script doesn't work would be nice. # # If you are contributing code to qTox that change its dependencies / the way # it's being build, please keep in mind that changing just bootstrap.sh # *IS NOT* and will not be sufficient - you should update INSTALL.md first. set -eu -o pipefail # copy libs to given destination copy_libs() { local dest="$@" local libs=( /usr/local/lib/libsodium* /usr/local/lib/libvpx* /usr/local/lib/libopus* /usr/local/lib/libav* /usr/local/lib/libswscale* /usr/local/lib/libqrencode* /usr/local/lib/libsqlcipher* ) echo Copying libraries… for lib in "${libs[@]}" do cp -v "$lib" "$dest" done } # copy includes to given destination copy_includes() { local dest="$@" local includes=( /usr/local/include/vpx* /usr/local/include/sodium* /usr/local/include/qrencode* /usr/local/include/libav* /usr/local/include/libswscale* /usr/local/include/sqlcipher* ) echo Copying include files… for include in "${includes[@]}" do cp -v -r "$include" "$dest" done } main() { local libs_dir="libs/lib" local inc_dir="libs/include" echo Creating directories… mkdir -v -p "$libs_dir" "$inc_dir" copy_libs "$libs_dir" copy_includes "$inc_dir" echo Done. } main qTox/bootstrap.sh000077500000000000000000000151241415623743500144020ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # This script's purpose is to ease compiling qTox for users. # # NO AUTOMATED BUILDS SHOULD DEPEND ON IT. # # This script is and will be a subject to breaking changes, and at no time one # should expect it to work - it's something that you could try to use but # don't expect that it will work for sure. # # If script doesn't work, you should use instructions provided in INSTALL.md # before reporting issues like “qTox doesn't compile”. # # With that being said, reporting that this script doesn't work would be nice. # # If you are contributing code to qTox that change its dependencies / the way # it's being build, please keep in mind that changing just bootstrap.sh # *IS NOT* and will not be sufficient - you should update INSTALL.md first. set -eu -o pipefail ################ parameters ################ # directory where the script is located readonly SCRIPT_DIR=$( cd $(dirname $0); pwd -P) # directory where dependencies will be installed readonly INSTALL_DIR=libs # just for convenience readonly BASE_DIR="${SCRIPT_DIR}/${INSTALL_DIR}" # versions of libs to checkout readonly TOXCORE_VERSION="v0.2.13" readonly SQLCIPHER_VERSION="v4.3.0" # directory names of cloned repositories readonly TOXCORE_DIR="libtoxcore-$TOXCORE_VERSION" readonly SQLCIPHER_DIR="sqlcipher-$SQLCIPHER_VERSION" # default values for user given parameters INSTALL_TOX=true INSTALL_SQLCIPHER=false SYSTEM_WIDE=true KEEP_BUILD_FILES=false # if Fedora, by default install sqlcipher if which dnf &> /dev/null then INSTALL_SQLCIPHER=true fi print_help() { echo "Use this script to install/update libtoxcore" echo "" echo "usage:" echo " ${0} PARAMETERS" echo "" echo "parameters:" echo " --with-tox : install/update libtoxcore" echo " --without-tox : do not install/update libtoxcore" echo " --with-sqlcipher : install/update sqlcipher" echo " --without-sqlcipher : do not install/update sqlcipher" echo " -h|--help : displays this help" echo " -l|--local : install packages into ${INSTALL_DIR}" echo " -k|--keep : keep build files after installation/update" echo "" echo "example usages:" echo " ${0} -- install libtoxcore" } ############ print debug output ############ print_debug_output() { echo "with tox : ${INSTALL_TOX}" echo "with sqlcipher : ${INSTALL_SQLCIPHER}" echo "install system-wide : ${SYSTEM_WIDE}" echo "keep build files : ${KEEP_BUILD_FILES}" } # remove not needed dirs remove_build_dirs() { rm -rf "${BASE_DIR}/${TOXCORE_DIR}" rm -rf "${BASE_DIR}/${SQLCIPHER_DIR}" } install_toxcore() { if [[ $INSTALL_TOX = "true" ]] then git clone https://github.com/toktok/c-toxcore.git \ --branch $TOXCORE_VERSION \ --depth 1 \ "${BASE_DIR}/${TOXCORE_DIR}" pushd ${BASE_DIR}/${TOXCORE_DIR} # compile and install if [[ $SYSTEM_WIDE = "false" ]] then cmake . -DCMAKE_INSTALL_PREFIX=${BASE_DIR} make -j $(nproc) make install else cmake . make -j $(nproc) sudo make install sudo ldconfig fi popd fi } install_sqlcipher() { if [[ $INSTALL_SQLCIPHER = "true" ]] then git clone https://github.com/sqlcipher/sqlcipher.git \ "${BASE_DIR}/${SQLCIPHER_DIR}" \ --branch $SQLCIPHER_VERSION \ --depth 1 pushd "${BASE_DIR}/${SQLCIPHER_DIR}" autoreconf -if if [[ $SYSTEM_WIDE = "false" ]] then ./configure --prefix="${BASE_DIR}" \ --enable-tempstore=yes \ CFLAGS="-DSQLITE_HAS_CODEC" make -j$(nproc) make install || \ echo "" && \ echo "Sqlcipher failed to install locally." && \ echo "" && \ echo "Try without \"-l|--local\"" && \ exit 1 else ./configure \ --enable-tempstore=yes \ CFLAGS="-DSQLITE_HAS_CODEC" make -j$(nproc) sudo make install sudo ldconfig fi popd fi } main() { ########## parse input parameters ########## while [ $# -ge 1 ] do if [ ${1} = "--with-tox" ] then INSTALL_TOX=true shift elif [ ${1} = "--without-tox" ] then INSTALL_TOX=false shift elif [ ${1} = "--with-sqlcipher" ] then INSTALL_SQLCIPHER=true shift elif [ ${1} = "--without-sqlcipher" ] then INSTALL_SQLCIPHER=false shift elif [ ${1} = "-l" -o ${1} = "--local" ] then SYSTEM_WIDE=false shift elif [ ${1} = "-k" -o ${1} = "--keep" ] then KEEP_BUILD_FILES=true shift else if [ ${1} != "-h" -a ${1} != "--help" ] then echo "[ERROR] Unknown parameter \"${1}\"" echo "" print_help exit 1 fi print_help exit 0 fi done print_debug_output ############### prepare step ############### # create BASE_DIR directory if necessary mkdir -p "${BASE_DIR}" # maybe an earlier run of this script failed # thus we should remove the cloned repositories # if they exist, otherwise cloning them may fail remove_build_dirs ############### install step ############### install_toxcore install_sqlcipher ############### cleanup step ############### # remove cloned repositories if [[ $KEEP_BUILD_FILES = "false" ]] then remove_build_dirs fi } main $@ qTox/cmake/000077500000000000000000000000001415623743500131035ustar00rootroot00000000000000qTox/cmake/CheckAtomic.cmake000066400000000000000000000075351415623743500162710ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # atomic builtins are required for threading support. INCLUDE(CheckCXXSourceCompiles) INCLUDE(CheckLibraryExists) # Sometimes linking against libatomic is required for atomic ops, if # the platform doesn't support lock-free atomics. function(check_working_cxx_atomics varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; int main() { return x; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) endfunction(check_working_cxx_atomics) function(check_working_cxx_atomics64 varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") CHECK_CXX_SOURCE_COMPILES(" #include #include std::atomic x (0); int main() { uint64_t i = x.load(std::memory_order_relaxed); return 0; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) endfunction(check_working_cxx_atomics64) # Determine if the compiler has GCC-compatible command-line syntax. if(NOT DEFINED COMPILER_IS_GCC_COMPATIBLE) if(CMAKE_COMPILER_IS_GNUCXX) set(COMPILER_IS_GCC_COMPATIBLE ON) elseif( MSVC ) set(COMPILER_IS_GCC_COMPATIBLE OFF) elseif( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" ) set(COMPILER_IS_GCC_COMPATIBLE ON) elseif( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel" ) set(COMPILER_IS_GCC_COMPATIBLE ON) else() message(STATUS "Warning: Unknown compiler, assume GCC compatible") set(COMPILER_IS_GCC_COMPATIBLE ON) endif() endif() # This isn't necessary on MSVC, so avoid command-line switch annoyance # by only running on GCC-like hosts. if (COMPILER_IS_GCC_COMPATIBLE) # First check if atomics work without the library. check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC) if( HAVE_LIBATOMIC ) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) if (NOT HAVE_CXX_ATOMICS_WITH_LIB) message(FATAL_ERROR "Host compiler must support std::atomic!") endif() else() message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.") endif() endif() endif() # Check for 64 bit atomic operations. if(MSVC) set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True) else() check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB) endif() # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) if(HAVE_CXX_LIBATOMICS64) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) message(FATAL_ERROR "Host compiler must support std::atomic!") endif() else() message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.") endif() endif() qTox/cmake/Dependencies.cmake000066400000000000000000000171221415623743500164760ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see ################################################################################ # # :: Dependencies # ################################################################################ # This should go into subdirectories later. find_package(PkgConfig REQUIRED) find_package(Qt5Concurrent REQUIRED) find_package(Qt5Core REQUIRED) find_package(Qt5Gui REQUIRED) find_package(Qt5LinguistTools REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5OpenGL REQUIRED) find_package(Qt5Svg REQUIRED) find_package(Qt5Test REQUIRED) find_package(Qt5Widgets REQUIRED) find_package(Qt5Xml REQUIRED) function(add_dependency) set(ALL_LIBRARIES ${ALL_LIBRARIES} ${ARGN} PARENT_SCOPE) endfunction() # Everything links to these Qt libraries. add_dependency( Qt5::Core Qt5::Gui Qt5::Network Qt5::OpenGL Qt5::Svg Qt5::Widgets Qt5::Xml) include(CMakeParseArguments) include(Qt5CorePatches) function(search_dependency pkg) set(options OPTIONAL STATIC_PACKAGE) set(oneValueArgs PACKAGE LIBRARY FRAMEWORK HEADER) set(multiValueArgs) cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # Try pkg-config first. if(NOT ${pkg}_FOUND AND arg_PACKAGE) pkg_search_module(${pkg} ${arg_PACKAGE}) endif() # Then, try OSX frameworks. if(NOT ${pkg}_FOUND AND arg_FRAMEWORK) find_library(${pkg}_LIBRARIES NAMES ${arg_FRAMEWORK} PATHS ${CMAKE_OSX_SYSROOT}/System/Library PATH_SUFFIXES Frameworks NO_DEFAULT_PATH ) if(${pkg}_LIBRARIES) set(${pkg}_FOUND TRUE) endif() endif() # Last, search for the library itself globally. if(NOT ${pkg}_FOUND AND arg_LIBRARY) find_library(${pkg}_LIBRARIES NAMES ${arg_LIBRARY}) if(arg_HEADER) find_path(${pkg}_INCLUDE_DIRS NAMES ${arg_HEADER}) endif() if(${pkg}_LIBRARIES AND (${pkg}_INCLUDE_DIRS OR NOT arg_HEADER)) set(${pkg}_FOUND TRUE) endif() endif() if(NOT ${pkg}_FOUND) if(NOT arg_OPTIONAL) message(FATAL_ERROR "${pkg} package, library or framework not found") else() message(STATUS "${pkg} not found") endif() else() if(arg_STATIC_PACKAGE) set(maybe_static _STATIC) else() set(maybe_static "") endif() message(STATUS ${pkg} " LIBRARY_DIRS: " "${${pkg}${maybe_static}_LIBRARY_DIRS}" ) message(STATUS ${pkg} " INCLUDE_DIRS: " "${${pkg}${maybe_static}_INCLUDE_DIRS}" ) message(STATUS ${pkg} " CFLAGS_OTHER: " "${${pkg}${maybe_static}_CFLAGS_OTHER}" ) message(STATUS ${pkg} " LIBRARIES: " "${${pkg}${maybe_static}_LIBRARIES}" ) link_directories(${${pkg}${maybe_static}_LIBRARY_DIRS}) include_directories(${${pkg}${maybe_static}_INCLUDE_DIRS}) foreach(flag ${${pkg}${maybe_static}_CFLAGS_OTHER}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}" PARENT_SCOPE) endforeach() set(ALL_LIBRARIES ${ALL_LIBRARIES} ${${pkg}${maybe_static}_LIBRARIES} PARENT_SCOPE) message(STATUS "${pkg} found") endif() set(${pkg}_FOUND ${${pkg}_FOUND} PARENT_SCOPE) endfunction() search_dependency(LIBAVCODEC PACKAGE libavcodec) search_dependency(LIBAVDEVICE PACKAGE libavdevice) search_dependency(LIBAVFORMAT PACKAGE libavformat) search_dependency(LIBAVUTIL PACKAGE libavutil) search_dependency(LIBEXIF PACKAGE libexif) search_dependency(LIBQRENCODE PACKAGE libqrencode) search_dependency(LIBSODIUM PACKAGE libsodium) search_dependency(LIBSWSCALE PACKAGE libswscale) search_dependency(SQLCIPHER PACKAGE sqlcipher) search_dependency(VPX PACKAGE vpx) if(${SPELL_CHECK}) find_package(KF5Sonnet) if(KF5Sonnet_FOUND) add_definitions(-DSPELL_CHECKING) add_dependency(KF5::SonnetUi) else() message(WARNING "Sonnet not found. Spell checking will be disabled.") endif() endif() # Try to find cmake toxcore libraries if(WIN32) search_dependency(TOXCORE PACKAGE toxcore OPTIONAL STATIC_PACKAGE) search_dependency(TOXAV PACKAGE toxav OPTIONAL STATIC_PACKAGE) search_dependency(TOXENCRYPTSAVE PACKAGE toxencryptsave OPTIONAL STATIC_PACKAGE) else() search_dependency(TOXCORE PACKAGE toxcore OPTIONAL) search_dependency(TOXAV PACKAGE toxav OPTIONAL) search_dependency(TOXENCRYPTSAVE PACKAGE toxencryptsave OPTIONAL) endif() # If not found, use automake toxcore libraries # We only check for TOXCORE, because the other two are gone in 0.2.0. if (NOT TOXCORE_FOUND) search_dependency(TOXCORE PACKAGE libtoxcore) search_dependency(TOXAV PACKAGE libtoxav) endif() search_dependency(OPENAL PACKAGE openal) if (PLATFORM_EXTENSIONS AND UNIX AND NOT APPLE) # Automatic auto-away support. (X11 also using for capslock detection) search_dependency(X11 PACKAGE x11 OPTIONAL) search_dependency(XSS PACKAGE xscrnsaver OPTIONAL) endif() if(APPLE) search_dependency(AVFOUNDATION FRAMEWORK AVFoundation) search_dependency(COREMEDIA FRAMEWORK CoreMedia ) search_dependency(COREGRAPHICS FRAMEWORK CoreGraphics) search_dependency(FOUNDATION FRAMEWORK Foundation OPTIONAL) search_dependency(IOKIT FRAMEWORK IOKit OPTIONAL) endif() if(WIN32) set(ALL_LIBRARIES ${ALL_LIBRARIES} strmiids) # Qt doesn't provide openssl on windows search_dependency(OPENSSL PACKAGE openssl) endif() if (NOT GIT_DESCRIBE) execute_process( COMMAND git describe --tags WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GIT_DESCRIBE ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE ) if(NOT GIT_DESCRIBE) set(GIT_DESCRIBE "Nightly") endif() endif() add_definitions( -DGIT_DESCRIBE="${GIT_DESCRIBE}" ) if (NOT GIT_VERSION) execute_process( COMMAND git rev-parse HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GIT_VERSION ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE ) if(NOT GIT_VERSION) set(GIT_VERSION "build without git") endif() endif() add_definitions( -DGIT_VERSION="${GIT_VERSION}" ) set(APPLE_EXT False) if (FOUNDATION_FOUND AND IOKIT_FOUND) set(APPLE_EXT True) endif() set(X11_EXT False) if (X11_FOUND AND XSS_FOUND) set(X11_EXT True) endif() if (PLATFORM_EXTENSIONS) if (${APPLE_EXT} OR ${X11_EXT} OR WIN32) add_definitions( -DQTOX_PLATFORM_EXT ) message(STATUS "Using platform extensions") else() message(WARNING "Not using platform extensions, dependencies not found") set(PLATFORM_EXTENSIONS OFF) endif() endif() if (${DESKTOP_NOTIFICATIONS}) # snorenotify does only provide a cmake find module find_package(LibsnoreQt5 0.7.0 REQUIRED) set(ALL_LIBRARIES ${ALL_LIBRARIES} Snore::Libsnore) endif() add_definitions( -DLOG_TO_FILE=1 ) qTox/cmake/Installation.cmake000066400000000000000000000061521415623743500165520ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see ################################################################################ # # :: Installation # ################################################################################ if(APPLE) set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/osx/info.plist") find_path(MACDEPLOYQT_PATH macdeployqt PATH_SUFFIXES bin) if(NOT MACDEPLOYQT_PATH) message(FATAL_ERROR "Could not find macdeployqt for OSX bundling. You can point MACDEPLOYQT_PATH to it's path.") endif() set(BUNDLE_PATH "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app") install(CODE " message(STATUS \"Creating app bundle\") execute_process(COMMAND ${MACDEPLOYQT_PATH}/macdeployqt ${BUNDLE_PATH} -no-strip) message(STATUS \"Updating library paths\") execute_process(COMMAND ${CMAKE_SOURCE_DIR}/osx/macfixrpath ${BUNDLE_PATH}) " COMPONENT Runtime ) install(FILES img/icons/qtox.icns DESTINATION ${BUNDLE_PATH}/Contents/Resources/) install(FILES img/icons/qtox_profile.icns DESTINATION ${BUNDLE_PATH}/Contents/Resources/) install(CODE " message(STATUS \"Creating dmg image\") execute_process(COMMAND ${CMAKE_SOURCE_DIR}/osx/createdmg ${CMAKE_SOURCE_DIR} ${BUNDLE_PATH}) " COMPONENT Runtime ) else() include( GNUInstallDirs ) # follow the xdg-desktop specification install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") install(FILES "res/io.github.qtox.qTox.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") install(FILES "io.github.qtox.qTox.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") # Install application icons according to the XDG spec set(ICON_SIZES 14 16 22 24 32 36 48 64 72 96 128 192 256 512) foreach(size ${ICON_SIZES}) set(path_from "img/icons/${size}x${size}/qtox.png") set(path_to "share/icons/hicolor/${size}x${size}/apps/") install(FILES ${path_from} DESTINATION ${path_to}) endforeach(size) # process the icon, compress if enabled set(SVG_SRC "${CMAKE_SOURCE_DIR}/img/icons/qtox.svg") if(${SVGZ_ICON}) set(SVG_GZIP "${CMAKE_BINARY_DIR}/qtox.svgz") install(CODE " execute_process(COMMAND gzip -S z INPUT_FILE ${SVG_SRC} OUTPUT_FILE ${SVG_GZIP}) " COMPONENT Runtime) set(SVG_DEST "${SVG_GZIP}") else() set(SVG_DEST "${SVG_SRC}") endif() install(FILES "${SVG_DEST}" DESTINATION "share/icons/hicolor/scalable/apps") endif() qTox/cmake/Qt5CorePatches.cmake000066400000000000000000000101631415623743500167000ustar00rootroot00000000000000#============================================================================= # Copyright 2005-2011 Kitware, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of Kitware, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================= ######################################################################### # # Patched versions of macros defined in Qt5CoreMacros.cmake # ######################################################################### # qt5_add_resources(outfiles inputfile ... ) function(QT5_ADD_RESOURCES outfiles ) set(options) set(oneValueArgs) set(multiValueArgs OPTIONS DEPENDS) cmake_parse_arguments(_RCC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(rcc_files ${_RCC_UNPARSED_ARGUMENTS}) set(rcc_options ${_RCC_OPTIONS}) foreach(it ${rcc_files}) get_filename_component(outfilename ${it} NAME_WE) get_filename_component(infile ${it} ABSOLUTE) get_filename_component(rc_path ${infile} PATH) set(outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${outfilename}.cpp) set(_RC_DEPENDS) if(EXISTS "${infile}") # parse file for dependencies # all files are absolute paths or relative to the location of the qrc file file(READ "${infile}" _RC_FILE_CONTENTS) string(REGEX MATCHALL "]*>" "" _RC_FILE "${_RC_FILE}") if(NOT IS_ABSOLUTE "${_RC_FILE}") set(_RC_FILE "${rc_path}/${_RC_FILE}") endif() set(_RC_DEPENDS ${_RC_DEPENDS} "${_RC_FILE}") endforeach() # Since this cmake macro is doing the dependency scanning for these files, # let's make a configured file and add it as a dependency so cmake is run # again when dependencies need to be recomputed. qt5_make_output_file("${infile}" "" "qrc.depends" out_depends) configure_file("${infile}" "${out_depends}") else() # The .qrc file does not exist (yet). Let's add a dependency and hope # that it will be generated later set(out_depends) endif() add_custom_command(OUTPUT ${outfile} COMMAND ${Qt5Core_RCC_EXECUTABLE} ARGS ${rcc_options} -name ${outfilename} -o ${outfile} ${infile} MAIN_DEPENDENCY ${infile} DEPENDS ${_RC_DEPENDS} "${out_depends}" ${_RCC_DEPENDS} VERBATIM) list(APPEND ${outfiles} ${outfile}) endforeach() set(${outfiles} ${${outfiles}} PARENT_SCOPE) endfunction() qTox/cmake/Testing.cmake000066400000000000000000000033131415623743500155220ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see ################################################################################ # # :: Testing # ################################################################################ enable_testing() function(auto_test subsystem module) add_executable(test_${module} test/${subsystem}/${module}_test.cpp) target_link_libraries(test_${module} ${PROJECT_NAME}_static ${CHECK_LIBRARIES} Qt5::Test) add_test( NAME test_${module} COMMAND ${TEST_CROSSCOMPILING_EMULATOR} test_${module}) endfunction() auto_test(core core) auto_test(core contactid) auto_test(core toxid) auto_test(core toxstring) auto_test(chatlog textformatter) auto_test(net bsu) auto_test(persistence paths) auto_test(persistence dbschema) auto_test(persistence offlinemsgengine) auto_test(model friendmessagedispatcher) auto_test(model groupmessagedispatcher) auto_test(model messageprocessor) auto_test(model sessionchatlog) if (UNIX) auto_test(platform posixsignalnotifier) endif() qTox/doc/000077500000000000000000000000001415623743500125705ustar00rootroot00000000000000qTox/doc/TCS_state.md000066400000000000000000000016211415623743500147430ustar00rootroot00000000000000# qTox TCS Support state All section number refer to a section in the [TCS document]. |Symbol|Description| |------|-----------| |Y| qTox implements this point| |N| qTox is missing this point, add issue number| |U| Unknown if qTox supports this point| |TCS Section | qTox State | |------------|------------| | 1.0.1 |U| | 1.0.2 |U| | 1.0.3 |U| | 1.0.4 |U| | 2.1.1 |U| | 2.1.2 |U| | 2.2.1 |U| | 2.2.2 |U| | 2.2.3 |U| | 2.2.4 |U| | 2.2.5 |U| | 2.2.6 |U| | 2.2.7 |U| | 2.2.8 |U| | 2.2.9 |U| | 2.2.10 |U| | 2.2.11 |U| | 3.1.1 |U| | 3.1.2 |U| | 3.2.1 |U| | 3.2.2 |U| | 3.2.3 |U| | 3.3.1 |U| | 3.3.2 |U| | 3.3.3 |U| | 3.4.1 |U| | 3.4.2 |U| | 3.3.1 |U| | 3.3.2 |U| | 3.3.3 |U| | 3.3.4 |U| | 3.3.5 |U| | 4.0.1 |U| | 4.0.2 |U| | 5.0.1 |U| | 5.0.2 |U| | 5.0.3 |U| | 5.0.4 |U| | 5.1.1 |U| | 5.1.2 |U| | 5.1.3 |U| [TCS document]: https://tox.gitbooks.io/tox-client-standard/content/ qTox/doc/coding_standards.md000066400000000000000000000437101415623743500164250ustar00rootroot00000000000000# Coding Standards & Guidelines This document defines the qTox coding standards and style, all code contributions are expected to adhere to the rules described below. Most stylistic features described below are described as clang-format rules present in the root of the repository, as such most code formatting rules can be applied by simply running clang-format over the source code in question. You can run [`tools/format-code.sh`] to format all C++ files tracked by git. ## Coding Standard qTox is written under **[ISO/IEC 14882:2011 (C++11)][ISO/IEC/C++11]** without GNU/GCC specific extensions (i.e. qTox should compile with `CXXFLAGS` set to `-std=c++11`, regardless of if `-std=gnu+11` is being used during compile time). Source code must be able to be compiled under multiple different compilers and operating systems including but not limited to GCC and Clang on Microsoft Windows, Apple OS X and GNU/Linux-based derivatives. In addition to the base language, the following additional restrictions are imposed: ### Compatibility qTox is linked against Qt 5, allowing the use of Qt constructs and library features. The current minimum supported Qt version is Qt 5.5, meaning that all code must compile in a Qt 5.5 environment. Any usage post-Qt 5.5 features must be optional and be disabled when compiling/running in a Qt 5.5 environment. ### No Exceptions qTox is compiled without support for [C++11 exceptions][Exceptions], meaning that any code contribution or dependency cannot throw a C++ exception at runtime or else the application will crash. For code present in the qTox repository, this is enforced by the use of the `-fno-exceptions` flag in the CMake configuration. Note: This restriction prohibits the use of external libraries that may throw unhandled exceptions to qTox code. External libraries using exceptions, but never require qTox code to handle them directly, will work fine. ### No RTTI qTox is compiled without support for [RTTI], as such code contributions using `dynamic_cast()` or `std::dynamic_pointer_cast()` may fail to compile and may be rejected on this basis. The implications of this are that the signature of all polymorphic types must be known at compile time or stored in an implementation-specific way. In essence, if a substitution from `dynamic_cast()` to `static_cast()` can be performed without affecting program correctness, the construct in question is valid. **Note: no usage of `dynamic_cast()` or `std::dynamic_pointer_cast()` is permitted, even if the code compiles**. An optimizing compiler may be silently replacing your dynamic casts with static casts if it can ensure the replacement is to the same effect. For manipulation of Qt-based objects, use `qobject_cast()` instead. ## Coding Style ### Indentation All code is to be formatted with an indentation size of 4 characters, using spaces. **Tabs are not permitted.** Scope specifiers and namespaces are not to be indented. The following example demonstrates well formatted code under the indentation rules: ```c++ namespace Foo { class Bar { public: Bar() { // Some code here switch (...) { case 0: { // Some code here } } // More code if (...) { // Conditional code } } }; } ``` ### Spacing Spaces are to be added before the opening parenthesis of all control statements. No spaces should be present preceeding or trailing in argument lists, template specification, array indexing or between any set of brackets. Spaces should additionally be present in between all binary, ternary and assignment operators, but should **not** be present in unary operators between the operator and the operand. Inline comments have to be one space away from the end of the statement unless being aligned in a group. The following example demonstrates well formatted code under the spacing rules: ```c++ void foo(int a, int b) { int x = 0; int y = 0; // Inline comments have to be at least one space away ++x; // Example unary operator int z = x + y; // Example binary operator int x = z > 2 ? 1 : 3; // Example ternary operator if (z >= 1) { // Some code here } else { // More code here } for (std::size_t i = 0; i < 56; ++i) { // For loop body } while (true) { // While loop body } } template void bar(T a) { std::vector foo{a}; std::cout << foo[0] << std::endl; } ``` ### Alignment If an argument list is to be split into multiple lines, the subsequent arguments must be aligned with the opening brace of the argument list. Alignment should also be performed on multiline binary or ternary operations. If multiple trailing inline comments are used, they should all be aligned together. The following example demonstrates well formatted code under the alignment rules: ```c++ void foo() { int a = 2; // Inline comments int b = 3; // must be aligned int c = a + // Multiline binary operator has to be aligned. b; } void bar(int a, int b, int c, int d, int e, int f) { // Function body here } ``` ### Braces and Line Breaks The line length limit is set to around 100 characters. This means that most expressions approaching 100 characters should be broken into multiline statements. Clang-format will attempt to target this limit, going over the limit slightly if there are tokens that should not be split. Comments should wrap around unless they include elements that cannot be split (e.g. URLs). Line breaks should be added before braces on enum, function, record definitions and after all template specializations except for the `extern "C"` specifier. Lambdas have special rules that need to be handled separately, see section below. Other control structures should not have line breaks before braces. Braces should be added for all control structures, even those whose bodies only contain a single line. **Note: Clang-format does not have the ability to enforce brace presence, one must manually ensure all braces are present before formatting via clang-format.** The following example demonstrates well formatted code under the braces and line break rules: ```c++ extern "C" { #include } namespace Foo { struct Bar { void foobar(); }; template void example(T veryLongArgumentName, T anotherVeryLongArgumentName, T aThirdVeryLongArgumentName, T aForthVeryLongArgumentName, T aFifthVeryLongArgumentName) { // This is a very long comment that has been broken into two lines due to it exceeding the 100 // characters per line rules. if (...) { // Single line control statements are still required to use braces. } else { // Multiline block // Multiline block } } } ``` ### Lambdas Lambdas are to follow special break rules defined by clang-format. In particular, if the lambda body contains a single statement and line length permits, the lambda is to be treated as a single expression, represented in an inlined format (i.e. no newlines). Or else, a newline is to be inserted **after** the opening bracket. The following example demonstrates well formatted code under the lambda rules: ```c++ // Empty lambda, all on same line. auto a = []() {}; // Lambda with single statement, all on same line. auto b = []() { return 0; }; // Lambda with multiple statements, line break after the opening bracket. auto swap = [](int& a, int& b) { a = a ^ b; b = a ^ b; a = a ^ b; }; // Long lambda with single statement, line break after the opening bracket. auto compareAndUpdate = [](const int expect, int& actual, int& newVal) -> int { actual = (actual == expect) ? newVal : actual; }; ``` ### Pointers Pointers, references and rvalue references should be be aligned left, combining with the type **when it is possible to do so**. What this means that in a regular pointer declaration of variable `x` pointing to a type `T` should be declared as `T* x;` where the \* glyph is placed next to the type `T` without any spaces separating them. A space should be present between pointer type and the variable name except in the special cases described below. Special cases exist when the pointer glyph and the variable needs to put in parentheses such as when declaring pointers to C-style arrays and pointers to functions. In these cases, the pointer **should be combined with the variable** and placed one space away from the pointer type, see examples below. As a reminder, usage of C-style arrays should be minimized and generally restricted to interactions with C-based APIs present in external libraries. Consider using the keyword `auto` to allow automatic type deduction by the compiler to avoid long and messy type ids. This rule should apply everywhere: function parameters, declarations, constructor initializer lists, etc, applying even if the variable name is not specified. A few examples of pointer specifications is given below: ```c++ int* bar(int* foo) { // Return type pointer binds to type and does not float in the middle. } int a = 0; int* x; // Pointer is put next to the type int& y = a; // Reference is put next to the type. int (*z)[1]; // Special case: pointer binded to 'z' due to requirement of being in paratheses. int* (*a)(int*) = &bar; // Pointer binded to 'a' due to require of being in paratheses, rest of the // type maintains pointer being next to the type. void foo(int* x, int&&); // Forward function declaration pointers and rvalue references bind to type // even if there is no name. ``` ### Unary Increment/Decrement When the use of the prefix and postfix notation for increment and decrement operators yield the same effect (typical when the return value is ignored), the prefix notation is preferred to ensure a consistent style. This applies to all uses of the increment/decrement operators, including those embedded in for-loops. A few examples of the usage of increment/decrement operators: ```c++ int a = 0; ++a; // Preferred over "a++". // Usage of ++i rather than i++. for (std::size_t i = 0; i < 5; ++i) { // for-loop code } // Allowed since ++a is not equivalent. if (a++ == 0) { // if statement body } ``` **Note: Clang-format does not have the ability to enforce consistent prefix/postfix choice, one must manually ensure the correct style is used.** ### Includes Minimize the amount of include directives used in header files if they can be placed in the source file (i.e. don't include something used in the source but not in the header declaration). This helps improve compile times and keeps the header lean. Include directives should include header files in the following order: | Order | Header Type | Description | | :---: | :--------------: | :--------------------------------------------------------------------------------------------------------------------------- | | 1 | Main | The main header corresponding to a source (e.g. a source file `foo.cpp` includes `foo.h` as it's main header). | | 2 | Local/Module | Headers in the same folder as the current file. These headers should be included directly, without specifying the full path. | | 3 | Project | Headers belonging to the qTox project. These should be specified using full header paths starting within "src/". | | 4\* | Qt | Headers for Qt objects and functions. | | 5\* | Other | Headers for any other dependencies (external libraries, tox, C/C++ STL, system headers, etc. | \* These headers should be included with angle bracket (e.g. `#include `). For better header sorting, consider additionally sorting headers in the "other" category (category 5) in the following order: Tox, external libraries, C/C++ STL and system headers for a smaller include profile (this is not mandatory). Newlines can be present between includes to indicate logical grouping, however be wary that clang-format does not sort includes properly this way, electing to sort each group individually according to the criteria defined above. The following example demonstrates the above include rules: ```c++ #include "core.h" #include "cdata.h" #include "coreav.h" #include "corefile.h" #include "cstring.h" #include "net/avatarbroadcaster.h" #include "nexus.h" #include "persistence/profile.h" #include "persistence/profilelocker.h" #include "persistence/settings.h" #include "widget/gui.h" #include #include #include #include #include extern "C" { #include } #include #include #include ``` ### Singletons Do not introduce new singleton classes. Prefer to move code in the direction of fewer singleton classes over time. Singletons complicate destruction, complicate making multiple instances of something in the future, i.e. having two Tox profiles loaded at once is difficult to implement in qTox because both Settings and Profile are singleton. Singleton's also make unit testing and reasoning more difficult by more tightly coupling classes. ## Documentation When adding new code to qTox also add doxygen style comments before the implementation. If an old function is changed, make sure the existing documentation is updated to reflect the changes or if none exists, add it. Always attempt to put the documentation at the point of implementation (i.e. put as much in the source `.cpp` files as possible and minimize clutter in `.h` files.) The documentation style mandates the use of `/**` to start a doxygen style comment, and having ` *` (space asterisk) on all lines following the starting line. Doxygen keywords like `@brief`, `@param` and `@return` should be used such that doxygen can intelligently generate the appropriate documentation. On all updates to master, doxygen comments are automatically generated for the source code, available at https://qtox.github.io/doxygen. ```C++ /*...license info...*/ #include "blabla.h" /** * @brief I can be briefly described as well! * * And here goes my longer descrption! * * @param x Description for the first parameter * @param y Description for the second paramater * @return An amazing result */ static int example(int x, int y) { // Function implementation... } /** * @class OurClass * @brief Exists for some reason...!? * * Longer description */ /** * @enum OurClass::OurEnum * @brief The brief description line. * * @var EnumValue1 * means something * * @var EnumValue2 * means something else * * Optional long description */ /** * @fn OurClass::somethingHappened(const QString &happened) * @param[in] happened tells what has happened... * @brief This signal is emitted when something has happened in the class. * * Here's an optional longer description of what the signal additionally does. */ ``` ## No translatable HTML tags Do not put HTML in UI files, or inside Qt's `tr()`. Instead, you can embed HTML directly into C++ in the following way, to make only the user-facing text translatable: ```C++ someWidget->setTooltip(QStringLiteral("") + tr("Translatable text…") + QStringLiteral("")); ``` ## Strings * Use `QStringLiteral` macro when creating new string. In this example, string is not intended to be modified or copied (like appending) into other string: ``` QApplication a(argc, argv); a.setApplicationName(QStringLiteral("qTox")); ``` * Use `QLatin1String` when specialized overload exists. Overload such as `QString::operator==(QLatin1String)` helps to avoid creating temporary QString and thus avoids malloc: ``` if (eventType == QLatin1String("uri")) handleToxURI(firstParam.toUtf8()); else if (eventType == QLatin1String("save")) handleToxSave(firstParam.toUtf8()); ``` * Use `QStringBuilder` and `QLatin1String` when joining strings (and chars) together. `QLatin1String` is literal type and knows string length at compile time (compared to `QString(const char*)` run-time cost with plain C++ string literal). Also, copying 8-bit latin string requires less memory bandwith compared to 16-bit `QStringLiteral` mentioned earlier, and copying here is unavoidable (and thus `QStringLiteral` loses it's purpose). Include `` and use `%` operator for optimized single-pass concatenation with help of expression template's lazy evaluation: ``` if (!dir.rename(logFileDir % QLatin1String("qtox.log"), logFileDir % QLatin1String("qtox.log.1"))) qCritical() << "Unable to move logs"; ``` ``` QCommandLineParser parser; parser.setApplicationDescription(QLatin1String("qTox, version: ") % QLatin1String(GIT_VERSION) % QLatin1String("\nBuilt: ") % QLatin1String(__TIME__) % QLatin1Char(' ') % QLatin1String(__DATE__)); ``` * Use `QLatin1Char` to avoid UTF-16-char handling (same as in previous example): ``` QString path = QString(__FILE__); path = path.left(path.lastIndexOf(QLatin1Char('/')) + 1); ``` * Use `QLatin1String` and `QLatin1Char` _only_ for Latin-1 strings and chars. [Latin-1][Latin-1] is ASCII-based standard character encoding, use `QStringLiteral` for Unicode instead. For more info, see: * [Using QString Effectively] * [QStringLiteral explained] * [String concatenation with QStringBuilder] [ISO/IEC/C++11]: http://www.iso.org/iso/catalogue_detail.htm?csnumber=50372 [Exceptions]: https://en.wikipedia.org/wiki/C%2B%2B#Exception_handling [RTTI]: https://en.wikipedia.org/wiki/Run-time_type_information [`tools/format-code.sh`]: /tools/format-code.sh [Using QString Effectively]: https://wiki.qt.io/Using_QString_Effectively [QStringLiteral explained]: https://woboq.com/blog/qstringliteral.html [String concatenation with QStringBuilder]: https://blog.qt.io/blog/2011/06/13/string-concatenation-with-qstringbuilder/ [Latin-1]: https://en.wikipedia.org/wiki/ISO/IEC_8859-1 qTox/doc/user_manual_en.md000066400000000000000000000454151415623743500161200ustar00rootroot00000000000000# qTox User Manual ## Index * [Profile corner](#profile-corner) * [Contact list](#contact-list) * [User Profile](#user-profile) * [Settings](#settings) * [Groupchats](#groupchats) * [Message Styling](#message-styling) * [Quotes](#quotes) * [Multi Window Mode](#multi-window-mode) * [Keyboard Shortcuts](#keyboard-shortcuts) * [Commandline Options](#commandline-options) * [Emoji Packs](#emoji-packs) ## Profile corner Located in the top left corner of the main window. * __Avatar__: picture that is shown to your contacts. Clicking on it will open [user profile] where you can change it. * __Name__: your name shown to your contacts. Clicking on it will open [user profile] where you can change it. * __Status message__: your status message shown to your contacts. Click on it to change it. * __Status orb__: colored orb button that shows your current status. Click on it to change it. ### Status Status can be one of: * `Online` – green * `Away` – yellow * `Busy` – red * `Offline` – gray, set automatically by qTox when there is no connection to the Tox network. ## Contact list Located on the left, below the [profile corner]. Can be sorted e.g. `By Activity`. `By Activity` sorting in qTox is updated whenever client receives something that is directly aimed at you, and not sent to everyone, that is: * audio/video call * file transfer * groupchat invite * message **Not** updated on: * avatar change * groupchat message * name change * [status](#status) change * status message change ### Contact menu Can be accessed by right-clicking on a contact or [circle](#circles). When right-clicking on a contact a menu appears that has the following options: * __Open chat in a new window:__ opens a new window for the chosen contact. * __Invite to group:__ offers an option to create a new groupchat and automatically invite the friend to it or to an already existing groupchat. * __Move to circle:__ offers an option to move friend to a new [circle](#circles), or to an existing one. * __Set alias:__ set alias that will be displayed instead of contact's name. * __Auto accept group invites:__ if enabled, all group chat invites from this friend are automatically accepted. * __Auto accept files from this friend:__ option to automatically save files from the selected contact in a chosen directory. * __Remove friend:__ option to remove the contact. Confirmation is needed to remove the friend. * __Show details:__ show [details](#contact-details) of a friend. #### Circles Circles allow you to group contacts together and give this circle a name. Contacts can be in one or in no circle. #### Contact details Contact details can be accessed by right-clicking on a contact and picking the `Show details` option. Some of the informations listed are: * avatar * name * status message * Public Key (PK) – note that PK is only a part of Tox ID and alone can't be used to add the contact e.g. on a different profile. Part not known by qTox is the [NoSpam](#nospam). ## User Profile To access it, click on __Avatar__/__Name__ in the [profile corner]. Your User Profile contains everything you share with other people on Tox. You can open it by clicking the picture in the top left corner. It contains the following settings: ### Public Information * __Name:__ This is your nickname which everyone who is on your contact list can see. * __Status:__ You can post a status message here, which again everyone on your contact list can see. #### Avatar Your profile picture that all your friends can see. To add or change, click on the avatar. To remove, right-click. ### Tox ID The long code in hexadecimal format is your Tox ID, share this with everyone you want to talk to. Click it to copy it to your clipboard. Your Tox ID is also shown as QR code to easily share it with friends over a smartphone. The "Save image" button saves the QR code into a image file, while the "Copy image" button copies into your clipboard. ### Register on ToxMe An integration for ToxMe service providers that allows you to create a simple alias for your Tox ID that will look like `user@example.com`. A default service provider (toxme.io) is already listed, and you can add your own. * __Username:__ This will be used as an alias for your Tox ID. * __Biography:__ Optional. If you want, you can write something here. * __Server:__ Service address where your alias will be registered. Note that by default aliases are public, but you can check the option to make a private one, but given that it would be stored on a server that you don't control, it's not actually private. At best you have a promise of privacy from the owner of the server. For 100% privacy use Tox IDs. After registration, you can give your new alias, e.g. `user@example.com` to your friends instead of the long Tox ID. ### Profile qTox allows you to use multiple Tox IDs with different profiles, each of which can have different nicknames, status messages and friends. + __Current profile:__ Shows the filename which stores your information. + __Current profile location:__ Shows the path to the profile file. + __Rename:__ Allows you to rename your profile. Your nickname and profile name don't have to be the same. + __Delete:__ Deletes your profile and the corresponding chat history. + __Export:__ Allows you to export your profile in a format compatible with other Tox clients. You can also manually back up your \*.tox files. + __Logout:__ Close your current profile and show the login window. + __Remove password:__ Removes the existing password for your profile. If the profile already has no password, you will be notified. + __Change password:__ Allows you to either change an existing password, or create a new password if your profile does not have one. ## Friends' options In the friend's window you can customize some options for this friend specifically. * _Auto answer:_ chooses the way to autoaccept audio and video calls. * _Manual:_ All calls must be manually accepted. * _Audio:_ Only audio calls will be automatically accepted. Video calls must be manually accepted. * _Audio + video:_ All calls will be automatically accepted. ## Settings ### General * __Language:__ Changes which language the qTox interface uses. * __Autostart:__ If set, qTox will start when you login on your computer. qTox will also automatically open the profile which was active when you ticked the checkbox, but this only works if your profile isn't encrypted (has no password set). * __Light icon:__ If set, qTox will use a different icon, which is easier to see on black backgrounds. * __Show system tray icon:__ If set, qTox will show its icon in your system tray. * __Start in tray:__ On start, qTox will only show its tray icon and no window. * __Minimize to tray:__ The minimize button on the top right, will minimize qTox to its tray icon. There won't be a taskbar item. * __Close to tray__: The close button on the top right will minimize qTox to its tray icon. There won't be a taskbar item. * __Play sound:__ If checked, qTox will play a sound when you get a new message. * __Play sound while Busy__: If checked, qTox will play a sound even when your status is set to `Busy`. * __Show contacts' status changes:__ If set, qTox will show contact status changes in your chat window. * __Faux offline messaging:__ If enabled, qTox will attempt to send messages when a currently offline contact comes online again. * __Auto away after (0 to disable):__ After the specified amount of time, qTox will set your status to "Away". A setting of 0 will never change your status. * __Default directory to save files:__ Allows you to specify the default destination for incoming file transfers. * __Autoaccept files:__ If set, qTox will automatically accept file transfers and put them in the directory specified above. ### User Interface #### Chat * __Base font:__ You can set a non-default font and its size for the chat. The new font setting will be used for new messages and all messages after qTox has been restarted. * __Text Style format:__ see [Message styling](#message-styling) section. #### New message * __Open window:__ If checked, the qTox window will be opened when you receive a new message. If you use the multiple windows mode, see [Multi Window Mode](#multi-window-mode) for details. * __Focus window:__ If checked, the qTox window will additionally be focused when you receive a new message. #### Contact list * __Group chats always notify:__ If set, qTox will notify you on every new message in a groupchat. * __Place groupchats at top of friend list:__ If checked, your groupchats will be at the top of the contacts list instead of being sorted with your other contacts. * __Compact contact list:__ If set, qTox will use a contact list layout which takes up less screen space. * __Multiple windows mode:__ If enabled, the qTox user interface will be split into multiple independent windows. For details see [Multi Window Mode](#multi-window-mode). * __Open each chat in an individual window:__ If checked, a new window will be opened for every chat you open. If you manually grouped the chat into another window, the window which hosts the chat will be focused. #### Emoticons * __Use emoticons:__ If enabled, qTox will replace smileys ( e.g. `:-)` ) with corresponding graphical emoticons. * __Smiley Pack:__ Allows you to choose from different sets of shipped emoticon styles. * __Emoticon size:__ Allows you to change the size of the emoticons. #### Theme * __Style:__ Changes the appearance of qTox. * __Theme color:__ Changes the colors qTox uses. * __Timestamp format:__ Changes the format in which qTox displays message timestamps. * __Date format:__ Same as above for the date. ### Privacy * __Send typing notifications:__ If enabled, notify your chat partner when you are currently typing. * __Keep chat history:__ If enabled, qTox will save your sent and received messages. Encrypt your profile, if you want to encrypt the chat history. ***Note*** that disabling history disables `Faux offline messaging`. With disabled history qTox doesn't store messages, so it can't try to re-send them. #### NoSpam NoSpam is a feature of Tox that prevents a malicious user from spamming you with friend requests. If you get spammed, enter or generate a new NoSpam value. This will alter your Tox ID. You don't need to tell your existing contacts your new Tox ID, but you have to tell new contacts your new Tox ID. Your Tox ID can be found in your [User Profile](#user-profile). #### BlackList BlackList is a feature of qTox that locally blocks a group member's messages across all your joined groups, in case someone spams a group. You need to put a members public key into the BlackList text box one per line to activate it. Currently qTox doesn't have a method to get the public key from a group member, this will be added in the future. ### Audio/Video #### Audio Settings * __Playback device:__ Select the device qTox should use for all audio output (notifications, calls, etc). * __Volume:__ Here you can adjust the playback volume to your needs. * __Capture device:__ Select the device qTox should use for audio input in calls. * __Gain:__ Set the input volume of your microphone with this slider. When you are talking normally, the displayed volume indicator should be in the green range. #### Video Settings * __Video device:__ Select the video device qTox should use for video calls. "None" will show a dummy picture to your chat partner. "Desktop" will stream the content of your screen. * __Resolution:__ You can select from the available resolutions and frame rates here. Higher resolutions provide more quality, but if the bandwidth of your connection is low, the video may get choppy. If you set up everything correctly, you should see the preview of your video device in the box below. * __Rescan devices:__ Use this button to search for newly attached devices, e.g. you plugged in a webcam. ### Advanced #### Portable * __Make Tox portable:__ If enabled, qTox will load/save user data from the working directory, instead of ` ~/.config/tox/ `. #### Connection Settings * __Enable IPv6 (recommended):__ If enabled, qTox will use IPv4 and IPv6 protocols, whichever is available. If disabled, qTox will only use IPv4. * __Enable UDP (recommended):__ If enabled, qTox will use TCP and UDP protocols. If disabled, qTox will only use TCP, which lowers the amount of open connections and slightly decreases required bandwidth, but is also slower and puts more load on other network participants. Most users will want both options enabled, but if qTox negatively impacts your router or connection, you can try to disable them. * __Proxy type:__ If you want to use a proxy, set the type here. "None" disables the proxy. * __Address:__ If you use a proxy, enter the address here. * __Port:__ If you use a proxy, enter the port here. * __Reconnect:__ Reconnect to the Tox network, e.g. if you changed the proxy settings. --- * __Reset to default settings:__ Use this button to revert any changes you made to the qTox settings. *Note that current implentation [is buggy](https://github.com/qTox/qTox/issues/3664) and aside from settings also friend [aliases] will be removed!* ### About * __Version:__ Shows the version of qTox and the libraries it depends on. Please append this information to every bug report. * __License:__ Shows the license under which the code of qTox is available. * __Authors:__ Lists the people who developed this shiny piece of software. * __Known Issues:__ Links to our list of known issues and improvements. ## Groupchats Groupchats are a way to talk with multiple friends at the same time, like when you are standing together in a group. To create a groupchat click the groupchat icon in the bottom left corner and set a name. Now you can invite your contacts by right-clicking on the contact and selecting "Invite to group". Currently, if the last person leaves the chat, it is closed and you have to create a new one. Videochats and file transfers are currently unsupported in groupchats. ## Message Styling Similar to other messaging applications, qTox supports stylized text formatting. * For **Bold**, surround text in single or double asterisks: `*text*` or `**text**` * For **Italics**, surround text in single or double forward slashes: `/text/` or `//text//` * For **Strikethrough**, surround text in single or double tilde's: `~text~` or `~~text~~` * For **Underline**, surround text in single or double underscores: `_text_` or `__text__` * For **Code**, surround your code in in single backticks: `` `text` `` Additionally, qTox supports three modes of Markdown parsing: * `Plaintext`: No text is stylized * `Show Formatting Characters`: Stylize text while showing formatting characters (Default) * `Don't Show Formatting Characters`: Stylize text without showing formatting characters *Note that any change in Markdown preference will require a restart.* qTox also supports action messages by prefixing a message with `/me`, where `/me` is replaced with your current username. For example `/me likes cats` turns into *` * qTox User likes cats`*. ## Quotes qTox has feature to quote selected text in chat window: 1. Select the text you want to quote. 2. Right-click on the selected text and choose "Quote selected text" in the context menu. You also can use `ALT` + `q` shortcut. 3. Selected text will be automatically quoted into the message input area in a pretty formatting. ## Friend- and Groupinvites To invite a friend to a chat with you, you have to click the `+` button on the bottom left of the qTox window. The "Add a friend" Tab allows you to enter the Tox ID of your friend, or the username of a [ToxMe service] if your friend registered there. On the "Friend requests" tab you can see, friend requests you got from other Tox users. You can then choose to either accept or decline these requests. On the Groupinvites page, you can create a new groupchat and add users to it by using the context menu in your contact list. Invites from your contacts are also displayed here and you can accept and decline them. ## Multi Window Mode In this mode, qTox will separate its main window into a single contact list and one or multiple chat windows, which allows you to have multiple conversations on your screen at the same time. Additionally you can manually group chats into a window by dragging and dropping them onto each other. This mode can be activated and configured in [settings](#settings). ## Keyboard Shortcuts The following shortcuts are currently supported: | Shortcut | Action | |------------------------------|--------------------------------| | `Arrow up` | Paste last sent message | | `CTRL` + `SHIFT` + `L` | Clear chat | | `CTRL` + `q` | Quit qTox | | `CTRL` + `Page Down` | Switch to the next contact | | `CTRL` + `Page Up` | Switch to the previous contact | | `CTRL` + `TAB` | Switch to the next contact | | `CTRL` + `SHIFT` + `TAB` | Switch to the previous contact | | `CTRL` + `p` | [Push to talk](#push-to-talk) | | `ALT` + `q` | Quote selected text | | `F11` | Toggle fullscreen mode | ## Push to talk In audio group chat microphone mute state will be changed while `Ctrl` + `p` pressed and reverted on release. ## Commandline Options | Option | Action | |------------------------------------|----------------------------------------------------| | `-p` `` | Use specified unencrypted profile | | `-l` | Start with loginscreen | | `-I` `` | Sets IPv6 toggle [Default: ON] | | `-U` `` | Sets UDP toggle [Default: ON] | | `-L` `` | Sets LAN toggle [Default: ON] | | `-P` `:

:` | Applies [proxy options](#commandline-proxy-options) [Default: NONE]| ### Commandline Proxy Options Protocol: NONE, HTTP or SOCKS5
Address: Proxy address
Port: Proxy port number (0-65535)
Example input:
`qtox -P SOCKS5:192.168.0.1:2121`
`qtox -P none` ## Emoji Packs qTox provides support for custom emoji packs. To install a new emoji pack put it in `%LOCALAPPDATA%/emoticons` for Windows or `~/.local/share/emoticons` for Linux. If these directories don't exist, you have to create them. The emoji files have to be in a subfolder also containing `emoticon.xml`, see the structure of https://github.com/qTox/qTox/tree/v1.5.2/smileys for further information. [ToxMe service]: #register-on-toxme [user profile]: #user-profile [profile corner]: #profile-corner qTox/docker/000077500000000000000000000000001415623743500132725ustar00rootroot00000000000000qTox/docker/Dockerfile.debian000066400000000000000000000032511415623743500165060ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see FROM debian:stretch RUN apt-get update && \ apt-get -y --force-yes install \ automake \ autotools-dev \ build-essential \ check \ checkinstall \ cmake \ ffmpeg \ git \ libavcodec-dev \ libavdevice-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libgtk2.0-dev \ libopenal-dev \ libopus-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ libsodium-dev \ libsqlcipher-dev \ libtool \ libvpx-dev \ libxss-dev \ pkg-config \ qrencode \ qt5-default \ qttools5-dev \ qttools5-dev-tools \ yasm RUN git clone https://github.com/toktok/c-toxcore.git /toxcore WORKDIR /toxcore RUN git checkout v0.2.13 && \ cmake . && \ cmake --build . && \ make install && \ echo '/usr/local/lib/' >> /etc/ld.so.conf.d/locallib.conf && \ ldconfig COPY . /qtox WORKDIR /qtox RUN cmake . && cmake --build . qTox/docker/Dockerfile.ubuntu000066400000000000000000000032321415623743500166050ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see FROM ubuntu:16.04 RUN apt-get update && \ apt-get -y --force-yes install \ build-essential \ cmake \ git \ libavcodec-dev \ libavdevice-dev \ libavfilter-dev \ libavutil-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ libopenal-dev \ libopus-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ libsodium-dev \ libsqlcipher-dev \ libswresample-dev \ libswscale-dev \ libvpx-dev \ libxss-dev \ qrencode \ qt5-default \ qttools5-dev-tools \ qttools5-dev RUN git clone https://github.com/toktok/c-toxcore.git /toxcore WORKDIR /toxcore RUN git checkout v0.2.13 && \ cmake . && \ cmake --build . && \ make install && \ echo '/usr/local/lib/' >> /etc/ld.so.conf.d/locallib.conf && \ ldconfig COPY . /qtox WORKDIR /qtox RUN cmake . && cmake --build . qTox/docker/build-debian.sh000077500000000000000000000015251415623743500161530ustar00rootroot00000000000000#!/bin/bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see cd "$(dirname "$0")/.." docker build . -f docker/Dockerfile.debian -t qtox cd - qTox/docker/build-ubuntu.sh000077500000000000000000000015251415623743500162530ustar00rootroot00000000000000#!/bin/bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see cd "$(dirname "$0")/.." docker build . -f docker/Dockerfile.ubuntu -t qtox cd - qTox/docker/start-qtox.sh000077500000000000000000000017621415623743500157650ustar00rootroot00000000000000#!/bin/sh # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see XSOCK=/tmp/.X11-unix XAUTH=/tmp/.docker.xauth touch $XAUTH xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge - docker run -ti --rm -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e DISPLAY=$DISPLAY -e XAUTHORITY=$XAUTH qtox ./qtox qTox/doxygen.conf000066400000000000000000000007721415623743500143550ustar00rootroot00000000000000# General PROJECT_NAME = qTox EXTRACT_ALL = YES EXTRACT_PRIVATE = YES SOURCE_BROWSER = YES GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES # Input INPUT = src FILE_PATTERNS = *.h *.cpp EXTENSION_MAPPING = h=C++ RECURSIVE = YES # Output OUTPUT_DIRECTORY = doc OUTPUT_LANGUAGE = English GENERATE_LATEX = NO GENERATE_HTML = YES HTML_OUTPUT = html EXTRACT_ALL = YES HAVE_DOT = YES UML_LOOK = YES CALL_GRAPH = YES CALLER_GRAPH = YES qTox/flatpak/000077500000000000000000000000001415623743500134455ustar00rootroot00000000000000qTox/flatpak/build-flatpak.sh000077500000000000000000000026441415623743500165310ustar00rootroot00000000000000#!/usr/bin/env bash # SPDX-License-Identifier: GPL-3.0+ # # Copyright © 2018-2019 by The qTox Project Contributors # # This script should be run from the root of the repository # usage: ./flatpak/build-flatpak.sh [Debug] # # If [Debug] is set to "Debug" the container will run in interactive mode and # stay open to poke around in the filesystem. readonly DEBUG="$1" # Fail out on error set -exo pipefail if [ ! -f ./flatpak/build-flatpak.sh ]; then echo "" echo "You are attempting to run the build-flatpak.sh from a wrong directory." echo "If you wish to run this script, you'll have to have" echo "the repository root directory as the working directory." echo "" exit 1 fi mkdir -p ./output if [ "$DEBUG" == "Debug" ] then echo "Execute: /qtox/appimage/build.sh to start the build script" echo "Execute: exit to leave the container" docker run --rm --privileged -it \ -v $PWD:/qtox \ -v $PWD/output:/output \ debian:buster-slim \ /bin/bash else docker run --rm --privileged \ -v $PWD:/qtox \ -v $PWD/output:/output \ debian:buster-slim \ /bin/bash -c "/qtox/flatpak/build.sh" fi # use the version number in the name when building a tag on Travis CI if [ -n "$TRAVIS_TAG" ] then readonly OUTFILE=./output/qTox-"$TRAVIS_TAG".x86_64.flatpak mv ./output/*.flatpak "$OUTFILE" sha256sum "$OUTFILE" > "$OUTFILE".sha256 fi qTox/flatpak/build.sh000077500000000000000000000034041415623743500151040ustar00rootroot00000000000000#!/usr/bin/env bash # SPDX-License-Identifier: GPL-3.0+ # # Copyright © 2018-2019 by The qTox Project Contributors # Fail out on error set -exuo pipefail # directory paths readonly QTOX_SRC_DIR="/qtox" readonly OUTPUT_DIR="/output" readonly BUILD_DIR="/build" readonly QTOX_BUILD_DIR="$BUILD_DIR"/qtox readonly FP_BUILD_DIR="$BUILD_DIR"/flatpak readonly APT_FLAGS="-y --no-install-recommends" # flatpak manifest download location readonly MANIFEST_FILE="flatpak/io.github.qtox.qTox.json" # directory containing necessary patches readonly PATCH_DIR="flatpak/patches" # use multiple cores when building export MAKEFLAGS="-j$(nproc)" # Get packages apt-get update apt-get install $APT_FLAGS ca-certificates git elfutils wget xz-utils patch bzip2 librsvg2-2 librsvg2-common flatpak flatpak-builder # create build directory mkdir -p "$BUILD_DIR" cd "$BUILD_DIR" # copy qtox source cp -r "$QTOX_SRC_DIR" "$QTOX_BUILD_DIR" cd "$QTOX_BUILD_DIR" # create flatpak build directory mkdir -p "$FP_BUILD_DIR" cd "$FP_BUILD_DIR" # Add 'https://flathub.org' remote: flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo ## Workaround for Flathub download issues: https://github.com/flathub/flathub/issues/845 # Pre download org.kde.Sdk because it fails often for i in {1..5} do echo "Download try $i" flatpak --system install flathub -y org.kde.Sdk/x86_64/5.15 || true done ## Workaround end # Build the qTox flatpak flatpak-builder --disable-rofiles-fuse --install-deps-from=flathub --force-clean --repo=tox-repo qTox-flatpak "$QTOX_BUILD_DIR"/flatpak/io.github.qtox.qTox.json # Create a bundle for distribution flatpak build-bundle tox-repo "$OUTPUT_DIR"/qtox.flatpak io.github.qtox.qTox # Chmod since everything is root:root chmod 755 -R "$OUTPUT_DIR" qTox/flatpak/io.github.qtox.qTox.json000066400000000000000000000067241415623743500201650ustar00rootroot00000000000000{ "app-id": "io.github.qtox.qTox", "runtime": "org.kde.Platform", "sdk": "org.kde.Sdk", "runtime-version": "5.15", "command": "qtox", "rename-icon": "qtox", "finish-args": [ "--share=network", "--socket=pulseaudio", "--socket=wayland", "--socket=x11", "--share=ipc", "--talk-name=org.kde.StatusNotifierWatcher", "--filesystem=xdg-desktop", "--filesystem=xdg-documents", "--filesystem=xdg-download", "--filesystem=xdg-music", "--filesystem=xdg-pictures", "--filesystem=xdg-videos", "--filesystem=/media", "--device=all" ], "add-extensions": { "org.freedesktop.Platform.ffmpeg-full": { "directory": "lib/ffmpeg", "version": "20.08", "add-ld-path": "." } }, "cleanup-commands": [ "mkdir -p /app/lib/ffmpeg" ], "cleanup": [ "/include", "/lib/pkgconfig", "/share/man", "*.la", "*.a" ], "modules": [ { "name": "tcl", "subdir": "unix", "build-options": { "no-debuginfo": true }, "cleanup": [ '*' ], "sources": [ { "type": "archive", "url": "https://downloads.sourceforge.net/project/tcl/Tcl/8.6.10/tcl8.6.10-src.tar.gz", "sha256": "5196dbf6638e3df8d5c87b5815c8c2b758496eb6f0e41446596c9a4e638d87ed" } ] }, { "name": "sqlcipher", "cleanup": [ "/bin" ], "config-opts": [ "--enable-tempstore=yes", "--disable-tcl" ], "build-options": { "cflags": "-DSQLITE_HAS_CODEC", "ldflags": "-lcrypto" }, "sources": [ { "type": "git", "url": "https://github.com/sqlcipher/sqlcipher", "tag": "v4.4.0", "commit": "4a81bea61e1da6fec222d713852830f1fd01aed2", "disable-fsckobjects" : true } ] }, { "name": "libsodium", "sources": [ { "type": "git", "url": "https://github.com/jedisct1/libsodium", "tag": "1.0.18", "commit": "4f5e89fa84ce1d178a6765b8b46f2b6f91216677" } ] }, { "name": "c-toxcore", "buildsystem": "cmake-ninja", "config-opts": [ "-DDHT_BOOTSTRAP=OFF", "-DBOOTSTRAP_DAEMON=OFF", "-DENABLE_STATIC=OFF" ], "sources": [ { "type": "git", "url": "https://github.com/toktok/c-toxcore", "tag": "v0.2.13", "commit": "4348b96a5b482134a9cd55cb0ef9616798b4eb3c" } ] }, { "name": "qTox", "buildsystem": "cmake-ninja", "config-opts": [ "-DSVGZ_ICON=OFF", "-DSTRICT_OPTIONS=ON" ], "sources": [ { "type": "dir", "path": "/build/qtox/" } ] } ] } qTox/img/000077500000000000000000000000001415623743500125775ustar00rootroot00000000000000qTox/img/add.svg000066400000000000000000000012311415623743500140450ustar00rootroot00000000000000 qTox/img/avatar_mask.svg000066400000000000000000000067331415623743500156220ustar00rootroot00000000000000 image/svg+xml qTox/img/caps_lock.svg000066400000000000000000000067621415623743500152710ustar00rootroot00000000000000 image/svg+xml qTox/img/contact.svg000066400000000000000000000012721415623743500147550ustar00rootroot00000000000000 qTox/img/contact_dark.svg000066400000000000000000000012721415623743500157560ustar00rootroot00000000000000 qTox/img/group.svg000066400000000000000000000031461415623743500144600ustar00rootroot00000000000000 qTox/img/group_dark.svg000066400000000000000000000031461415623743500154610ustar00rootroot00000000000000 qTox/img/icons/000077500000000000000000000000001415623743500137125ustar00rootroot00000000000000qTox/img/icons/128x128/000077500000000000000000000000001415623743500146475ustar00rootroot00000000000000qTox/img/icons/128x128/qtox.png000066400000000000000000000050431415623743500163520ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYsu85tEXtSoftwarewww.inkscape.org< IDATxypMWǩT:vT$XB"ۋؚ,/K^ZZ%"ĖbB!U#CVI(ZbHͼ75$νs0}{9SQȣ6v" Z`?md?N!r X ȳMMH UiÙ\3O'QUQPP`1lBT`' P;+mAپ}p+mB BIïo2畖 ߙ!+;%K1 yo(% "` k:,wvR' cx~ުܨK K$biV-[])[#ZX+Q$|Ҙ3ZY_1 x ƯMVԡQƉuHA,g3S cQF2U p0!^OoߎZF7i.I]m`Sa÷{H8Hw,3v$CWpSdC8pyjMEm?lK`  x~ !@tK!@os6~H =k @#y LLH\BceOl@OΟ?oA{& H)@M޾rJh/@%; 88}adP j"Ak֗HK0o"_bc&Tv;(s铹OpLѺs}xIpy`אqk߿ k#['dZBR 24+~@D,*1NQ*0={z)6?e9ޱþ2vB6r.K%Ej}fk.u-svVX9f.ثLiQd( - #=\SgT&J))QG>Y9x42J`ZCVV)|¨auC7 q<cƁ%t9iEo7?C=7 .%KVݲr2ug B?dIYlCxбaNR2+m$)^j)c$j參+ p\Ia N*wH')@4AŐ2E:1t?p(WԪ`.@2p<_]$dKQw ;  \.J.@2`3 aw+ o kXܱ&;UD;r ?`FI`|7P=) #prhaj=bIߖWI8Ic$Ñb&>{=8&#t ߙ_{ t=Wv%Bpp.5w)2Rw9 innH U$e_5j&2mYZ&, q+û=j"@v [bQ ; W#@0ӧM%,b|X&y"Sa@0/^E+ YG@$ @$ @@WU;w. #$`>| o$ ,Z `d$~_k ׭ՙld iTk?8oÇ| /p6L\6 xm}Wz.=|5dD ,= ` YZ'/rwLIL-Xd6C. { `.A9ہz92 $BjdN"9 O)4E<jf wIENDB`qTox/img/icons/14x14/000077500000000000000000000000001415623743500144735ustar00rootroot00000000000000qTox/img/icons/14x14/qtox.png000066400000000000000000000007401415623743500161750ustar00rootroot00000000000000PNG  IHDR(sBITO pHYsOtEXtSoftwarewww.inkscape.org<PLTE%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%½%#%xwxvuv\Z\cbc\Z\MKMMLMPNPECE%#%%#%A@A%#%BAB'%'@>@*(*%#%%#%868757%#%323%#%202%#%(&(*(*)'))')%#%%#%=3tRNS58LQTY\_`bcgikpu}fwIDATM`ѷ,Qf _tvGerk6gϵGbw01usYVL$IAmH:GIp7kaVtokM#k4rIVC?TUUdehp1iIDATWU0(v{ bCW@CnI'1 `r \[F4_g )!?Z֞{@P\d]o! OݹfIENDB`qTox/img/icons/192x192/000077500000000000000000000000001415623743500146515ustar00rootroot00000000000000qTox/img/icons/192x192/qtox.png000066400000000000000000000077331415623743500163640ustar00rootroot00000000000000PNG  IHDRRlsBIT|d pHYs a aJ%tEXtSoftwarewww.inkscape.org<XIDATxyPUǣNۦn,YD5"Q(4j0D0QDFX("aB`c\bF 1!(fKxs~8&w~{{\^AAAAo,GJRSr»+ Z \zjSC/챟DW`A9;/s\Y-[j$`{GVmj'_~{z-Xxɬn5 O2C8r',!` vwTUU ٽM8laG^:X7١nV# nW5h$̓K_۞3IIL P,cgUU×g$u j2&4J,^eI^@]Ɗ.w'sz7$0+fElF ?&IXLfuыڌ~" {3m4D,͙itv=@)4z/`AX)ȼ!,A+2km_,aD-fԱW ENջLgbyO@E71! s  TdqgLlL,@Q"f`Y21Cu,;9l 1C80 pšx#"_$">.>lYN-! >;}>NѮMTXF&iL}c4Xf&l~A4nmݾGfo h ߿ 3\oXR&48"VI{9-˨n `nM6QҸ@Og4|*$܍˗/\I,&ۼyS yXQ鷍~Fސ8!EpڬYTQ'FB?˹*O8GS5hpJOߦMxyz]2x&Eb!4% $^Ct3;zGٽ>^vaa$j5<mN|'OLj[Ɔf`D-vk\44?I7&))9&n>/Q`;OAA F oqhih $n"cq&|z}t&+D! OpJI3n##-2&hC @ava f7=_^^>20  C`A] nx℉,:>2N xzx~uK]*0N.QR1Iܾc~?"~u c T`(07oL|}b|JR5#*Q6zw&rO<ǻ:vxcs_&3(k껋_|ną1qP8v K\bЅ>gzEtvVv:wSE MH46:vG]*o[9n =hNXB ` tJS+BRD E!NWA`DWx>v @cȮ(Q1`EQ,*"@aFD*']H̊(5% jP, aO .@,.#RKЋF@eݩD3C `5qE.-3RK8CԢ|JtO4%"pk̴ȥf4DqCD y\;dxFb 0=@=;=><>?>?%#%:8:878%#%868535%#%%#%%#%1/1%#%%#%/-/0.0/-/%#%%#%%#%+)+*(**(*)')%#%(&(%#%'%'&$&&$&&$&%#%aT_tRNS=CDHTUUVX`aceorx#J IDAT(SmV1 "BQ[)7,'w;IBjh,Wש%pN7sM>`| HÀgJ\uB_!Z )a?060o878s͇#ҽS7]i4k&p*Kc7&7K,Ligy_i$S j*Ķpb(X'QX%:"wjNyqUIENDB`qTox/img/icons/256x256/000077500000000000000000000000001415623743500146535ustar00rootroot00000000000000qTox/img/icons/256x256/qtox.png000066400000000000000000000127761415623743500163710ustar00rootroot00000000000000PNG  IHDR\rfsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<{IDATx TVeiu3if͙Y딂W.y QA"@-`94E-SRFMe +K YĨyE?`{{g\^Z|{}y/z4z1 pIP$/,X*``u`Tc7 ֜%xV^kj``:V+ XR+څAO 6~|:v@=/X.lFV#5Ep#8io5`؂J`^j;qWoWmY=c{NUN .`*V#|Gkcbm RZK]c` : =!&c4rF!z/k !7mVs1r$n\E!ս#qD{zoԝrz-wЇBÙI/"#1QO4= - |MG tq\N¢G0,ly> ͋Zx3ϛ?a˖-O_ߓ&߈{H?L$QKK*y%=zl˘yܹJJJϜ1sjxM[[ѫͱ7rߦU]dʈfλ)*!0 8,OMgHT[h1cژʼm>QPYeC* ԫT~ _8޾']W⡞g***Q!#B&<7e@ л@N8≾ KzG=Sr=j`+qqɚ5Yf 'zWF5o&2323~'y}|i>KM+5׿~露xqp)>x 9s&Y%NLҨW14ѬÓ/Z,ZhG# 5Fu Uj?>\QPлIZUf>T@+ ~IEEEkQoi#%Nr7*)=n%:/=S^^i7H:wܯRqp+,Գ)'O<Îe_u8m_~'!Bd؅>GOz~ϝ3ws$x6ՈrMIXHXH@mxUF|g_xa /(5:MNoٳgIuPѣ ?#7|P' u:xFRSR_6yPUz5|Modt@GzT@^{mDzTT=k{t}N3= `'F^3}#ڄe= `͗LuvzT.sY~fƏ?K'@U4Պ~j\ <*]vM#5y4%= wh^3rlt<*`,Q: = [c),,q6=^4^vVd^3m}/EG}tcx X VJG_Fkf+&"`iyO.aiXZ]:wMkfqu=]0y4aSᩯ!`ix?D'!ic,/^?pP9.:."VVȑ#: Ыgw 5z6(/$ KZs a3/ @J΂j6ڜAnƧ<_=@ @24~w yӀqGJGKv l+wkcl(NI@̠fO@8c cP@ ۞leticx6Ĥ3gl޴9 eeeDU'zXRJA g(fw+e o3VVӉDrlNgS @dIOӉԴyBccw "r\.ٽkȈX6 p>j3]:w-V2JӔ?x"Q)@()&,6|;4M4LI4%LȦ`a&`LJ4eph^3 $ДfD*' %;u X۞ܽk  'a}=zl#գVXͿ8RW'-5-@g˳'ՑB ˳t1:r\`yZx_<:r\`yT:W%E$@   @@ @ @ `1`C6A@)q? #ՑB ԫѣC5xq?-_C}l3}8E vs7k^bw /ǁ~pf ݞ e9a&`u{0(Wa}& #?|s^56R0Mh&,`LȠ`i&$Q0Mn&xR0Mij8$phwԝXD4aI4ag 2`7@Ό[( @nZ8$"ԋ}Χ*~^\ԋȦ^DQ\e@ Epʥn`0KlaU-ȧ5"Fne$U(O ǐɳ9+rz1A2IENDB`qTox/img/icons/32x32/000077500000000000000000000000001415623743500144735ustar00rootroot00000000000000qTox/img/icons/32x32/qtox.png000066400000000000000000000016511415623743500161770ustar00rootroot00000000000000PNG  IHDR DsBITO pHYs:tEXtSoftwarewww.inkscape.org<YPLTE%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%ů%#%%#%%#%%#%%#%%#%%#%%#%wvw%#%utu%#%rqrqpq%#%omomlmlkljijihihfhbab%#%%#%^\^[Y[XVXWUWVTVUSU%#%SQSQOQPNP%#%JIJJHJIGI%#%HFH%#%%#%@>@@>@?=??>?>=>><><;<;9;:8:%#%656%#%2021/1%#%1/10.0%#%/-/%#%%#%*(*)')(&((&('%'%#%'%'%#%&$&&$&%#%&$&%#%rtRNS "*+<@IKQRTUVWZ[[\\]^`cdfoszz|}~DIDAT8OuWW@`*V `GTDPD&rH>NN2QL4ЪXa!RUo;A96 0頪a۵ 2-}@nN ;xF|@>a+32qTioo9~ڐN63#w;ENΩ:A!y񻯪y#/XgOHkH +J;Kl<@q" `^M g`78~yō88P~[ǂE-IENDB`qTox/img/icons/36x36/000077500000000000000000000000001415623743500145035ustar00rootroot00000000000000qTox/img/icons/36x36/qtox.png000066400000000000000000000017221415623743500162060ustar00rootroot00000000000000PNG  IHDR$$hsBITO pHYs3L tEXtSoftwarewww.inkscape.org<\PLTE%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%~}|}trtrqrpopnmnkik%#%%#%fdfcac^\^_]_%#%\Z\%#%VTV%#%%#%%#%%#%OMO%#%%#%LKL%#%%#%%#%GEGEDE%#%A?A:8:979646535424%#%323313202202%#%1/11/10.00.0%#%%#%,*,%#%%#%+)+%#%+)+%#%*(*%#%%#%)'))')%#%%#%'%'&$&%#%&$&%#% ZstRNS #&().134AINRTUUW[^__aabcfghiiqxxz{||}eWiIDAT8O7BAn![1B%}b vg;sӼ:M3)JMڗRbjC1#iMfYՔbcO:Hk؄( [)eTdsg16dsHRq!&/|U?%#%%#%;9;;:;;9;:8:%#%989767868%#%656757646%#%434%#%1/1%#%0.0/-/%#%.,.%#%%#%%#%+)+%#%*(*%#%('(%#%(&(%#%(&(%#%'%'%#%'%'%#%&$&%#%&$&%#%&$&%#%ctRNS &)*-29>@TUUVWWXYY[\^_aabcdfhhijkmnpqqstuvwz{.܏IDATHǵW[QဢF#"],(*aA#ػQ(j|?Ջ)9dɾq]MY33ϙPț9!M?m 02 h&'- vgQrKP'-) >@ nce,~P jn5 v]&hhLp#<3y5 kx{ &kƌ4u@uFdœ7c~=/۟>t?g%6G}fŹ;c"c> :)%E%:HN˕4oZ~%A9HEZTXD7tcFdg9 ` ʤ5R2i34qR;U;"g ʀ;ft%qM?>;;+|UmNY&vu O2IENDB`qTox/img/icons/512x512/000077500000000000000000000000001415623743500146415ustar00rootroot00000000000000qTox/img/icons/512x512/qtox.png000066400000000000000000000305331415623743500163460ustar00rootroot00000000000000PNG  IHDRxsBIT|d pHYs^tEXtSoftwarewww.inkscape.org<0IDATx TUWNRԫЫ_WUQ&AFATP1(*"(Z81Q*Q1q(cb1h $,B$op>_w]U\ܹoݟ@0\# &}&! h ~6υd+xs<;>Sa^H o`зTM/ ?hBap]ao› ag@*}GapBmora aǁ( -Q~ƚ@CgwFfžHVn 40-p\7b+6L_҃?l7rX,u  )» Z͓b/Y/3Xuy^k S-wT^:x/i&CSIu b>_}1Z7f`5Pc{@ 7NotsA@C+\@Ä3`g0`f'@pa imO 423`%{_ql;PP \ WaRZXwG !hBs/`3-İ3K ྀYygCGna\ g*C:.Pȃ=:0O @0!^FwߥMeO#? P|5Ž px¯+@@X&4e PiG&Ş WO ~P"6[Sυ={74n[ČsfY8oquܱsk6kkkE;zlԞ{R7nb̜>sqM;ɿSk{j'xt:&<zObo/F ܓbuP?ATSSS\\<>knVöswh3p<g7{^K. 7U}}} o vzJ~ gf'cdo=v7__x1^+Q" )90re`/ m簾ȿe7k f뿿S/|[%=2,q-G^?2JCa#ѽ1'[|&&9lڦuyꔩ URRR2&_;lcow&v|Ιo CbA td~wsF0b׬Hy}8I?a߼3$ 9 5٠~|>ʀ7<л~~Zal}<5Gǧ_%_,ĝߤfY[o@@+?M[[e >l'?c[le`[|鑛笷vUOك@0_M4y:䋐w;A7a{R=8 rĉƶ3zU^E ` {go oSī -ؓ@ߨÿs`3-tMgOkFl{u>=[ۚ!^*;I `#Oy.iϰծ53ALM `hc}kjjbڷ` F\<>c<`o# ,9;ivS#3*iF}  eFP;ݿ8~E k(fK  FL2K,շuQ>}.$nao P5zP^0ᩎٙ$NWo[uvt~$zop`s@699jٱx{3Nq ?T ˗uo9U?TE>wx[boڳw9`+PVV P|Mw9`ʛoySS/1q) ] `(W[my.h,W8jٿ@-JƍFn m XU7H{gW`@ D0hKWu1pCQ8^d*nOq) |}.(_ O@kQ>2 eU[o2hMīzII$E` {&6)u11c2{&>RqSTi2CO(޿~~-Vqwp "R[4hI Pq3a]h$@K`!7a[!\K@ǠCsρ@ `PcǮep= ZU=/ϐã(gFk `jy{yp)w;O)`TGVX!m `JDf^p)MT0@/]4Sw898)+@ad];w}S8 #8P L k*m~fc%-R,n ?foTj{tZ𫇓Љ= n]]]PCK466FڷoT,@SissqX$ǁ@sҦxa|RHg`J^xXZc/(@P4y+ ZXl6Ya5ΙPX_Te1K/Xǁ@sPҦn 3Ɔ*;@آҦsZxb{4Tn 3k6Z8+*mzϟ0CkȵXRF3r(ǁ@s.(Uq \\OA u8h.MA_>8V  @<,ppgЪf)>Fkd'@e@0iS-%@뀓Fo`5Rƥ @TXfhĄ @ 1$frqva5"GHcq 4ThH!۰~TZ{p)x=} W2RA"@бޡ2R޾ t̵5ZS_ρa@ڵkWC jhhl]'@r(Ykc@.l0ՒK @(ޡz] P=@P -]p)"E"@PDn'n0Ep7 !>wūeW2МE~> qK%C͑P @ Zj<:y=ztqA r@M>5aO@p'(0,|r6–/[ý/'@ýb_?a4;s"۷]ݫ*++c~xb-קA fN^2-4$@G{gϞMd  sP3 Bc^= A$Y@4&_hy EUԏ@x{͛6Of0_EIIDGeYYYCR]NNu !IGAlM}yw~3Եs7 ;>ۻwo CS'8|rvt^`S "wp}Vog8k<UTT9;~Mt;.6a0T'>?Gċj@@;oƍii f`ʻ)x"RpovM<%RΙ-UUU]s @X{1ϳ.=>f_ i a9QGkxN|>l]=<$@) zLTïNf8[3 @S' v|&knV?{33VVw(hFMMM(Ͽb@%aHI#[s eee MMMFN vsa<λ]ڷdH̐ɣˣ'J~0yeӦN[:kƬmuwZU lk Ä`@O'B0M'\‡N]碜aEBŬ!4}aϘn G3[ }aP >0X 53>C:}M.qZ%%%c3ГyEsN'p[8H"#I)bV޽k QrWWWt 60V30uXdzꙶW^z3G PW8>jɿS)ZAȬ#)Mÿ*6/>|Zd"oE$9{=0=+++ ۀOܽ&rrpJjך_]~03y q0`«\@  sȚԛFtFh{?&fy3a퇞,RK2E%&$nw !O ,R'<ۖ៓37TSJ ][3ޓ)-~PX,ՎX|EfKsFD +g},NbQm+pK|J`@xs &br_]iii)ӷľM~v0 ytS,FR'|ދٹt9dN򟙿4NlL~ey/`|=_EyYUUUfRSSb́8 u 9s] r{y̠SÆo9p |r\Ȥgķ@@+GիC>'o.LKI[; 07/Ƨ_)˘@?K. ࿟QQ> υ-nK^9>s[W| `H_g^` .w`- ;FtQ^޼ @@}|}=~*ޡ ,[p ^m r ,j^ּ\,Yd.kXuӞHqcY "0p)r`n(E cjӲeY wߔw3lI[AˠնNJY bngj[|\V*0U+Wbjۺ릳VA9ݾz{6|/Vgu5 "0 U:Ś@f?~> 5 b0&y:,00\!wAn6kE W}xi@汽h{:UgŚ@fqC W}kEYYYUgŚ@fQ[[pYfA`0uX ?LyyyʀնHG{ǯY lf2dmSɬU`V}C0dma[Y \]jW Z{ N2ǠK잴:@fbưզ^!Y b? 'q) ]mynsM`Q]fjKX 7|S ^mhllpdm  ?Ն)&Ay#{OX zGh`[6uR"*q\SIIMN>0? `on{q_ xzx^a ٣Qu͚1+l--n`8wWX{ h hٽQЌޡ2-k Xk @sNϠ=:9|:@he`WMMMׇ1Y߯^6@ m岦@^ܖ6W`z@!ظ8oq `0K0 ƋoV,_`g -@[zL<@g<]t-Zh@@[Ǎ7.6HT5 ,9Ãβ.@ C>9:7n8յk5k@p\>/|pц19 7Y >0hܘqk0wApoLJ?@rԛF0M:-L88h[c8kjjb&O\ 'Z~n@}>,_(ozyTy@0u^:Ĩ/@ps7*e˕O:>@ԭkgNId?`o@P433V0mSH +ozc26w諬ݑ_e˯8 ֑Ew!@,,0+Eۊ&2+}}YW @nG mccvp0K0-g|4イu6n'ߊ._)۬?`"rmF8X-驤 bے~5 1o!߭@?cbr {5 ,jJjV`vF7^)-+0ÆoaCUUU8:<ڗ/~A@#ϡgϦMY g_;w~I L&~sa Q`q)<{<<>e] f}mEnA}.#ch'~Xsq@99=P)uEn'j[Z@qwĉO1$'C5!ytz1;zl!=}&9q$ Ox>S8h'>A0(X1NkZly&И*++c@0xEGEs- `0232ĝ'!  bRƤ ?H/4&ns] 8~'4"C6~5[zLUZZ`-Rñx_wF@PTXvx*@@QK0 %%%cF@P ;0МؘV@PL 94GksrhNmmmxc.s̀ A0`={5"bbv2`f@PD̿0`%@@L}/  prp7r s~e 5DP`Ya/3"r sуjhCq s#Flf%JȵΥ0Wr s3XPCKΜk[p<Zb钥sv@й 7Le `0{vIe%/k;sL"C -qDt&kk_ 5w/ZaJ(<]@ ʖ+҃A]:t)Xi`RQ 悒ڶJHՙ y  @@ @ @  @@ @@ @ @ @ @ @  @  0::k1if7ZjLEg h-SfAn,5"͌4܆g7g1if?Tqv[|'O|bc2Ҭqv ~s6ˍH Xz͠dYqfL,sC}f$͊3)*pW S}{=ol {:22LpFxp=I40n(HLPqP~,$TdLG ev;*tT*Hv<矮ذ(?,* SokOAE^ ^ ??eppphZOڤkUfڥk>tAP~7X~P~,j_[ 5U 5N 5L 5@T 5uI;ipȀPS@u^ m Ux-02Ҵ}РP3@ͬaL0pā&Ҧ@&@g pe˾lmr5xtY|L ?>$0 Нd蒗C׆3ݍC)t&! $j3~0TX^+VL*)M"ag )%K2UcP(k~ @I 3@3g+ "' /W`s{2("0n 9Ȭ7 3iN2x00Iwc+]K%Ԇؚ("_p?--eϳ ڴ[E 3yß\, |V$jlm0+qZlx%Lkluc@+m}%u0=p!/\e EC70.l 9D|^L\I }5_/P=3@m+6oW C`N @W+_l7 ndx?@Txx7<CNA0?<vbXZ˿?筰*̰k@p_ /w.#a8 ca܁Z/=RIUckPIENDB`qTox/img/icons/64x64/000077500000000000000000000000001415623743500145055ustar00rootroot00000000000000qTox/img/icons/64x64/qtox.png000066400000000000000000000030151415623743500162050ustar00rootroot00000000000000PNG  IHDR@@sBITO pHYsvv}ՂtEXtSoftwarewww.inkscape.org<1PLTE%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%~}|}{y{yxywvwxwxtst%#%trt%#%srs%#%popljljij%#%%#%fefgfg%#%ece%#%bab%#%%#%a_a_]_%#%\Z\][]%#%ZYZXWXVTVTRTUSUSRS%#%RQRSQS%#%%#%OMOONO%#%%#%JIJ%#%%#%%#%FEF%#%%#%%#%%#%%#%%#%=;=;9;;9;:8:979767868757757%#%535646535434%#%313202%#%%#%1/10.00.0/-//-/%#%%#%.,.-+-,*,,*,%#%+)+%#%*(*%#%*(*%#%)')%#%)')%#%('((&((&(%#%%#%&$&&$&%#%&$&%#%{1tRNS &'023578@DHJKLOSTUUVXZ]^bcdeefghhiiijkklmnopqrrstwxz|||7IDATXý{ QQZjF{C*vĒZhkZV)u:ܹ3ӻyo9s~ohk,9P^whTM]@K{G+2yO3ڪyx~D':TG4?P|fkJn1_?%yƝeJDx U#GFf;_&{R[ϧGx? @h)eI{V.-vPF`if/@nW KIENDB`qTox/img/icons/72x72/000077500000000000000000000000001415623743500145035ustar00rootroot00000000000000qTox/img/icons/72x72/qtox.png000066400000000000000000000033421415623743500162060ustar00rootroot00000000000000PNG  IHDRHHb3CusBITO pHYsK?tEXtSoftwarewww.inkscape.org<PLTE%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%½%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%~~}~%#%|z|{y{yxyxwx%#%tstututrtrqrqpq%#%omonmn%#%ljljij%#%hfhfdf%#%%#%bab%#%^\^%#%]\]%#%%#%YXYZYZXWX%#%%#%VTVUTU%#%TRTRQRRPR%#%%#%OMO%#%MKMLJL%#%IGIHFH%#%FDFFEF%#%DBDCBC%#%B@B%#%@>@A?A%#%%#%%#%%#%<;<;9;;:;:8:%#%%#%979%#%868%#%868757%#%%#%323%#%313%#%202202%#%%#%/-//-/.,.%#%.,.%#%%#%,*,,*,%#%+)+%#%*(*%#%*(*)')%#%(&(%#%(&(%#%'%''%'%#%&$&%#%&$&%#%&$&%#% tRNS  $%(+012567<>BCGHTTUUVWXXYYZ[[]]]_aabcdgijklmmoppqrrttvwxxyzz||}~~IDATX_@{0En :@Pp‰[qu-uVܢ(-"Z{,^\./w&m%O-NTSu!3 MYo w1j(_GqIDcBWdbQℽDy*nE61v'T1I~0Cxޗ]V|Չ|6Hg7#yQreg}Rպ'wٜJ1>LPODC4P* bFxW&(]nMC<Ҙt| ig+P2jvR d_ rja2/!!TNx5=gw&Fh~R.fj)}w$]@}7&YC)H iHH$t U#0T?Q$CTEpeYB'ǎҨVf ԢUh. bw32/V(dM^j'Z#zzHg%@f u h* @Mih2?}fW4-B uff^,RC)jNP#F܏ Ph pÜEnor\*jVwpo_ZbyIENDB`qTox/img/icons/96x96/000077500000000000000000000000001415623743500145175ustar00rootroot00000000000000qTox/img/icons/96x96/qtox.png000066400000000000000000000042061415623743500162220ustar00rootroot00000000000000PNG  IHDR``F sBITO pHYs11(RtEXtSoftwarewww.inkscape.org<PLTE%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%~}~}|}%#%|z|{y{wvwxwx%#%rqrsrs%#%%#%mlm%#%%#%hghihi%#%%#%%#%dcdecebabcac%#%`^`%#%_^__]_%#%][]%#%[Z[[Y[%#%YXYZYZ%#%XVXXWXWUW%#%VTVVUVUSU%#%RPRQPQ%#%PNP%#%MLMNLN%#%MKM%#%%#%KJK%#%KIKIGI%#%GEG%#%CACBAB%#%A?A%#%A?A%#%@>@%#%%#%><>%#%=;=%#%=;=;9;:8:989979767868757%#%656757646646535%#%424%#%%#%313323%#%%#%202%#%2021/1%#%1/1%#%0.0%#%/-/%#%/-/%#%.,.%#%-+-%#%-+-,*,,*,%#%+)++)+*(*)')%#%)')%#%('(%#%%#%'%''%'%#%&$&&$&%#%&$&%#%…-tRNS  "$%&)-.1456<=>?@EFHKLQRTUUVWXXYYYZZ[[\\]]^___``abcdeeffgiiijklmmnoopqssttvvwxy{{|}*IDATheXQn 1A.D ;E {wsw;;3- αmpq&`96[ŐY#s`Kf>Vd*JV$J0Q0~_u>@? kfZyOr' ZOC$ƣ(KM(bmFD0Qi4  {DZɶ&&#`Hqhᧈ比 DTV4Յk74AddTYApTt*5CDf:,'fa _d rVogXl: < -L6-‘ϳI*Б`O]GR)} NZ÷>}d)S/a̶:*IENDB`qTox/img/icons/qtox.icns000066400000000000000000010303061415623743500155660ustar00rootroot00000000000000icns0is32=ڕfHHfA%ji**I')0?94@-$v!#}4=I!6$&C+a%3jˑ<,bO)6<.PP*8z{:-Pc'9fh;)a#>DD>%A87?>*YlglmhlZ(8!!5яcQPa=ڕfHHfA%ji**I')0?94@-$v!#}4=I!6$&C+a%3jˑ<,bO)6<.PP*8z{:-Pc'9fh;)a#>DD>%A87?>*YlglmhlZ(8!!5яcQPa=ڕfHHfA%ji**I')0?94@-$v!#}4=I!6$&C+a%3jˑ<,bO)6<.PP*8z{:-Pc'9fh;)a#>DD>%A87?>*YlglmhlZ(8!!5яcQPas8mk WâX HJpsMP Y\ƟY] NQqtJL XģZ il32 ҁ}Бۅ2E^ĊcOGFNbk\=TN,+39;<9412Q|V)1786+((*7<=:3Z΁l5.601)1qn34=<@8;.33./&FO7?=AA8s]533..+3NO>;>>@B?nV.40-0p$,.%t.?==A=Z&)60/-/!->6:?4.>==>D3M02/0&,>9 1/<9;>>@B?nV.40-0p$,.%t.?==A=Z&)60/-/!->6:?4.>==>D3M02/0&,>9 1/<9;>>@B?nV.40-0p$,.%t.?==A=Z&)60/-/!->6:?4.>==>D3M02/0&,>9 1/<9Bux!NopO#ih32k}Fvy{~DaNնVxUy п פ[#r鶊iWLGFJUhݤi%(tC0269<==;9646GuH|'b2/7:;<:9::9:;>?=67c )}21:96447:97798689;?C<:}q O-96252')11)(288:<=@B6Qi:3930/..2&9η~80>; ̱6771/..-0!cg1@=>?CB=ꬼ;661/.--0!jڶn1?==>>@DDCJO3721.--.(Bz2'(3wI8>==>>?DASȽ|-9210-,-0 Z*3557*\->=*>>@F;{g29310/-,.,1"C624;A*Հ;=s>?AG:nǶa1610//.,/'Ha+>938<=3dM7<;==>?@B9_18200//.,/%NZ,=96::<2^R3:88>;?7kSs/610/+-/&M{'@8=;57(yP0776;>=;8=7ovB5310/2/0'N35@CC?=,Q29;><67:=D/820/J1(&)I?264A3tL,410<=8568?5-710//2(ks{ANv5=58=4d0510/02(N6857<7`JP2410/01*65 6::N푢G75201+15469;EC85212+04544694m篿N:=:/uˏx*697E1;:84*-/0110/0.+5895X-ȳ5:953110/012201 37981⩹Ȁ87<642211012312246:55܁VI0=954321232123358:.Enr25=:654434543469;30l}' Z23:<;987767879;:20Vڡ% @jA3368:;::7412>fS6#xר}_NEA@DL]{Ӡn!SҷΠ`tW|X˰]s|{)}- }Fvy{~DaNնVxUy п פ[#r鶊iWLGFJUhݤi%(tC0269<==;9646GuH|'b2/7:;<:9::9:;>?=67c )}21:96447:97798689;?C<:}q O-96252')11)(288:<=@B6Qi:3930/..2&9η~80>; ̱6771/..-0!cg1@=>?CB=ꬼ;661/.--0!jڶn1?==>>@DDCJO3721.--.(Bz2'(3wI8>==>>?DASȽ|-9210-,-0 Z*3557*\->=*>>@F;{g29310/-,.,1"C624;A*Հ;=s>?AG:nǶa1610//.,/'Ha+>938<=3dM7<;==>?@B9_18200//.,/%NZ,=96::<2^R3:88>;?7kSs/610/+-/&M{'@8=;57(yP0776;>=;8=7ovB5310/2/0'N35@CC?=,Q29;><67:=D/820/J1(&)I?264A3tL,410<=8568?5-710//2(ks{ANv5=58=4d0510/02(N6857<7`JP2410/01*65 6::N푢G75201+15469;EC85212+04544694m篿N:=:/uˏx*697E1;:84*-/0110/0.+5895X-ȳ5:953110/012201 37981⩹Ȁ87<642211012312246:55܁VI0=954321232123358:.Enr25=:654434543469;30l}' Z23:<;987767879;:20Vڡ% @jA3368:;::7412>fS6#xר}_NEA@DL]{Ӡn!SҷΠ`tW|X˰]s|{)}- }Fvy{~DaNնVxUy п פ[#r鶊iWLGFJUhݤi%(tC0269<==;9646GuH|'b2/7:;<:9::9:;>?=67c )}21:96447:97798689;?C<:}q O-96252')11)(288:<=@B6Qi:3930/..2&9η~80>; ̱6771/..-0!cg1@=>?CB=ꬼ;661/.--0!jڶn1?==>>@DDCJO3721.--.(Bz2'(3wI8>==>>?DASȽ|-9210-,-0 Z*3557*\->=*>>@F;{g29310/-,.,1"C624;A*Հ;=s>?AG:nǶa1610//.,/'Ha+>938<=3dM7<;==>?@B9_18200//.,/%NZ,=96::<2^R3:88>;?7kSs/610/+-/&M{'@8=;57(yP0776;>=;8=7ovB5310/2/0'N35@CC?=,Q29;><67:=D/820/J1(&)I?264A3tL,410<=8568?5-710//2(ks{ANv5=58=4d0510/02(N6857<7`JP2410/01*65 6::N푢G75201+15469;EC85212+04544694m篿N:=:/uˏx*697E1;:84*-/0110/0.+5895X-ȳ5:953110/012201 37981⩹Ȁ87<642211012312246:55܁VI0=954321232123358:.Enr25=:654434543469;30l}' Z23:<;987767879;:20Vڡ% @jA3368:;::7412>fS6#xר}_NEA@DL]{Ӡn!SҷΠ`tW|X˰]s|{)}- h8mk  1::1!  FܻI04-1 +0JQV^KT.5 2:5=IS$,5??I?I6?$,KT7?4= 18OXZcNV.4 1648K࿍N$5??6%it32zɯȷ  ȥiT+ 'm¹r0b⿐Pyg{2Ϸ>fxjmǀ0 {ڽxģw p*%@njIP~ i3)T  dO\}\| щd.ʪwcULEB?>>?ADJR_rٓ?$yZ~繋dJ;67?@?>?@ABEEB<7Cl&c~ juO35=@><;;=>=<=>>=<=<=??@@ACEC<8Oq1.r߆B2:@><;:9:<<;:;<<;:;==>??@@ADEA8Cx9!?2#P@2=@=;::988778;::9::9:<=>>??@ABEC9Aj=jaTa;F2>@<;:9987766558:;<<==>?@@BFF@BCDHF:YRL77@<:987654347667656899:<=?@AACCEIA=\R[1?>::8765433256565489;>?@ABFH:W-AbPw=7A<:97654332344324567653478:<=>=>>??@BBCHA?Aa^oKr1=?;:865443220121256763.+**,.378754346679:;<=>?@@ABEF9B]/=Bp/@=:9765432210//.121476-%*9L\bbZJ7)'/884269:;<=>?@@ACG8f0OiU(^2A<:875443210//.135)+S޺K)*67;<;::9::;<=>?@AAG;VmE5&T4@;986543210//.- .05))l^'4=;<==<=<;<==>?@AF>N$Q5@;976443210/.- ./ O L-=<==>?@@AABF?KIn{T5@;97643210//.-.- y,<=>??@AACCDEFJCP_b^4@:97543210/.-.,$/<<=>??@ABCDFFKCX}o\:3j o2@;97543210/.-..$/=;<=>??@ABCDEFK@f+J0A;97543211/.-0ݵ->;<=>?@ABCDFGL=}z2@<9764321/..-,-1t^3&#$$#'3Zk0>;<=>?@ABCDEGL<>K, >=<:7643210/-,-.(@U"'15663*%O@9<;;=>?@ABCEFHJADZ3[6?:86432100/--,-0 3354234;6-->;=>??@ABCEFIEVllp0@:875322110/--,,-/%Q)??51234:=>)L6<;:;<=>??@BBDEFK?P@8?<975322110/,-0?=<9239; @*->;<=>??ABBDFGK>hZW\7>:76432110//,-.*9)0<=<;4236; <<4:;<=>?@ABDEFHET3or0@;8643210/-,,-0 lh"3<<;;7234:; >0k_3=;<=>?ABBCEFJ8dE;<975332110/-,-0//0<<;;:4347; <6->;<=> ?@@AACCECCBvq1@:86432110/.,,-/% 21<<;:;723344:; >-.>;=>@ABCB@E:w d?=;9753310/.,-.,0y"21<<;::933448; =0n4<;=?A >?AE=2?:86432110/.,,-.):a$12<;:6345; <4Z9;<<=>>?>>?@>=?@D:sfpA<<96432110/,,-.)>Z&22<;:98449; <5U>;<;<<;>?>>?>>?>;<>?AD?1?:8543210/,,-.)>`%22<;:9957<; =3\=89876>?>>=>;:;=>@E9N:<97432110/-,-.)>v#23<;:9878776 9+n;4767>==>=><9:;<>?BBGI<2?;8643210/-,- .)>!33<:997=?94334 558&=4767>=>=<889;;=>@E7m4>97532110/0.,- .)>0/3<: 98;@@A?9535 7-=47678>= <<9788:;<=?C=`@<;8643210/0.- .)>i#7;:99@A?:645570O=476678>>=-<<;67789:<=>AD>V1?:7533210/00/-.)>)5<::99?@A?:65:'=4789>>=<<7677889;<=@E7Áj4>97532110/0/-.)> "?::9=A@?@><3B=58789>=-<<95667889:<=?C=]C;<8643210/0-.)>y)=;;@?D/=5877899=-<;65667788:;<>AC@P3?:7542210/030./*? +5DB@? B4f>69:;99:=<<74556%889;<>@E7y 1?97532110/010#$''&'!8 O29@CCBA?@A?TE;;8643210/02&ӭD=B/5;=<<95%6778:;<>@C@Z6>:7542210/02'[ 7D/~v/?<;65$6789;<=?D8.G1?:7532110/03"42q,?<845$6779;<=?D6s1?96532210/03"\(w-?:5$6689:;=>C9tp3>96432210/03"%-?75 66789;=>B<^\7<86433210/03"^.=5"6789;<>A?OP:=8643320//03#-95"6779;<>@ADG>>;864320//04#(85)44556779;<=@C>A?=;86443211014#&745'44556679;<=?C:=@=:8644321014#&7335'44556679:<=?D9;@<:86443214#&73454$556678:<=?D8g:@<:86543214#&734554#556778:;=?D8A:@<:8654314$ 䯐&734"556778:;=?D8A;@<:86531225$ D-*)'9&7434%556678:;=?D9d=?=;875332125$"0561 &743)4556678:;=?D:@?=;865432110125$'55347$&7443'22456678:;=?C<G>=;875432125&h(636*\&7443'22356678:<=@B@P<>;975432137&B0432:&74432$335778:<=@AF\:?;9754321237&823433&7432$334579:<>A?Oo7?<975432125%@143438&7432 3344578;<>B<^4A<:8644322125$b*736,W&74332(3344678:<>B9rѭL4B<:8654322125$%85349#&7432&3345678:;>D7u,8A=;8654321125% 253352t&742&3345679:;>C83F>>;9754425( =3553=&74323(45689:<>A>_:?<97644323326* O056.\&7432'33455689:=?=P5A<:8644328*j$/5445-,&742&3345578:;=A8pW5A=;8654323:*U#85465"v&74323*45679:;>C5{D?>;97545443325:*t#84 9" &743233445789:<>A=g8@<:865433237:+*634550&743)45678::=@;9765454332388:*K-545/I&743455689:<>B:[ h8@<:86545543322687:*7254543&7456789:=@;9765433237:*33456/&745689:;>C6 L>?<:766543357:*Њ4560&745678::B7st@@><986654787:* *)*+*)**+ &74568::<>C8|6B=;8766556887:*jghghhi&7457889:;>B8fO>A><98665587:*&74 56799::B6hnpC@?=:876898779)&745689:;:=?@87p^4C><988987:*&7456799;<<>B2{$jU<@=;9::9878:)&74456789:<>>@<;<;98:*&7456789:;=?@A1JFuz5C=>=;:987:*z#63346679:<>@4j8S<;:984BJ+345679;<=;DxW";CA?><::98:.^ h'43345679; ?3NY'6F@?>;::87757,8bhghgghihg he;'42345679: @1@{5C=<986543213/%#$%$%&&%&'%&/42345679:9:?4f7Oa6A<;9865432332233232211223234434 54455423223433434567998:?7P9Q#4cmQ9A<;9876543210//0120122334568987:>9DoZ3H:A<;:8765432100//0123200122345578877:>;>F9A=<:8765533210/0123001223345578768:>:<H8A=<:976654432100110120123445678668:?9>y/"6wO5A=<;987654433112101231233455688679:?7D#?H6(M^Cj]2@==;:9876544332121212334456787679;@4QDUl{Q$,u1>>=<:99766543212122123123344566887689=?1eTcY-79?<<;:98776543322332312334556788679:?;3SgWL2?=<<;:98876543223234423456789878:<@4D@eRw3:?<=<;:98877654434542334567899779;?;2j`JM2=>==<;:9876654454565456789:989:>>2EV]lD<4>?>=<;::99887667656567789;;99:=?58{NTU/ar84>@>=<<;::98787677876788::;;:;>>55hsBr`:so92==<;;:9898977899:;<=<==<;:99:9::;;<<=??>81?rڌh' {]x ڙZ84;BCA@>>=<==<;<<;;<=>@B@817U9j~&8gŋZ>58=ABBA@?>>=>=??AA@=739TvzH$e̠tS@768;>@ABBCCB A><:756>PmԑtE9ίu`QF@<:88779;>DN]qԚBM _0¹͏+FTsyYKeۻpy]{HxM@Ó'Z|Ku'(h!ӻ-MMQ}n|[PXIʶS! <}V+hҚQCz~K(Žr ^qa Tȥupòuɯȷ  ȥiT+ 'm¹r0b⿐Pyg{2Ϸ>fxjmǀ0 {ڽxģw p*%@njIP~ i3)T  dO\}\| щd.ʪwcULEB?>>?ADJR_rٓ?$yZ~繋dJ;67?@?>?@ABEEB<7Cl&c~ juO35=@><;;=>=<=>>=<=<=??@@ACEC<8Oq1.r߆B2:@><;:9:<<;:;<<;:;==>??@@ADEA8Cx9!?2#P@2=@=;::988778;::9::9:<=>>??@ABEC9Aj=jaTa;F2>@<;:9987766558:;<<==>?@@BFF@BCDHF:YRL77@<:987654347667656899:<=?@AACCEIA=\R[1?>::8765433256565489;>?@ABFH:W-AbPw=7A<:97654332344324567653478:<=>=>>??@BBCHA?Aa^oKr1=?;:865443220121256763.+**,.378754346679:;<=>?@@ABEF9B]/=Bp/@=:9765432210//.121476-%*9L\bbZJ7)'/884269:;<=>?@@ACG8f0OiU(^2A<:875443210//.135)+S޺K)*67;<;::9::;<=>?@AAG;VmE5&T4@;986543210//.- .05))l^'4=;<==<=<;<==>?@AF>N$Q5@;976443210/.- ./ O L-=<==>?@@AABF?KIn{T5@;97643210//.-.- y,<=>??@AACCDEFJCP_b^4@:97543210/.-.,$/<<=>??@ABCDFFKCX}o\:3j o2@;97543210/.-..$/=;<=>??@ABCDEFK@f+J0A;97543211/.-0ݵ->;<=>?@ABCDFGL=}z2@<9764321/..-,-1t^3&#$$#'3Zk0>;<=>?@ABCDEGL<>K, >=<:7643210/-,-.(@U"'15663*%O@9<;;=>?@ABCEFHJADZ3[6?:86432100/--,-0 3354234;6-->;=>??@ABCEFIEVllp0@:875322110/--,,-/%Q)??51234:=>)L6<;:;<=>??@BBDEFK?P@8?<975322110/,-0?=<9239; @*->;<=>??ABBDFGK>hZW\7>:76432110//,-.*9)0<=<;4236; <<4:;<=>?@ABDEFHET3or0@;8643210/-,,-0 lh"3<<;;7234:; >0k_3=;<=>?ABBCEFJ8dE;<975332110/-,-0//0<<;;:4347; <6->;<=> ?@@AACCECCBvq1@:86432110/.,,-/% 21<<;:;723344:; >-.>;=>@ABCB@E:w d?=;9753310/.,-.,0y"21<<;::933448; =0n4<;=?A >?AE=2?:86432110/.,,-.):a$12<;:6345; <4Z9;<<=>>?>>?@>=?@D:sfpA<<96432110/,,-.)>Z&22<;:98449; <5U>;<;<<;>?>>?>>?>;<>?AD?1?:8543210/,,-.)>`%22<;:9957<; =3\=89876>?>>=>;:;=>@E9N:<97432110/-,-.)>v#23<;:9878776 9+n;4767>==>=><9:;<>?BBGI<2?;8643210/-,- .)>!33<:997=?94334 558&=4767>=>=<889;;=>@E7m4>97532110/0.,- .)>0/3<: 98;@@A?9535 7-=47678>= <<9788:;<=?C=`@<;8643210/0.- .)>i#7;:99@A?:645570O=476678>>=-<<;67789:<=>AD>V1?:7533210/00/-.)>)5<::99?@A?:65:'=4789>>=<<7677889;<=@E7Áj4>97532110/0/-.)> "?::9=A@?@><3B=58789>=-<<95667889:<=?C=]C;<8643210/0-.)>y)=;;@?D/=5877899=-<;65667788:;<>AC@P3?:7542210/030./*? +5DB@? B4f>69:;99:=<<74556%889;<>@E7y 1?97532110/010#$''&'!8 O29@CCBA?@A?TE;;8643210/02&ӭD=B/5;=<<95%6778:;<>@C@Z6>:7542210/02'[ 7D/~v/?<;65$6789;<=?D8.G1?:7532110/03"42q,?<845$6779;<=?D6s1?96532210/03"\(w-?:5$6689:;=>C9tp3>96432210/03"%-?75 66789;=>B<^\7<86433210/03"^.=5"6789;<>A?OP:=8643320//03#-95"6779;<>@ADG>>;864320//04#(85)44556779;<=@C>A?=;86443211014#&745'44556679;<=?C:=@=:8644321014#&7335'44556679:<=?D9;@<:86443214#&73454$556678:<=?D8g:@<:86543214#&734554#556778:;=?D8A:@<:8654314$ 䯐&734"556778:;=?D8A;@<:86531225$ D-*)'9&7434%556678:;=?D9d=?=;875332125$"0561 &743)4556678:;=?D:@?=;865432110125$'55347$&7443'22456678:;=?C<G>=;875432125&h(636*\&7443'22356678:<=@B@P<>;975432137&B0432:&74432$335778:<=@AF\:?;9754321237&823433&7432$334579:<>A?Oo7?<975432125%@143438&7432 3344578;<>B<^4A<:8644322125$b*736,W&74332(3344678:<>B9rѭL4B<:8654322125$%85349#&7432&3345678:;>D7u,8A=;8654321125% 253352t&742&3345679:;>C83F>>;9754425( =3553=&74323(45689:<>A>_:?<97644323326* O056.\&7432'33455689:=?=P5A<:8644328*j$/5445-,&742&3345578:;=A8pW5A=;8654323:*U#85465"v&74323*45679:;>C5{D?>;97545443325:*t#84 9" &743233445789:<>A=g8@<:865433237:+*634550&743)45678::=@;9765454332388:*K-545/I&743455689:<>B:[ h8@<:86545543322687:*7254543&7456789:=@;9765433237:*33456/&745689:;>C6 L>?<:766543357:*Њ4560&745678::B7st@@><986654787:* *)*+*)**+ &74568::<>C8|6B=;8766556887:*jghghhi&7457889:;>B8fO>A><98665587:*&74 56799::B6hnpC@?=:876898779)&745689:;:=?@87p^4C><988987:*&7456799;<<>B2{$jU<@=;9::9878:)&74456789:<>>@<;<;98:*&7456789:;=?@A1JFuz5C=>=;:987:*z#63346679:<>@4j8S<;:984BJ+345679;<=;DxW";CA?><::98:.^ h'43345679; ?3NY'6F@?>;::87757,8bhghgghihg he;'42345679: @1@{5C=<986543213/%#$%$%&&%&'%&/42345679:9:?4f7Oa6A<;9865432332233232211223234434 54455423223433434567998:?7P9Q#4cmQ9A<;9876543210//0120122334568987:>9DoZ3H:A<;:8765432100//0123200122345578877:>;>F9A=<:8765533210/0123001223345578768:>:<H8A=<:976654432100110120123445678668:?9>y/"6wO5A=<;987654433112101231233455688679:?7D#?H6(M^Cj]2@==;:9876544332121212334456787679;@4QDUl{Q$,u1>>=<:99766543212122123123344566887689=?1eTcY-79?<<;:98776543322332312334556788679:?;3SgWL2?=<<;:98876543223234423456789878:<@4D@eRw3:?<=<;:98877654434542334567899779;?;2j`JM2=>==<;:9876654454565456789:989:>>2EV]lD<4>?>=<;::99887667656567789;;99:=?58{NTU/ar84>@>=<<;::98787677876788::;;:;>>55hsBr`:so92==<;;:9898977899:;<=<==<;:99:9::;;<<=??>81?rڌh' {]x ڙZ84;BCA@>>=<==<;<<;;<=>@B@817U9j~&8gŋZ>58=ABBA@?>>=>=??AA@=739TvzH$e̠tS@768;>@ABBCCB A><:756>PmԑtE9ίu`QF@<:88779;>DN]qԚBM _0¹͏+FTsyYKeۻpy]{HxM@Ó'Z|Ku'(h!ӻ-MMQ}n|[PXIʶS! <}V+hҚQCz~K(Žr ^qa Tȥupòuɯȷ  ȥiT+ 'm¹r0b⿐Pyg{2Ϸ>fxjmǀ0 {ڽxģw p*%@njIP~ i3)T  dO\}\| щd.ʪwcULEB?>>?ADJR_rٓ?$yZ~繋dJ;67?@?>?@ABEEB<7Cl&c~ juO35=@><;;=>=<=>>=<=<=??@@ACEC<8Oq1.r߆B2:@><;:9:<<;:;<<;:;==>??@@ADEA8Cx9!?2#P@2=@=;::988778;::9::9:<=>>??@ABEC9Aj=jaTa;F2>@<;:9987766558:;<<==>?@@BFF@BCDHF:YRL77@<:987654347667656899:<=?@AACCEIA=\R[1?>::8765433256565489;>?@ABFH:W-AbPw=7A<:97654332344324567653478:<=>=>>??@BBCHA?Aa^oKr1=?;:865443220121256763.+**,.378754346679:;<=>?@@ABEF9B]/=Bp/@=:9765432210//.121476-%*9L\bbZJ7)'/884269:;<=>?@@ACG8f0OiU(^2A<:875443210//.135)+S޺K)*67;<;::9::;<=>?@AAG;VmE5&T4@;986543210//.- .05))l^'4=;<==<=<;<==>?@AF>N$Q5@;976443210/.- ./ O L-=<==>?@@AABF?KIn{T5@;97643210//.-.- y,<=>??@AACCDEFJCP_b^4@:97543210/.-.,$/<<=>??@ABCDFFKCX}o\:3j o2@;97543210/.-..$/=;<=>??@ABCDEFK@f+J0A;97543211/.-0ݵ->;<=>?@ABCDFGL=}z2@<9764321/..-,-1t^3&#$$#'3Zk0>;<=>?@ABCDEGL<>K, >=<:7643210/-,-.(@U"'15663*%O@9<;;=>?@ABCEFHJADZ3[6?:86432100/--,-0 3354234;6-->;=>??@ABCEFIEVllp0@:875322110/--,,-/%Q)??51234:=>)L6<;:;<=>??@BBDEFK?P@8?<975322110/,-0?=<9239; @*->;<=>??ABBDFGK>hZW\7>:76432110//,-.*9)0<=<;4236; <<4:;<=>?@ABDEFHET3or0@;8643210/-,,-0 lh"3<<;;7234:; >0k_3=;<=>?ABBCEFJ8dE;<975332110/-,-0//0<<;;:4347; <6->;<=> ?@@AACCECCBvq1@:86432110/.,,-/% 21<<;:;723344:; >-.>;=>@ABCB@E:w d?=;9753310/.,-.,0y"21<<;::933448; =0n4<;=?A >?AE=2?:86432110/.,,-.):a$12<;:6345; <4Z9;<<=>>?>>?@>=?@D:sfpA<<96432110/,,-.)>Z&22<;:98449; <5U>;<;<<;>?>>?>>?>;<>?AD?1?:8543210/,,-.)>`%22<;:9957<; =3\=89876>?>>=>;:;=>@E9N:<97432110/-,-.)>v#23<;:9878776 9+n;4767>==>=><9:;<>?BBGI<2?;8643210/-,- .)>!33<:997=?94334 558&=4767>=>=<889;;=>@E7m4>97532110/0.,- .)>0/3<: 98;@@A?9535 7-=47678>= <<9788:;<=?C=`@<;8643210/0.- .)>i#7;:99@A?:645570O=476678>>=-<<;67789:<=>AD>V1?:7533210/00/-.)>)5<::99?@A?:65:'=4789>>=<<7677889;<=@E7Áj4>97532110/0/-.)> "?::9=A@?@><3B=58789>=-<<95667889:<=?C=]C;<8643210/0-.)>y)=;;@?D/=5877899=-<;65667788:;<>AC@P3?:7542210/030./*? +5DB@? B4f>69:;99:=<<74556%889;<>@E7y 1?97532110/010#$''&'!8 O29@CCBA?@A?TE;;8643210/02&ӭD=B/5;=<<95%6778:;<>@C@Z6>:7542210/02'[ 7D/~v/?<;65$6789;<=?D8.G1?:7532110/03"42q,?<845$6779;<=?D6s1?96532210/03"\(w-?:5$6689:;=>C9tp3>96432210/03"%-?75 66789;=>B<^\7<86433210/03"^.=5"6789;<>A?OP:=8643320//03#-95"6779;<>@ADG>>;864320//04#(85)44556779;<=@C>A?=;86443211014#&745'44556679;<=?C:=@=:8644321014#&7335'44556679:<=?D9;@<:86443214#&73454$556678:<=?D8g:@<:86543214#&734554#556778:;=?D8A:@<:8654314$ 䯐&734"556778:;=?D8A;@<:86531225$ D-*)'9&7434%556678:;=?D9d=?=;875332125$"0561 &743)4556678:;=?D:@?=;865432110125$'55347$&7443'22456678:;=?C<G>=;875432125&h(636*\&7443'22356678:<=@B@P<>;975432137&B0432:&74432$335778:<=@AF\:?;9754321237&823433&7432$334579:<>A?Oo7?<975432125%@143438&7432 3344578;<>B<^4A<:8644322125$b*736,W&74332(3344678:<>B9rѭL4B<:8654322125$%85349#&7432&3345678:;>D7u,8A=;8654321125% 253352t&742&3345679:;>C83F>>;9754425( =3553=&74323(45689:<>A>_:?<97644323326* O056.\&7432'33455689:=?=P5A<:8644328*j$/5445-,&742&3345578:;=A8pW5A=;8654323:*U#85465"v&74323*45679:;>C5{D?>;97545443325:*t#84 9" &743233445789:<>A=g8@<:865433237:+*634550&743)45678::=@;9765454332388:*K-545/I&743455689:<>B:[ h8@<:86545543322687:*7254543&7456789:=@;9765433237:*33456/&745689:;>C6 L>?<:766543357:*Њ4560&745678::B7st@@><986654787:* *)*+*)**+ &74568::<>C8|6B=;8766556887:*jghghhi&7457889:;>B8fO>A><98665587:*&74 56799::B6hnpC@?=:876898779)&745689:;:=?@87p^4C><988987:*&7456799;<<>B2{$jU<@=;9::9878:)&74456789:<>>@<;<;98:*&7456789:;=?@A1JFuz5C=>=;:987:*z#63346679:<>@4j8S<;:984BJ+345679;<=;DxW";CA?><::98:.^ h'43345679; ?3NY'6F@?>;::87757,8bhghgghihg he;'42345679: @1@{5C=<986543213/%#$%$%&&%&'%&/42345679:9:?4f7Oa6A<;9865432332233232211223234434 54455423223433434567998:?7P9Q#4cmQ9A<;9876543210//0120122334568987:>9DoZ3H:A<;:8765432100//0123200122345578877:>;>F9A=<:8765533210/0123001223345578768:>:<H8A=<:976654432100110120123445678668:?9>y/"6wO5A=<;987654433112101231233455688679:?7D#?H6(M^Cj]2@==;:9876544332121212334456787679;@4QDUl{Q$,u1>>=<:99766543212122123123344566887689=?1eTcY-79?<<;:98776543322332312334556788679:?;3SgWL2?=<<;:98876543223234423456789878:<@4D@eRw3:?<=<;:98877654434542334567899779;?;2j`JM2=>==<;:9876654454565456789:989:>>2EV]lD<4>?>=<;::99887667656567789;;99:=?58{NTU/ar84>@>=<<;::98787677876788::;;:;>>55hsBr`:so92==<;;:9898977899:;<=<==<;:99:9::;;<<=??>81?rڌh' {]x ڙZ84;BCA@>>=<==<;<<;;<=>@B@817U9j~&8gŋZ>58=ABBA@?>>=>=??AA@=739TvzH$e̠tS@768;>@ABBCCB A><:756>PmԑtE9ίu`QF@<:88779;>DN]qԚBM _0¹͏+FTsyYKeۻpy]{HxM@Ó'Z|Ku'(h!ӻ-MMQ}n|[PXIʶS! <}V+hҚQCz~K(Žr ^qa Tȥupòut8mk@ +E`y|cH. .Y۹_2RИX"XaJ$Rb7Fau&-.) e|;P& Xp/E[ `y!i!_x E[!2c| %7]v!7Le~ ,5IQim    nRk6K. h9O#az':g$6J` e~%p&fKb $5`x ,CXn  %!0&6&5 ." mAQ%]n",HU drmzbm FP"dm(%^ڥe+7eśk< 5PmƽoT7   ic08 jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2d#Creator: JasPer Version 1.900.1R \@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP ߂Xb5uY̫W-r1 W$t_ۅwo <@KR%MZGDџ߂Xb5uY̫W-r1 W$t_ۅwo <@KR%MZGDџ߂Xb5uY̫W-r1 W$t_ۅwo <@KR%MZGDџ߂0(W+'BZYM _/_˖?7 6@[@|uJnߚEO$a+Tj <"u$P}SIAR.<\zԡ?s,@lzŵڪEb)rL)V"5nDY]{h{bv9(>pr-"{㰋$J&vD5("Z^0CK8WP*_$&.'|왮U)gc=ǜ'v!sߚEO$a+Tj <"u$P}SIAR.<\zԡ?s,@lzŵڪEb)rL)V"5nDY]{h{bv9(>pr-"{㰋$J&vD5("Z^0CK8WP*_$&.'|왮U)gc=ǜ'v!sߚEO$a+Tj <"u$P}SIAR.<\zԡ?s,@lzŵڪEb)rL)V"5nDY]{h{bv9(>pr-"{㰋$J&vD5("Z^0CK8WP*_$&.'|왮U)gc=ǜ'v!sߙGd7sPNq}iH;j\\ R/ޖ#aO5ԩv0*?H?qV(fm)С0&7,~y1Q-[i-q >Oe _.bvuLx4(nBߝ~w ~|~\|\.qAE`jQ1JsBN%LebqeJqg!㍒.T~d jgH0pB 47/mAF r)ڱX 2҆v{|%U߽jYB8piaM,:Q@jp:tҾ@4*cV)w3II[ HDEm@3L\l[՝];#HS3nh>s5_*zh[.vMhYfZ'C;[=6ԖHM{%l&k>kӧ !Ai9 ͂)3YIEF&_5wT0$/WiP9ɗ:_/P>g2z$k"@^rlD:-.+(8ZLK_[|$if"p#kWԿGB[f.z\8Dm0=RM-Ϯ7.K]qsd<:cI̋jv?a+s9 Vw9D5, hFq򍸢`[Uw ċ~)Y7K oM.!nms(iߝ~w ~|~\|\.qAE`jQ1JsBN%LebqeJqg!㍒.T~d jgH0pB 47/mAF r)ڱX 2҆v{|%U߽jYB8piaM,:Q@jp:tҾ@4*cV)w3II[ HDEm@3L\l[՝];#HS3nh>s5_*zh[.vMhYfZ'C;[=6ԖHM{%l&k>kӧ !Ai9 ͂)3YIEF&_5wT0$/WiP9ɗ:_/P>g2z$k"@^rlD:-.+(8ZLK_[|$if"p#kWԿGB[f.z\8Dm0=RM-Ϯ7.K]qsd<:cI̋jv?a+s9 Vw9D5, hFq򍸢`[Uw ċ~)Y7K oM.!nms(iߝ~w ~|~\|\.qAE`jQ1JsBN%LebqeJqg!㍒.T~d jgH0pB 47/mAF r)ڱX 2҆v{|%U߽jYB8piaM,:Q@jp:tҾ@4*cV)w3II[ HDEm@3L\l[՝];#HS3nh>s5_*zh[.vMhYfZ'C;[=6ԖHM{%l&k>kӧ !Ai9 ͂)3YIEF&_5wT0$/WiP9ɗ:_/P>g2z$k"@^rlD:-.+(8ZLK_[|$if"p#kWԿGB[f.z\8Dm0=RM-Ϯ7.K]qsd<:cI̋jv?a+s9 Vw9D5, hFq򍸢`[Uw ċ~)Y7K oM.!nms(iߛV e~PEn{/dq@|.-8A~,f\n"49bUmT3;.&l0}7ٜ:YpDƽ'f:d!VO7yV\"fYw4?]?1#K_c( P'0$qf;~ qjӛM%ڄڅ.nI 툉\Mž|t$BC(Jd@iLH|q\s(ni"φ :?50$><m \hD|aȲD8`Z`E;?X~J :NըQEm\"9KrPEwuNR' OGj3 viރW3$$AAL#+a&: q7K۸3WV^o| z6Xaj>^E*#4w؝ ̉z75}3!e< 6JūxΈ1WN vH^LJCr\)Sey2n+<6=um z5IMimxTj 4 y:i~2yP 3ݻ`|%rT:amJQDd<. FmJMb`VDT:&Yi'ף{^ԛQ8sY`!~WVBp7ߢ=D t]ݦHW$٢oYNZ{ {8l8o نi:풴"hK6(5:Ia@!_^2Ȫ{2xcsf %ɧ]l|C^ [(iQ邢cU3|^9Cxa#D,GM b{kujs 1X6*yBDH|G& >ymu|Fɋ 9Oq,%vܣ$:Pk\"Xzu;#LYm|J,1$^u?cyd 0#<KC)uj2U@V:gJBj[N3vѻECJX+U$%ԃ)CF'|BH9~\cst ?o׌GMeyDhR/< %D| Ж3A7~ ( 4L" 63֡mm,9r[˞')&蛣: Gr0aFᚘ\v8l#?VQk$A+9KnY2KL:ReCH➧Y <5q:nDwUxfhq6V<N2Q xM]ʤ+g׋& ai_ætK`LjiX(eW"֛֓pVx`jx,^\jGJh{`bޏ[Ur%W,\)U+.9@ uq"H4>><m \hD|aȲD8`Z`E;?X~J :NըQEm\"9KrPEwuNR' OGj3 viރW3$$AAL#+a&: q7K۸3WV^o| z6Xaj>^E*#4w؝ ̉z75}3!e< 6JūxΈ1WN vH^LJCr\)Sey2n+<6=um z5IMimxTj 4 y:i~2yP 3ݻ`|%rT:amJQDd<. FmJMb`VDT:&Yi'ף{^ԛQ8sY`!~WVBp7ߢ=D t]ݦHW$٢oYNZ{ {8l8o نi:풴"hK6(5:Ia@!_^2Ȫ{2xcsf %ɧ]l|C^ [(iQ邢cU3|^9Cxa#D,GM b{kujs 1X6*yBDH|G& >ymu|Fɋ 9Oq,%vܣ$:Pk\"Xzu;#LYm|J,1$^u?cyd 0#<KC)uj2U@V:gJBj[N3vѻECJX+U$%ԃ)CF'|BH9~\cst ?o׌GMeyDhR/< %D| Ж3A7~ ( 4L" 63֡mm,9r[˞')&蛣: Gr0aFᚘ\v8l#?VQk$A+9KnY2KL:ReCH➧Y <5q:nDwUxfhq6V<N2Q xM]ʤ+g׋& ai_ætK`LjiX(eW"֛֓pVx`jx,^\jGJh{`bޏ[Ur%W,\)U+.9@ uq"H4>><m \hD|aȲD8`Z`E;?X~J :NըQEm\"9KrPEwuNR' OGj3 viރW3$$AAL#+a&: q7K۸3WV^o| z6Xaj>^E*#4w؝ ̉z75}3!e< 6JūxΈ1WN vH^LJCr\)Sey2n+<6=um z5IMimxTj 4 y:i~2yP 3ݻ`|%rT:amJQDd<. FmJMb`VDT:&Yi'ף{^ԛQ8sY`!~WVBp7ߢ=D t]ݦHW$٢oYNZ{ {8l8o نi:풴"hK6(5:Ia@!_^2Ȫ{2xcsf %ɧ]l|C^ [(iQ邢cU3|^9Cxa#D,GM b{kujs 1X6*yBDH|G& >ymu|Fɋ 9Oq,%vܣ$:Pk\"Xzu;#LYm|J,1$^u?cyd 0#<KC)uj2U@V:gJBj[N3vѻECJX+U$%ԃ)CF'|BH9~\cst ?o׌GMeyDhR/< %D| Ж3A7~ ( 4L" 63֡mm,9r[˞')&蛣: Gr0aFᚘ\v8l#?VQk$A+9KnY2KL:ReCH➧Y <5q:nDwUxfhq6V<N2Q xM]ʤ+g׋& ai_ߝ~zJ UN ^Ӗ>)vz4׸q!F1ima)Q005KJ2yhor~L34dT>7^xXbcn2j[t?@b]Ú]Zw8:x늙?YS]歆;dRڼ_Nq)K?4rpu٠\;Gtg%;r赴2=:0;4k0\ AD}xSmiPI]e_F$=o}ϐG[R;{ZmdswßPI}YVu0,ɐ٨vwmLv^`"1\LCl"B,>/ TG֎ݞiDr-+ I%!Dzåk%ₑ@z`34;U9>.Ɏɿqgyǖ@O߉7F8Bvt@o7F'T| Y?Cy !3+b+V4iRǓ \LT*pУ(0ʂrC.I&'iK&DWړ?zan%{MJP1!Y5apž<5TWuʋu\>yt~ݎEֱoDo~Ʊ{)A<5;;֕~N\[Q5QSzХ(R= ]/.C5"YJbMQwsr!ǚKͽe@ -&+7W7,W|R|ok,HC?&M˯cfdQ|^1g #+Zx2lyueHZmL`m;>xUՐB=:3<) ؟тp'n fr>abV8 8xsZ轟~Β?C ٯ:wKcV|u^EV*0}~bIX5hƐ=Go=+9} z 5l LF/< jE. <]Rj[l]P4*&8G6$i_d mG/!~Q@BMysR&wN?}ʫ98^ha >T<=*;&wֿL5RoUrOwrb ^x*}k cK&-{oA]RҌOu! qId$ _O ~iacy"eQeVJx7ÃF<\"l')-urepEӍ+d-j|q^P8c.˟}s"pJ3h#C$%j2 !xZ'914tq]_5 y /m[eAB ^2e) $z9>i\ǪD k"CxCWt!)oqa?h:kzw419L~07v^I'[8w xX󁋘K MpKĒs!b&eTx"9;3l,#s+ƙ$ Qoкb{U!dV@4cLwy7H؇I?EQ`Y<,f0҄zDGQ3+L\O5emZ'a&&>#MhO^> &Ω_ ^ s17r\ nz֣y^̘V73}6K Jݗ3TJmԪs]ۿCA.7免/0uֻsi.-4hxԤ7(` 0]@JoDhZ:Is ZuE;Tye宕⸼,9)Uq272ZG~so!4G!DzTGэ qUqSL^a&F=BG-9 >$U{ӥY#Ʉ~ȬJ CFOy(ɵ6-"x׽'V:2s꺮ݷC7V͈ba`D@z')9:M 36%!-IuJ[,9ۦ=LORf5/DBu, Z˔,NutNz5FQͤ5aIqsQTmd/4Zkht%m* 0Yr_ KO-vlA)'e5+Lv^yQuVٍ5[6'@?ڞ0:a$z"@WcDx#v>@9 2JUpç'M@ Q8]^+M;s.2Ag:rq=՚,H'Ѐpd6 7ȷ Y?ˌ|rif`WjYP~\6DU}OI7W;?\%F,>bSeCL 6TFn*z׶E V' L"LHQQ'7=Ǔ$ '&"DZsXRAD'I!bfaBŠEF?.\kϪ]zMa/a(~Vpx0nś_e$qIڛ$+M i~I l朦 37Iڙ, > ۑ:bx9=b%ckL>=Y@SaVTsJk1j3ۣpvMճID@kBUdwVmQpCO]/}ϊZoEg|_ .GfC[{̍mHk ڬ,#*v+pr9bf-G$g-dP*lgO1W"6V@T#"Pf_ܷ^m匾X5ow| x"6OzGOb5(41|ݛGEŶWͻf*!ǯ[z X,hXB!TO,d 3>vw*]۶tֲ`@Lpܱm͙ʆe˶ 2jY"~3WEbxA΢ZG:LpS 1ZѢb̄ UŬw@ ^Nl H܋ק:iC0[sٗ͏HK*=-#rt\ Mgd5[d@(loGJ >0AtObKS'%hKr,‹^Nn2.]<<XW#뽃*TRu-܈mKڙ- =Pw)7W&q_y$ӏl[o}F1-&eљr`_p@\F]e\ĺz$cj[io?Q {0l&Z%'/p19&sEhk۳"~j 3VKp ` {;hbZ#cm_M!ZVp_v& K -aSDR\[,ߛ0XJQp/cM0ZܣXj\V^ VFcb ]O;NbƤ,tr8u_#,!s"$c2/i,B~ஸ1 ,W5ޭ[oARW&N?%e`t}W =AFe'os&Ӎlc/N'G~M&5u a<~s |`Sr誔1XؗhNd~mƀ'\r%*5 3(~y"d?hs=h& sAtD^C-&wCS*aܹk93kָ;<[ >W^W$`$l{t|=p gifKDeh}`/@Vঔ(* 5,ŵ(&rHpڸJ Av뙥+kCߧEwVqkࡽ[o}2ۻ P|aITTtiRp^eϬ w^~ Yj.ؕ>.HfUґ/swK3syϧ;unQ<6B_ð=(y1 ]=˔gf>5XĮW3Y_Nɞ(Ɗ)؇m>؛ga再i1~cfoh {GL"ۼTC;^wvg֩wһ'V]Mx afi9YVvDD^j_uvX*YgvV.b\ 0H}ZafDn e'b%@-zS>*.ϕ 1hvWIl[gAolGr.A##RB%vbH ,mP3( .2>PgY S;0R7NK4/g2$CJ|&>$|O}dF~]5xKFg`L<~)nЮ!14m5&e҇;DoA{CzԪ XG0])dkaBvw^َDIAHBZe-K0+qp稻aTV|$Jǻ yn L C CpGQˬh,?Q6dlNU-bZEolU +Xd pPny- xUՐB=:3<) ؟тp'n fr>abV8 8xsZ轟~Β?C ٯ:wKcV|u^EV*0}~bIX5hƐ=Go=+9} z 5l LF/< jE. <]Rj[l]P4*&8G6$i_d mG/!~Q@BMysR&wN?}ʫ98^ha >T<=*;&wֿL5RoUrOwrb ^x*}k cK&-{oA]RҌOu! qId$ _O ~iacy"eQeVJx7ÃF<\"l')-urepEӍ+d-j|q^P8c.˟}s"pJ3h#C$%j2 !xZ'914tq]_5 y /m[eAB ^2e) $z9>i\ǪD k"CxCWt!)oqa?h:kzw419L~07v^I'[8w xX󁋘K MpKĒs!b&eTx"9;3l,#s+ƙ$ Qoкb{U!dV@4cLwy7H؇I?EQ`Y<,f0҄zDGQ3+L\O5emZ'a&&>#MhO^> &Ω_ ^ s17r\ nz֣y^̘V73}6K Jݗ3TJmԪs]ۿCA.7免/0uֻsi.-4hxԤ7(` 0]@JoDhZ:Is ZuE;Tye宕⸼,9)Uq272ZG~so!4G!DzTGэ qUqSL^a&F=BG-9 >$U{ӥY#Ʉ~ȬJ CFOy(ɵ6-"x׽'V:2s꺮ݷC7V͈ba`D@z')9:M 36%!-IuJ[,9ۦ=LORf5/DBu, Z˔,NutNz5FQͤ5aIqsQTmd/4Zkht%m* 0Yr_ KO-vlA)'e5+Lv^yQuVٍ5[6'@?ڞ0:a$z"@WcDx#v>@9 2JUpç'M@ Q8]^+M;s.2Ag:rq=՚,H'Ѐpd6 7ȷ Y?ˌ|rif`WjYP~\6DU}OI7W;?\%F,>bSeCL 6TFn*z׶E V' L"LHQQ'7=Ǔ$ '&"DZsXRAD'I!bfaBŠEF?.\kϪ]zMa/a(~Vpx0nś_e$qIڛ$+M i~I l朦 37Iڙ, > ۑ:bx9=b%ckL>=Y@SaVTsJk1j3ۣpvMճID@kBUdwVmQpCO]/}ϊZoEg|_ .GfC[{̍mHk ڬ,#*v+pr9bf-G$g-dP*lgO1W"6V@T#"Pf_ܷ^m匾X5ow| x"6OzGOb5(41|ݛGEŶWͻf*!ǯ[z X,hXB!TO,d 3>vw*]۶tֲ`@Lpܱm͙ʆe˶ 2jY"~3WEbxA΢ZG:LpS 1ZѢb̄ UŬw@ ^Nl H܋ק:iC0[sٗ͏HK*=-#rt\ Mgd5[d@(loGJ >0AtObKS'%hKr,‹^Nn2.]<<XW#뽃*TRu-܈mKڙ- =Pw)7W&q_y$ӏl[o}F1-&eљr`_p@\F]e\ĺz$cj[io?Q {0l&Z%'/p19&sEhk۳"~j 3VKp ` {;hbZ#cm_M!ZVp_v& K -aSDR\[,ߛ0XJQp/cM0ZܣXj\V^ VFcb ]O;NbƤ,tr8u_#,!s"$c2/i,B~ஸ1 ,W5ޭ[oARW&N?%e`t}W =AFe'os&Ӎlc/N'G~M&5u a<~s |`Sr誔1XؗhNd~mƀ'\r%*5 3(~y"d?hs=h& sAtD^C-&wCS*aܹk93kָ;<[ >W^W$`$l{t|=p gifKDeh}`/@Vঔ(* 5,ŵ(&rHpڸJ Av뙥+kCߧEwVqkࡽ[o}2ۻ P|aITTtiRp^eϬ w^~ Yj.ؕ>.HfUґ/swK3syϧ;unQ<6B_ð=(y1 ]=˔gf>5XĮW3Y_Nɞ(Ɗ)؇m>؛ga再i1~cfoh {GL"ۼTC;^wvg֩wһ'V]Mx afi9YVvDD^j_uvX*YgvV.b\ 0H}ZafDn e'b%@-zS>*.ϕ 1hvWIl[gAolGr.A##RB%vbH ,mP3( .2>PgY S;0R7NK4/g2$CJ|&>$|O}dF~]5xKFg`L<~)nЮ!14m5&e҇;DoA{CzԪ XG0])dkaBvw^َDIAHBZe-K0+qp稻aTV|$Jǻ yn L C CpGQˬh,?Q6dlNU-bZEolU +Xd pPny- xUՐB=:3<) ؟тp'n fr>abV8 8xsZ轟~Β?C ٯ:wKcV|u^EV*0}~bIX5hƐ=Go=+9} z 5l LF/< jE. <]Rj[l]P4*&8G6$i_d mG/!~Q@BMysR&wN?}ʫ98^ha >T<=*;&wֿL5RoUrOwrb ^x*}k cK&-{oA]RҌOu! qId$ _O ~iacy"eQeVJx7ÃF<\"l')-urepEӍ+d-j|q^P8c.˟}s"pJ3h#C$%j2 !xZ'914tq]_5 y /m[eAB ^2e) $z9>i\ǪD k"CxCWt!)oqa?h:kzw419L~07v^I'[8w xX󁋘K MpKĒs!b&eTx"9;3l,#s+ƙ$ Qoкb{U!dV@4cLwy7H؇I?EQ`Y<,f0҄zDGQ3+L\O5emZ'a&&>#MhO^> &Ω_ ^ s17r\ nz֣y^̘V73}6K Jݗ3TJmԪs]ۿCA.7免/0uֻsi.-4hxԤ7(` 0]@JoDhZ:Is ZuE;Tye宕⸼,9)Uq272ZG~so!4G!DzTGэ qUqSL^a&F=BG-9 >$U{ӥY#Ʉ~ȬJ CFOy(ɵ6-"x׽'V:2s꺮ݷC7V͈ba`D@z')9:M 36%!-IuJ[,9ۦ=LORf5/DBu, Z˔,NutNz5FQͤ5aIqsQTmd/4Zkht%m* 0Yr_ KO-vlA)'e5+Lv^yQuVٍ5[6'@?ڞ0:a$z"@WcDx#v>@9 2JUpç'M@ Q8]^+M;s.2Ag:rq=՚,H'Ѐpd6 7ȷ Y?ˌ|rif`WjYP~\6DU}OI7W;?\%F,>bSeCL 6TFn*z׶E V' L"LHQQ'7=Ǔ$ '&"DZsXRAD'I!bfaBŠEF?.\kϪ]zMa/a(~Vpx0nś_e$qIڛ$+M i~I l朦 37Iڙ, > ۑ:bx9=b%ckL>=Y@SaVTsJk1j3ۣpvMճID@kBUdwVmQpCO]/}ϊZoEg|_ .GfC[{̍mHk ڬ,#*v+pr9bf-G$g-dP*lgO1W"6V@T#"Pf_ܷ^m匾X5ow| x"6OzGOb5(41|ݛGEŶWͻf*!ǯ[z X,hXB!TO,d 3>vw*]۶tֲ`@Lpܱm͙ʆe˶ 2jY"~3WEbxA΢ZG:LpS 1ZѢb̄ UŬw@ ^Nl H܋ק:iC0[sٗ͏HK*=-#rt\ Mgd5[d@(loGJ >0AtObKS'%hKr,‹^Nn2.]<<XW#뽃*TRu-܈mKڙ- =Pw)7W&q_y$ӏl[o}F1-&eљr`_p@\F]e\ĺz$cj[io?Q {0l&Z%'/p19&sEhk۳"~j 3VKp ` {;hbZ#cm_M!ZVp_v& K -aSDR\[,ߛ0XJQp/cM0ZܣXj\V^ VFcb ]O;NbƤ,tr8u_#,!s"$c2/i,B~ஸ1 ,W5ޭ[oARW&N?%e`t}W =AFe'os&Ӎlc/N'G~M&5u a<~s |`Sr誔1XؗhNd~mƀ'\r%*5 3(~y"d?hs=h& sAtD^C-&wCS*aܹk93kָ;<[ >W^W$`$l{t|=p gifKDeh}`/@Vঔ(* 5,ŵ(&rHpڸJ Av뙥+kCߧEwVqkࡽ[o}2ۻ P|aITTtiRp^eϬ w^~ Yj.ؕ>.HfUґ/swK3syϧ;unQ<6B_ð=(y1 ]=˔gf>5XĮW3Y_Nɞ(Ɗ)؇m>؛ga再i1~cfoh {GL"ۼTC;^wvg֩wһ'V]Mx afi9YVvDD^j_uvX*YgvV.b\ 0H}ZafDn e'b%@-zS>*.ϕ 1hvWIl[gAolGr.A##RB%vbH ,mP3( .2>PgY S;0R7NK4/g2$CJ|&>$|O}dF~]5xKFg`L<~)nЮ!14m5&e҇;DoA{CzԪ XG0])dkaBvw^َDIAHBZe-K0+qp稻aTV|$Jǻ yn L C CpGQˬh,?Q6dlNU-bZEolU +Xd pPny- џz\OOB#y^We1;J%@TQeF|cXTH8T{4y{PeabpOqf(r6xHQ*\$`s {0ạ(~Xl.W[~oamMx (e~C=g(/W>Ks )%_Su4YjŮ z8fJ1 *< ̠s\`d"Sm<^Fթ~|S[ M4~9} Rvb_v֛\MjG<<55F?<>BlѢ %,|P/lx EеPH XQ~NKidWY)jG85k$lf[nn4Gdh^՗"dS#mb,N Lr̍T!el%#X̲ `Y߱ݳ5zKAF Lp7]sauσi1p2\S"u> f#4YB#\{%Om0a3ߧ F^  ߮~LDH+=gyc|mik)OTix 1t"񶾑w<D %gc7$4Gz.@z_ K]Q|ỸcJjmsE Lzpmyi԰t(iD2c@/K'$i ^tW݃zI{yt$`Vݙ eb;2R<Շ'|tA?f?d%nU@81sQ7L SD4fܠtwPFgf[),D xHe8rmw?:TvVh/y9z42Z6"'l4ߏ.m(T^ 8sypas"UȇsMӗ3)$lJ[:gM(~q11飵yb/w4%̿EQrZml.{V@R1 bK8x,zG.>Hv3< 1ℚz?:1zmtUY>8RC^ZӔ,GCGvZby|Qy Z2kEfBGo6<# Ka!]?)gX8>ЈBPڟ+oؗnyE m>xHp`c"䷃= .?t]V.Zb~.6Xu"Hݶ)W`t9|).߅L]^z&RN |&цtl(wd_ȴ)^̜اCÿ/}_oxxu;lڲbى-ņyUXyq)F"2eu{\>Ǣhlv;Uov}酱2)qUrjohnu}-sn;aTmJBf'f<ގacVm۪"fu!y}au94 1l 3~ۨj3;cV!ho?g Sm6,>Kg$|Lgc|hWV<~ :YwCydwfQB IZ\[05 + p{65ܨ oIl[&C߻:\= X7J̰P5F"P6)v~X]jE%i>>$$ gx iÑ7DlHaB"z?4$/ük`}NNRE"uj죍ey<8+/CUX &&bHּP:jٹsymqY@Ԏ넰4䒪i#B2%KI/'>L[qE5!гR0ڌZ)oD^ )`]`L{;-ҢLꮿ Օip]sWYz z3A)_50 n6~vp)sm |%g8,8(cj`!_R YJV}EbsroFW>P_lƈ_A5jܙ?d["z>e>~ dMʠY×T" xU$L۱XYf-(;ԲR h [JFWFY14,~/(}W*Am. _XbA*"M)_=Q^zqH<6Z h@{u(-Ìʋ_u8KhAr9(׋!UQj;pκwj_V4~B/Bx ׁ44Bqu0ͳ1|].WiUT-y.v%>4poG`,^h$lF=V&bf]FW91@ HzUAqs SHhϜ(9^} }皩7U%@}dʙ DrT:yxԸeR'Il#֮~ukC٢VX07UGvZ׿ 4Ձ ]'i.wzIB7kW6&, _/ V# &2cVuU~?}d/:ٞ   '~塜tsa:p~$nKV}wQZ&oY(OhX2Naehs*NfI7{B@G#N!("Wإ鱹(KϬwě"!dY덩P UqsVL2?^;Ԫ?7)N',w&!VD W<n (Ʉ vDѮ|)v<ܐcMW^?L%y535te T +0dK{uңn9oL! M,pKP1?O ')Ԥs:X,L@$p[Z: ՏkÏ2isلܾƑwP|4 b^~>p\ۉTmb+%8vL͗-Цe@S1`*x]znK[,\ԇ kޡ("(S. ))*~[WU/Xo,Kj0ZTj |-KbU r~pS~Oo>.Zs}9'+=kU5>J%-P4տ]Ly6%eu]8Y΀LĔ2\EdkR7z.{3ihfܸ2 \ߋ$21냐2.ACvZGz#LewK!3ͿJ1ؙF"!eͯ͝B3ىlKtpqX'ۭ{~WǷo]P}I^;ѫŹ 3?Q4 v& $S$\Ik%]-{fOԣDz(O;ژ{_GMݲwB WK"v(dO?vo_$~PsNNGtcom* Vc^(j[ ]`z^s_0\$G,rZUi0S^p&wFx;G"i$蜐 ͩy?HP)o2~tCj}N f~f=tKFkRRR {+_BّqT[&z0-tqЗ/Q7UhXO zoUw/@{ՠwM~u+i9=\x~znzxhW)! ;[`o3JmYA{kx?%sz^O;"GV|ݓ3= p"B<<2 xcۯ̀9X&sݙD] I'#,,\ׂgC(, c;rm<^d_ڐ؞GĄph]d;SUє%ӧ4 )YsKZs@~0 JL8P{aMfqߏ% `vphRW)[wǨE+Ej>vtJ  2oyׁY =+Ja1r a"F2t3Pe$Rׂ /ƛK˖ʁiG]#3CRg$q9ckW{8ҦT |.&4.lOO}NY6X;\Gn23 1R435V|RB9`URgQbҾEH J5aoH2Y(Y0_$`X_jġ:tџn2}h1F2\Ubp[3Gj~>Y'ywֲPPGU1}!18*Y ֏Yҹ2IOCvܹQ@&+nǗpQcT29xJ@sO`>9@|됺:/[OX%ƾ $ֲT3g&o + 2?I2sz*&jlW:U Z wuAnC FF1^ֿͩ@4"7&e!M҃hÓPNM3&./),9&Es1:3ˏcw e1+ضE]/d^igK Я^ kfؓ"I,9n‚pk;o dDZˁO<.{Ba!' MPޤcf@=2bk98^[Nڸ F f<6sn$C*C%'-?- B׈N7މ~ZcUNwT,>l>]ԞKԋw3s O/~UZxOu NShl 9)+"?Q@|ٰ,tcdIMi)@os=)e)l9+Dd7)^ȰrMٕJS! A8۞K%r:Ӣ'ҩ\Đx]'sIVn5g6a*.X|c:Bc]9ӿ 8kLNdRYK}YM;?=VhZDt=MjQ 5$J9)VZ,mltQ@a79f,Y/8_R7[ Ul14T+K8'u4(zYm[D0ß[妛Ԑ0BH ??+mpF$$3b ˿jLYge։V@{P\-~PRN)DrjB^IzTB ˮi^6NՄ,W!ۆ9|l&1©1 ͫX 8XXȞ^9ĵISLݬ)}'w#,S]1mqA޾1M!cW:Jz[u@v?`3l+,|KV^0d-+;:{ 82ܟSyjZ*J!1{ ,EаGo*"RDp{@٬ .B%ZkI8=(ڹ >#uN:oóXNS/3{- mdH='dgWs΃Zv#0LVˑn;!brEzӴ.C˟Q{2Յia:4On)Sqɗv58RɸSKٌ Jg[oja7maWJU-⾎ ѱX73k4MVk]dZ;O}&1`pIHƂ'w0H}8I[?lq255b91̃EBHg%C4w\ !t qdUt>b|#]ZN5Ƌ.<:#ڿ XF(qq>(ĕ ~eHN@"=f1ICƄ!hNz撝յɯ T9bMNϞ 8#~ipykWvhWFuK0FD|()NZ$ 9z&6ǐ2__>;LF G)tJIDD!(%i9khKp/c"fy8_5|VPVd.1Y) ( :G8:m t̰Ԍ𥙈ucĐHm^B0'8+RiD,/{YZ=eh\o0*|i^i㭎 [y3z@ԭP,1XQD kaaBGټbȺSxyA>dy-(X_ *Tm׵Gx^P@PoUJChk^B KHv[c[Nʕ$k-Q/qP4.4_nrwYI 9J g)Щ'˃Frm)ZIhbہ;J!B*N 1CCZ.(1 ?˸aڸ& b|(kI=-'fBV_QQ~enS?3j]BO1 ߑ'V}+!vĴaWeq XaD8 ثe'uG:iR_DaI+Lc%zj=\kY$aD?RK\&81bIld,2q0u& qϟ&*c8!†y} \;ܳ0r9?C_,yyٛe/,:-} aDĖ#T(9E`(ȳPs)Z?8Ոb-#3y3H1-F+Fw ;6&mHUt2aHz7{J^O*~X$Gb 9ވVPD Qo"-N긁ع*8>a\pޘ-mSY@Q'6--u];x9 7'3*1cFy&QPjdpc*$؃wkZZ#m#v 뭺(ćcFnk,!Yh?/$3JLCW`G1k#IFN,LgD|" % Z04EZ#:{7;X# !|FBcz:V9c ~Q^p:? 8طQLg{ܦHpNCMN[<4xՆ:ه_]$ዠ~6kBNNUw 7EI|/0$;`ᓴF44>A[j&+yUXG hxVOZIcR$cgS!Pm}gY͗G|yo'`HNF&k.=a87/QjK rS\v^G!pZ|.DW6PԌcV ]SwOR,\[?Cu.lKƽ]YnV4-CmZGG؜YQ!C|3r޹*%}[8g!%4}Ի)& ۰o"U+5\i2B|tXR0+DNfkDP.Bއ"kd5@F@M3LT{ZyªBYMJ3rۃ`5s@8HfAD}1T[% !h`;PK-!CL8*iu!>[S ,r>ŞM{V u'tjKXXb!E! 8:2JY$Pit`(*(isL2qє1c@2_Wc){H[qcCX7m۞xr7gcx;ݓ)㾌qNu w#%kHHd,bݳ55}g>ӉX^9u#ELi IIOWX3>!RU\ȰѬqEw0@/ѐ:#U6E(M3RRk/ij{Oe!蔅#coK﷿&nT>nf7^Ba-܁q-, iqU 9U_$|bUQP"n_dvh[Bi&jpy w'Ȧ}`px~qvRV ߿5nsGjh7!nv! y47liW*.E9ƮEQ7 B[w/_$i 3Z7֞8+ W+Jw:lߨ:^hEswAfd\Mb7n݇!fLwwp\$ZAƂOtL*x{'`L뗤c} s"2+"mE0 ڊln":FEt!iDՒo3k^;Z&[ eZ,6,)/O lg :>K-z-<(d֫1e]zZn%yG+Q*'kbL2MeIvzǿR%^# Cʜ _#n+&A2:Vw+f !$*bƁ4N))xLKK+; 4M~7Raih]MN!B&>ǩR>ݯί˛߈@TS4ymŃ?e?i"0 mjB6Eo9=% P4SJCd#Ycr HXuVnl}2Z!X6PnoeSBP4xcx0qtL2$VHNz ͱm); +^ΕBPmg[8||? 6#Jk!JIV|i=AqNmxm}Q(eM ?@|1Cÿ/}_oxxu;lڲbى-ņyUXyq)F"2eu{\>Ǣhlv;Uov}酱2)qUrjohnu}-sn;aTmJBf'f<ގacVm۪"fu!y}au94 1l 3~ۨj3;cV!ho?g Sm6,>Kg$|Lgc|hWV<~ :YwCydwfQB IZ\[05 + p{65ܨ oIl[&C߻:\= X7J̰P5F"P6)v~X]jE%i>>$$ gx iÑ7DlHaB"z?4$/ük`}NNRE"uj죍ey<8+/CUX &&bHּP:jٹsymqY@Ԏ넰4䒪i#B2%KI/'>L[qE5!гR0ڌZ)oD^ )`]`L{;-ҢLꮿ Օip]sWYz z3A)_50 n6~vp)sm |%g8,8(cj`!_R YJV}EbsroFW>P_lƈ_A5jܙ?d["z>e>~ dMʠY×T" xU$L۱XYf-(;ԲR h [JFWFY14,~/(}W*Am. _XbA*"M)_=Q^zqH<6Z h@{u(-Ìʋ_u8KhAr9(׋!UQj;pκwj_V4~B/Bx ׁ44Bqu0ͳ1|].WiUT-y.v%>4poG`,^h$lF=V&bf]FW91@ HzUAqs SHhϜ(9^} }皩7U%@}dʙ DrT:yxԸeR'Il#֮~ukC٢VX07UGvZ׿ 4Ձ ]'i.wzIB7kW6&, _/ V# &2cVuU~?}d/:ٞ   '~塜tsa:p~$nKV}wQZ&oY(OhX2Naehs*NfI7{B@G#N!("Wإ鱹(KϬwě"!dY덩P UqsVL2?^;Ԫ?7)N',w&!VD W<n (Ʉ vDѮ|)v<ܐcMW^?L%y535te T +0dK{uңn9oL! M,pKP1?O ')Ԥs:X,L@$p[Z: ՏkÏ2isلܾƑwP|4 b^~>p\ۉTmb+%8vL͗-Цe@S1`*x]znK[,\ԇ kޡ("(S. ))*~[WU/Xo,Kj0ZTj |-KbU r~pS~Oo>.Zs}9'+=kU5>J%-P4տ]Ly6%eu]8Y΀LĔ2\EdkR7z.{3ihfܸ2 \ߋ$21냐2.ACvZGz#LewK!3ͿJ1ؙF"!eͯ͝B3ىlKtpqX'ۭ{~WǷo]P}I^;ѫŹ 3?Q4 v& $S$\Ik%]-{fOԣDz(O;ژ{_GMݲwB WK"v(dO?vo_$~PsNNGtcom* Vc^(j[ ]`z^s_0\$G,rZUi0S^p&wFx;G"i$蜐 ͩy?HP)o2~tCj}N f~f=tKFkRRR {+_BّqT[&z0-tqЗ/Q7UhXO zoUw/@{ՠwM~u+i9=\x~znzxhW)! ;[`o3JmYA{kx?%sz^O;"GV|ݓ3= p"B<<2 xcۯ̀9X&sݙD] I'#,,\ׂgC(, c;rm<^d_ڐ؞GĄph]d;SUє%ӧ4 )YsKZs@~0 JL8P{aMfqߏ% `vphRW)[wǨE+Ej>vtJ  2oyׁY =+Ja1r a"F2t3Pe$Rׂ /ƛK˖ʁiG]#3CRg$q9ckW{8ҦT |.&4.lOO}NY6X;\Gn23 1R435V|RB9`URgQbҾEH J5aoH2Y(Y0_$`X_jġ:tџn2}h1F2\Ubp[3Gj~>Y'ywֲPPGU1}!18*Y ֏Yҹ2IOCvܹQ@&+nǗpQcT29xJ@sO`>9@|됺:/[OX%ƾ $ֲT3g&o + 2?I2sz*&jlW:U Z wuAnC FF1^ֿͩ@4"7&e!M҃hÓPNM3&./),9&Es1:3ˏcw e1+ضE]/d^igK Я^ kfؓ"I,9n‚pk;o dDZˁO<.{Ba!' MPޤcf@=2bk98^[Nڸ F f<6sn$C*C%'-?- B׈N7މ~ZcUNwT,>l>]ԞKԋw3s O/~UZxOu NShl 9)+"?Q@|ٰ,tcdIMi)@os=)e)l9+Dd7)^ȰrMٕJS! A8۞K%r:Ӣ'ҩ\Đx]'sIVn5g6a*.X|c:Bc]9ӿ 8kLNdRYK}YM;?=VhZDt=MjQ 5$J9)VZ,mltQ@a79f,Y/8_R7[ Ul14T+K8'u4(zYm[D0ß[妛Ԑ0BH ??+mpF$$3b ˿jLYge։V@{P\-~PRN)DrjB^IzTB ˮi^6NՄ,W!ۆ9|l&1©1 ͫX 8XXȞ^9ĵISLݬ)}'w#,S]1mqA޾1M!cW:Jz[u@v?`3l+,|KV^0d-+;:{ 82ܟSyjZ*J!1{ ,EаGo*"RDp{@٬ .B%ZkI8=(ڹ >#uN:oóXNS/3{- mdH='dgWs΃Zv#0LVˑn;!brEzӴ.C˟Q{2Յia:4On)Sqɗv58RɸSKٌ Jg[oja7maWJU-⾎ ѱX73k4MVk]dZ;O}&1`pIHƂ'w0H}8I[?lq255b91̃EBHg%C4w\ !t qdUt>b|#]ZN5Ƌ.<:#ڿ XF(qq>(ĕ ~eHN@"=f1ICƄ!hNz撝յɯ T9bMNϞ 8#~ipykWvhWFuK0FD|()NZ$ 9z&6ǐ2__>;LF G)tJIDD!(%i9khKp/c"fy8_5|VPVd.1Y) ( :G8:m t̰Ԍ𥙈ucĐHm^B0'8+RiD,/{YZ=eh\o0*|i^i㭎 [y3z@ԭP,1XQD kaaBGټbȺSxyA>dy-(X_ *Tm׵Gx^P@PoUJChk^B KHv[c[Nʕ$k-Q/qP4.4_nrwYI 9J g)Щ'˃Frm)ZIhbہ;J!B*N 1CCZ.(1 ?˸aڸ& b|(kI=-'fBV_QQ~enS?3j]BO1 ߑ'V}+!vĴaWeq XaD8 ثe'uG:iR_DaI+Lc%zj=\kY$aD?RK\&81bIld,2q0u& qϟ&*c8!†y} \;ܳ0r9?C_,yyٛe/,:-} aDĖ#T(9E`(ȳPs)Z?8Ոb-#3y3H1-F+Fw ;6&mHUt2aHz7{J^O*~X$Gb 9ވVPD Qo"-N긁ع*8>a\pޘ-mSY@Q'6--u];x9 7'3*1cFy&QPjdpc*$؃wkZZ#m#v 뭺(ćcFnk,!Yh?/$3JLCW`G1k#IFN,LgD|" % Z04EZ#:{7;X# !|FBcz:V9c ~Q^p:? 8طQLg{ܦHpNCMN[<4xՆ:ه_]$ዠ~6kBNNUw 7EI|/0$;`ᓴF44>A[j&+yUXG hxVOZIcR$cgS!Pm}gY͗G|yo'`HNF&k.=a87/QjK rS\v^G!pZ|.DW6PԌcV ]SwOR,\[?Cu.lKƽ]YnV4-CmZGG؜YQ!C|3r޹*%}[8g!%4}Ի)& ۰o"U+5\i2B|tXR0+DNfkDP.Bއ"kd5@F@M3LT{ZyªBYMJ3rۃ`5s@8HfAD}1T[% !h`;PK-!CL8*iu!>[S ,r>ŞM{V u'tjKXXb!E! 8:2JY$Pit`(*(isL2qє1c@2_Wc){H[qcCX7m۞xr7gcx;ݓ)㾌qNu w#%kHHd,bݳ55}g>ӉX^9u#ELi IIOWX3>!RU\ȰѬqEw0@/ѐ:#U6E(M3RRk/ij{Oe!蔅#coK﷿&nT>nf7^Ba-܁q-, iqU 9U_$|bUQP"n_dvh[Bi&jpy w'Ȧ}`px~qvRV ߿5nsGjh7!nv! y47liW*.E9ƮEQ7 B[w/_$i 3Z7֞8+ W+Jw:lߨ:^hEswAfd\Mb7n݇!fLwwp\$ZAƂOtL*x{'`L뗤c} s"2+"mE0 ڊln":FEt!iDՒo3k^;Z&[ eZ,6,)/O lg :>K-z-<(d֫1e]zZn%yG+Q*'kbL2MeIvzǿR%^# Cʜ _#n+&A2:Vw+f !$*bƁ4N))xLKK+; 4M~7Raih]MN!B&>ǩR>ݯί˛߈@TS4ymŃ?e?i"0 mjB6Eo9=% P4SJCd#Ycr HXuVnl}2Z!X6PnoeSBP4xcx0qtL2$VHNz ͱm); +^ΕBPmg[8||? 6#Jk!JIV|i=AqNmxm}Q(eM ?@|1Cÿ/}_oxxu;lڲbى-ņyUXyq)F"2eu{\>Ǣhlv;Uov}酱2)qUrjohnu}-sn;aTmJBf'f<ގacVm۪"fu!y}au94 1l 3~ۨj3;cV!ho?g Sm6,>Kg$|Lgc|hWV<~ :YwCydwfQB IZ\[05 + p{65ܨ oIl[&C߻:\= X7J̰P5F"P6)v~X]jE%i>>$$ gx iÑ7DlHaB"z?4$/ük`}NNRE"uj죍ey<8+/CUX &&bHּP:jٹsymqY@Ԏ넰4䒪i#B2%KI/'>L[qE5!гR0ڌZ)oD^ )`]`L{;-ҢLꮿ Օip]sWYz z3A)_50 n6~vp)sm |%g8,8(cj`!_R YJV}EbsroFW>P_lƈ_A5jܙ?d["z>e>~ dMʠY×T" xU$L۱XYf-(;ԲR h [JFWFY14,~/(}W*Am. _XbA*"M)_=Q^zqH<6Z h@{u(-Ìʋ_u8KhAr9(׋!UQj;pκwj_V4~B/Bx ׁ44Bqu0ͳ1|].WiUT-y.v%>4poG`,^h$lF=V&bf]FW91@ HzUAqs SHhϜ(9^} }皩7U%@}dʙ DrT:yxԸeR'Il#֮~ukC٢VX07UGvZ׿ 4Ձ ]'i.wzIB7kW6&, _/ V# &2cVuU~?}d/:ٞ   '~塜tsa:p~$nKV}wQZ&oY(OhX2Naehs*NfI7{B@G#N!("Wإ鱹(KϬwě"!dY덩P UqsVL2?^;Ԫ?7)N',w&!VD W<n (Ʉ vDѮ|)v<ܐcMW^?L%y535te T +0dK{uңn9oL! M,pKP1?O ')Ԥs:X,L@$p[Z: ՏkÏ2isلܾƑwP|4 b^~>p\ۉTmb+%8vL͗-Цe@S1`*x]znK[,\ԇ kޡ("(S. ))*~[WU/Xo,Kj0ZTj |-KbU r~pS~Oo>.Zs}9'+=kU5>J%-P4տ]Ly6%eu]8Y΀LĔ2\EdkR7z.{3ihfܸ2 \ߋ$21냐2.ACvZGz#LewK!3ͿJ1ؙF"!eͯ͝B3ىlKtpqX'ۭ{~WǷo]P}I^;ѫŹ 3?Q4 v& $S$\Ik%]-{fOԣDz(O;ژ{_GMݲwB WK"v(dO?vo_$~PsNNGtcom* Vc^(j[ ]`z^s_0\$G,rZUi0S^p&wFx;G"i$蜐 ͩy?HP)o2~tCj}N f~f=tKFkRRR {+_BّqT[&z0-tqЗ/Q7UhXO zoUw/@{ՠwM~u+i9=\x~znzxhW)! ;[`o3JmYA{kx?%sz^O;"GV|ݓ3= p"B<<2 xcۯ̀9X&sݙD] I'#,,\ׂgC(, c;rm<^d_ڐ؞GĄph]d;SUє%ӧ4 )YsKZs@~0 JL8P{aMfqߏ% `vphRW)[wǨE+Ej>vtJ  2oyׁY =+Ja1r a"F2t3Pe$Rׂ /ƛK˖ʁiG]#3CRg$q9ckW{8ҦT |.&4.lOO}NY6X;\Gn23 1R435V|RB9`URgQbҾEH J5aoH2Y(Y0_$`X_jġ:tџn2}h1F2\Ubp[3Gj~>Y'ywֲPPGU1}!18*Y ֏Yҹ2IOCvܹQ@&+nǗpQcT29xJ@sO`>9@|됺:/[OX%ƾ $ֲT3g&o + 2?I2sz*&jlW:U Z wuAnC FF1^ֿͩ@4"7&e!M҃hÓPNM3&./),9&Es1:3ˏcw e1+ضE]/d^igK Я^ kfؓ"I,9n‚pk;o dDZˁO<.{Ba!' MPޤcf@=2bk98^[Nڸ F f<6sn$C*C%'-?- B׈N7މ~ZcUNwT,>l>]ԞKԋw3s O/~UZxOu NShl 9)+"?Q@|ٰ,tcdIMi)@os=)e)l9+Dd7)^ȰrMٕJS! A8۞K%r:Ӣ'ҩ\Đx]'sIVn5g6a*.X|c:Bc]9ӿ 8kLNdRYK}YM;?=VhZDt=MjQ 5$J9)VZ,mltQ@a79f,Y/8_R7[ Ul14T+K8'u4(zYm[D0ß[妛Ԑ0BH ??+mpF$$3b ˿jLYge։V@{P\-~PRN)DrjB^IzTB ˮi^6NՄ,W!ۆ9|l&1©1 ͫX 8XXȞ^9ĵISLݬ)}'w#,S]1mqA޾1M!cW:Jz[u@v?`3l+,|KV^0d-+;:{ 82ܟSyjZ*J!1{ ,EаGo*"RDp{@٬ .B%ZkI8=(ڹ >#uN:oóXNS/3{- mdH='dgWs΃Zv#0LVˑn;!brEzӴ.C˟Q{2Յia:4On)Sqɗv58RɸSKٌ Jg[oja7maWJU-⾎ ѱX73k4MVk]dZ;O}&1`pIHƂ'w0H}8I[?lq255b91̃EBHg%C4w\ !t qdUt>b|#]ZN5Ƌ.<:#ڿ XF(qq>(ĕ ~eHN@"=f1ICƄ!hNz撝յɯ T9bMNϞ 8#~ipykWvhWFuK0FD|()NZ$ 9z&6ǐ2__>;LF G)tJIDD!(%i9khKp/c"fy8_5|VPVd.1Y) ( :G8:m t̰Ԍ𥙈ucĐHm^B0'8+RiD,/{YZ=eh\o0*|i^i㭎 [y3z@ԭP,1XQD kaaBGټbȺSxyA>dy-(X_ *Tm׵Gx^P@PoUJChk^B KHv[c[Nʕ$k-Q/qP4.4_nrwYI 9J g)Щ'˃Frm)ZIhbہ;J!B*N 1CCZ.(1 ?˸aڸ& b|(kI=-'fBV_QQ~enS?3j]BO1 ߑ'V}+!vĴaWeq XaD8 ثe'uG:iR_DaI+Lc%zj=\kY$aD?RK\&81bIld,2q0u& qϟ&*c8!†y} \;ܳ0r9?C_,yyٛe/,:-} aDĖ#T(9E`(ȳPs)Z?8Ոb-#3y3H1-F+Fw ;6&mHUt2aHz7{J^O*~X$Gb 9ވVPD Qo"-N긁ع*8>a\pޘ-mSY@Q'6--u];x9 7'3*1cFy&QPjdpc*$؃wkZZ#m#v 뭺(ćcFnk,!Yh?/$3JLCW`G1k#IFN,LgD|" % Z04EZ#:{7;X# !|FBcz:V9c ~Q^p:? 8طQLg{ܦHpNCMN[<4xՆ:ه_]$ዠ~6kBNNUw 7EI|/0$;`ᓴF44>A[j&+yUXG hxVOZIcR$cgS!Pm}gY͗G|yo'`HNF&k.=a87/QjK rS\v^G!pZ|.DW6PԌcV ]SwOR,\[?Cu.lKƽ]YnV4-CmZGG؜YQ!C|3r޹*%}[8g!%4}Ի)& ۰o"U+5\i2B|tXR0+DNfkDP.Bއ"kd5@F@M3LT{ZyªBYMJ3rۃ`5s@8HfAD}1T[% !h`;PK-!CL8*iu!>[S ,r>ŞM{V u'tjKXXb!E! 8:2JY$Pit`(*(isL2qє1c@2_Wc){H[qcCX7m۞xr7gcx;ݓ)㾌qNu w#%kHHd,bݳ55}g>ӉX^9u#ELi IIOWX3>!RU\ȰѬqEw0@/ѐ:#U6E(M3RRk/ij{Oe!蔅#coK﷿&nT>nf7^Ba-܁q-, iqU 9U_$|bUQP"n_dvh[Bi&jpy w'Ȧ}`px~qvRV ߿5nsGjh7!nv! y47liW*.E9ƮEQ7 B[w/_$i 3Z7֞8+ W+Jw:lߨ:^hEswAfd\Mb7n݇!fLwwp\$ZAƂOtL*x{'`L뗤c} s"2+"mE0 ڊln":FEt!iDՒo3k^;Z&[ eZ,6,)/O lg :>K-z-<(d֫1e]zZn%yG+Q*'kbL2MeIvzǿR%^# Cʜ _#n+&A2:Vw+f !$*bƁ4N))xLKK+; 4M~7Raih]MN!B&>ǩR>ݯί˛߈@TS4ymŃ?e?i"0 mjB6Eo9=% P4SJCd#Ycr HXuVnl}2Z!X6PnoeSBP4xcx0qtL2$VHNz ͱm); +^ΕBPmg[8||? 6#Jk!JIV|i=AqNmxm}Q(eM ?@|1яۢ?F1tKwяۢx~oۡ?sܓ^ƥXUPjxOkg'ʁ0uc9+S%Tq=XЁqܧ̯}4FqmXeE!ɺ >ar*q*dٱܛ)(,WgL8NK|J㟻$nzޟ5tHoUSyHoF}ͥY|[y?߈q/W~Z)1&(܃~&U*߷շehԉrv9qlC=s"܎Reײe^wu=mx8dmmQC .?3>Cm@q$ 4Kp~\ t>B"Nv1ֿj2s/g7cwb‹h)*|8_]AʝX㐙Odu~vA&Ӧ^duv>+ h"L3 ^`|ڕSZSz@|n$13qIaC{DRk u7X0؁yqXST~Ll(YPk4 (CZo'2r+0)k2>Jq|Eh4@sqL(-#ܳ3\t/9p㞧K53:t.3O$TM0KF<=Q?ͺP-N+ ;u|o'!X}PosELe \m;  ?W"o`yRKalLx`hru=Zb @ɐ#ϼʂ@] u479Ў"'Gd`88xP҄ݍcN]͢c@uCf{S/RqVJktgpJs a3|ysUyBxw'?4NP:k +b|E@\ e47JiXV?nb}"!~[~ƦI'ѯ:Cg%2qc9/_ZTvc;o2ɥx"K6+T'~Gon3+Y4< Ib0?Cѹ+QW,1`|4lDG<#=Tb2k(H`yAgQ;݈/sg-70Rz9c}\.iwnhx km}*n`ՔSE7 p}||ҋBۇ,,$Kb}av8lNրjMwм,GFl_qbce[_]_ *wv&ܛ b$SðVhD7,aoi|,f?Yce= Ux&B8[~_=mQ(c2UR /}JV4Zrv)f4$ B^Δ?ggGMɧ؟تnr=M 8.4!ˆU#ixA0Q 33xtۼPL؁ GW y# 8@"$Yߍ-)6CO|=,BX6`el+y+ZaW*G{; /!"J+y-|eie]LENcC8+#RkL:Ce)(qCM4gR_.BETL:.&+|>lth3F!]غi \Tpgse|9qg3?!R0mHpeӅ՝Q"\50mZ~|`3H8uvއ5!> ߆NhA΍[`#C B 4n^uu򩔫Bq"")`1=+=atz+} F$CșE_x"GJ&"yQWď J-=ܷӚZ zOW/%)Lfa=ޒTtCd2 7j&mӅ՝Q"\50mZ~|`3H8uvއ5!> ߆NhA΍[`#C B 4n^uu򩔫Bq"")`1=+=atz+} F$CșE_x"GJ&"yQWď J-=ܷӚZ zOW/%)Lfa=ޒTtCd2 7j&mӅ՝Q"\50mZ~|`3H8uvއ5!> ߅Ngo_HO*1nns)郀u uS~ JO+Lr8p#@JSl-K=2+.!H1wAp:0m}ʕU/twƔ.SB UOS> Kn#6>J]% сu~u\P3ٳH%59畔,|ȥ?WS_5͞.mpwk˗ߝ~w$~|~\|\.h5xHqD [姪I5_FylI6΢o6sHbO:~Xz8- qg5IKCX" 3jF懀mA]<!UZK,_ftGeI_/AT?i4I)|@SMF^GoѾ,n$[` ۷[lIxN/ Oi|._hrUʅEn;C1ѴV+l)U(頋k qy8ee7R2*1G)'a+۩]͐re7#M$8R8ƹ`W-%e&z.k 9K3|vtU#hqu6^\+Zߝ~w$~|~\|\.h5xHqD [姪I5_FylI6΢o6sHbO:~Xz8- qg5IKCX" 3jF懀mA]<!UZK,_ftGeI_/AT?i4I)|@SMF^GoѾ,n$[` ۷[lIxN/ Oi|._hrUʅEn;C1ѴV+l)U(頋k qy8ee7R2*1G)'a+۩]͐re7#M$8R8ƹ`W-%e&z.k 9K3|vtU#hqu6^\+Zߝ~w$~|~\|\.h5xHqD [姪I5_FylI6΢o6sHbO:~Xz8- qg5IKCX" 3jF懀mA]<!UZK,_ftGeI_/AT?i4I)|@SMF^GoѾ,n$[` ۷[lIxN/ Oi|._hrUʅEn;C1ѴV+l)U(頋k qy8ee7R2*1G)'a+۩]͐re7#M$8R8ƹ`W-%e&z.k 9K3|vtU#hqu6^\+ZߛUddsZq鬋۴qm)6興g{sح;;$pKo[bAkq$oP{*f&(^ˌ셎DM4哪j8~Cwlr0Ȟ lCJKWJ2?Yfz"OV#nx^v:\ uW|U!W$tux>؍?6F e=X9_|q\ή8#~K!㜖6%@˻|G&(xcBS[$,E@E{ MXw B/͚`.\;U6Τq)uoȬB'ߟMwd~0J UN%,ܫr繘~~l1 P젯6U,oY>ڠDw1#ǘ$}tƬ*'|80edY[ɜ 7T&.8*$YbȘ~S#b+&xEpBxͻ+ZIcN1.2ZtkED6 uR1_ MbrPx Ԥt/DLBsiSaןzbL$| >Q- cDhm&Vٿ6PX|b(t@)y{T_q\GFŭG3sv`nAͮ+i` ^b۫?%/i϶^ƫS :{x Th35Vo4ctC|[B!#+3҄>Gj;FmEU5vCMS|Tg Iۇ$=mC7QMFk:0ъX)/w\n!t*3R 9:] !`}Enb<5KKF܅h~Q/bH%x~ܴr;=Wh5s ! E椨 S]1F_Pnj|lK%[-ZҏҊ3OGz" Pͣ+ _{ԘxJ}]*jor|o7 yF=cIIϻz:ã{>87iu|+aVq0."إe fM+Q(f,ΑMݍL{mԽB?pLKg0r(2:EATmjpg4gyxu&*pwFE?{Jf3~doqp0!ʢH|[;4;ɲIyt\gO3Ԡzzsj ?Ů2',&vF_fPV0WH7 fNS@,SH|p:_ҋm t1Ļ[Quծ5(,lFv%y'`"Y0A~\GnՃMC+t]M” g T--wO "v]j9d}jKHC#D~Y:7@5N͝U>zZ fFY4nzS~s7ˍGWb[vU9ڠDw1#ǘ$}tƬ*'|80edY[ɜ 7T&.8*$YbȘ~S#b+&xEpBxͻ+ZIcN1.2ZtkED6 uR1_ MbrPx Ԥt/DLBsiSaןzbL$| >Q- cDhm&Vٿ6PX|b(t@)y{T_q\GFŭG3sv`nAͮ+i` ^b۫?%/i϶^ƫS :{x Th35Vo4ctC|[B!#+3҄>Gj;FmEU5vCMS|Tg Iۇ$=mC7QMFk:0ъX)/w\n!t*3R 9:] !`}Enb<5KKF܅h~Q/bH%x~ܴr;=Wh5s ! E椨 S]1F_Pnj|lK%[-ZҏҊ3OGz" Pͣ+ _{ԘxJ}]*jor|o7 yF=cIIϻz:ã{>87iu|+aVq0."إe fM+Q(f,ΑMݍL{mԽB?pLKg0r(2:EATmjpg4gyxu&*pwFE?{Jf3~doqp0!ʢH|[;4;ɲIyt\gO3Ԡzzsj ?Ů2',&vF_fPV0WH7 fNS@,SH|p:_ҋm t1Ļ[Quծ5(,lFv%y'`"Y0A~\GnՃMC+t]M” g T--wO "v]j9d}jKHC#D~Y:7@5N͝U>zZ fFY4nzS~s7ˍGWb[vU9ڠDw1#ǘ$}tƬ*'|80edY[ɜ 7T&.8*$YbȘ~S#b+&xEpBxͻ+ZIcN1.2ZtkED6 uR1_ MbrPx Ԥt/DLBsiSaןzbL$| >Q- cDhm&Vٿ6PX|b(t@)y{T_q\GFŭG3sv`nAͮ+i` ^b۫?%/i϶^ƫS :{x Th35Vo4ctC|[B!#+3҄>Gj;FmEU5vCMS|Tg Iۇ$=mC7QMFk:0ъX)/w\n!t*3R 9:] !`}Enb<5KKF܅h~Q/bH%x~ܴr;=Wh5s ! E椨 S]1F_Pnj|lK%[-ZҏҊ3OGz" Pͣ+ _{ԘxJ}]*jor|o7 yF=cIIϻz:ã{>87iu|+aVq0."إe fM+Q(f,ΑMݍL{mԽB?pLKg0r(2:EATmjpg4gyxu&*pwFE?{Jf3~doqp0!ʢH|[;4;ɲIyt\gO3Ԡzzsj ?Ů2',&vF_fPV0WH7 fNS@,SH|p:_ҋm t1Ļ[Quծ5(,lFv%y'`"Y0A~\GnՃMC+t]M” g T--wO "v]j9d}jKHC#D~Y:7@5N͝U>zZ fFY4nzS~s7ˍGWb[vU9{WV \3tXx>ScGYIa5uO^}i e9E]sf=ޚo.='x74zXʞ/υ( -_f٬bTH>#GHFw6"bZ?#G0~CD'y8dRZdTHK4yI e5:.7J[W%rTkጧgEg sfb)a̋sjM`v +_"M1۟p Dt#4sgSUbϮ!r >i7o|<'/M\xdH[~/D\NNxv. 2+&@+˗:q/n)Z4"iat%̾ `b4;^Z{^ܾM8{0scX&BS2QXO+Oy1edjdqQskD |NR9]ivs `oi! 4m #P|k.AgV9Z' yC GˡE8ѵ19Y4{HjOB<\0~_?,^F} gMDdУ-؉*٢~ʳglzBzSB.l1ܩ d%[XBflXF'TÖ?lK Kl1kPB>CVЋi8kj?NRQQ´ԣvB Гe+yˍ-[Nf.X3̛ 0N,.$lsV*(?Ayt{jpgaBg0Uf<ش7l{T yJoʏባIO]qD]2a(b|V;!85Jʲj<~>rVE`j)ר*q+آ}hL2uJYq]3n2@vKҏ^:^{l0|%a󧫛<hNdVqE.$F::ⶻ+llJ`a}ʀPd5qxhh'K=%l7.eV ̉!*1[1Lل rJ֕m`^aIߥ#<|$xe,  sJ4-5yXŠx[dxL'`XEYi SpȐtEDkZ*_^b[TVNBg_s3VBx&ZpNb&_#" v$JtPJ/>㗃G!";Y48zpߧ`ZO%KDֹ{Ā4 $>pTzrY3WXe }p8 p1~m"dv44O79DTOir &[v&_{ZQEȌWÔ.%)҂Acn؅k׹ߑ_!{rdFwآD- { OM0uu{YxFbf;=K;s>iDX!ȼ.a%7x]ڣK* d,TnuWV`2է")Ec;$٬WmWi9jIgC.'~h'>o6$G`{`Bc+ŰJ5OMI}&Ӛϣ5"rCa!,-0 W \~Wy=G/kfې;ɲ2sk+P\Jhdg7fi-JQSXZpn}l;Zـ MjNn Wh3( (<VokRImv}r:usӍ*4u8FdS-y:@2W٨~Gt] xevSNVQ5*O`Kv,tWE*#\A p /L/nl*{uPtX}aT\8 Ysӑ%}:L3%z'N;yyk&.Z Q~u`U r7R7N:NeLKFvyk Ab*ad 钖HYf^K0}df 5ob)\o-xaOG D" װH3i▘@G)Kʋ4hJt*ןlR(_PJ;%KELk. 5?2[դpq}r.Njw(4^nyX厪,AYӜ4+b0LLm1y=ef옱:r{X??&2acO(]ϕ0dbjz²GЙ^q{0\O!DwG]_Vhhjnn:-u#p1?!f,4t3sL i ŕ}O^D0VFN{}#H;݃$<-?%#b Cec\ ]ܶOK-wnIMvefv7NGOq[]`p 7'~L7[?u2H Mųs09hCe>-ߣ$sWփt^2 Z+@'l=<읅K+o#6G}ڑct=}o B~Qi0Uxy5@'r_ӑ+)mԷn?9}w(gȋn0'X|ea–wTڄnDxasWWPJ72#rRBVy܄ mjzGKۓP7շq@&@gR5 xU?I4Um :qV+g/Lwu^dG+PY4(68N9qJij jrYŦ]dl,G;1}l"G(T^d&Bf!X2q_?5< KY⊇"BNL7͈u4R&3.10&4PuMJ!Oo .%*[MQx#^?7WEn`[U'Gްk;)]G/lCrXwxQ"`IH/ظE r%]ˁ첾1tÙ 㤙Q`9{OF[e6[.*8пln 7X P(7ğ8R#e#,GŊ/}uB8O#g(Ϳݤ{saoFC ķ]){e٘td<8Oy(\ TE|\Q<q Z7gi.g 9>O${ ;NCEv.yw]tӪ ޶ĐjEǣ:ϙ#WT-0x.wCSiߛ;B46fG&߶[ ep'>CJhUP>BA^[RT/G}ͤ$\ʳ\}"6"7 AeG1\LAHrSP -k! G ò8xߌY\͔hQ5e>%8j{zt~)S ?!]28th գ!Tv/T [^!U /jCٓy B%ګ5oX ]% %?gd2OJeӰk8Vcc#aFeCH_q^URU 9a$,4pnXꉰv³dE &!Dr#/K᤮Q nD^tLEThgS4  S ـ4(]l?itN4ceeTc^ItWP 0o'zLhK}lއ+kbS!l5_̝`PťἽ&9|^_%҄ YjRJ;W"݂Oy~[W-\#MNF;Kt-QA-gt_:kw>n(.g$t}W^:pv+S]9y=0I {3̧?aA`PF4fAi3Gy\{8W)Pcw=ˑCL^9_ZMzcg![A1]L%{ U#Xaцvjj]6EUl,<96l u_&zWKhL\GK m3vvDZ7Uox6Ypw%fG'Æ73i[9ҬctSSM?y\(z1"FjMcNN7: 5C Zx&E. Wp^pa#B uCݎih ,o`.IGhPkEG~<4@4<v_R!˫< {FQԡٳ;?)2L۰'Gi>MC+Crkv-B<-@PDYQkaZq4!p#?B?$5xl39@ٽ{͎T+1 w; FZ*/gKU :S_7u7!ݘFCQؤe!?In>h7g]P T+;?#cfx|G"%Ɲc*nQ9Af-m"c٢~ʳglzBzSB.l1ܩ d%[XBflXF'TÖ?lK Kl1kPB>CVЋi8kj?NRQQ´ԣvB Гe+yˍ-[Nf.X3̛ 0N,.$lsV*(?Ayt{jpgaBg0Uf<ش7l{T yJoʏባIO]qD]2a(b|V;!85Jʲj<~>rVE`j)ר*q+آ}hL2uJYq]3n2@vKҏ^:^{l0|%a󧫛<hNdVqE.$F::ⶻ+llJ`a}ʀPd5qxhh'K=%l7.eV ̉!*1[1Lل rJ֕m`^aIߥ#<|$xe,  sJ4-5yXŠx[dxL'`XEYi SpȐtEDkZ*_^b[TVNBg_s3VBx&ZpNb&_#" v$JtPJ/>㗃G!";Y48zpߧ`ZO%KDֹ{Ā4 $>pTzrY3WXe }p8 p1~m"dv44O79DTOir &[v&_{ZQEȌWÔ.%)҂Acn؅k׹ߑ_!{rdFwآD- { OM0uu{YxFbf;=K;s>iDX!ȼ.a%7x]ڣK* d,TnuWV`2է")Ec;$٬WmWi9jIgC.'~h'>o6$G`{`Bc+ŰJ5OMI}&Ӛϣ5"rCa!,-0 W \~Wy=G/kfې;ɲ2sk+P\Jhdg7fi-JQSXZpn}l;Zـ MjNn Wh3( (<VokRImv}r:usӍ*4u8FdS-y:@2W٨~Gt] xevSNVQ5*O`Kv,tWE*#\A p /L/nl*{uPtX}aT\8 Ysӑ%}:L3%z'N;yyk&.Z Q~u`U r7R7N:NeLKFvyk Ab*ad 钖HYf^K0}df 5ob)\o-xaOG D" װH3i▘@G)Kʋ4hJt*ןlR(_PJ;%KELk. 5?2[դpq}r.Njw(4^nyX厪,AYӜ4+b0LLm1y=ef옱:r{X??&2acO(]ϕ0dbjz²GЙ^q{0\O!DwG]_Vhhjnn:-u#p1?!f,4t3sL i ŕ}O^D0VFN{}#H;݃$<-?%#b Cec\ ]ܶOK-wnIMvefv7NGOq[]`p 7'~L7[?u2H Mųs09hCe>-ߣ$sWփt^2 Z+@'l=<읅K+o#6G}ڑct=}o B~Qi0Uxy5@'r_ӑ+)mԷn?9}w(gȋn0'X|ea–wTڄnDxasWWPJ72#rRBVy܄ mjzGKۓP7շq@&@gR5 xU?I4Um :qV+g/Lwu^dG+PY4(68N9qJij jrYŦ]dl,G;1}l"G(T^d&Bf!X2q_?5< KY⊇"BNL7͈u4R&3.10&4PuMJ!Oo .%*[MQx#^?7WEn`[U'Gްk;)]G/lCrXwxQ"`IH/ظE r%]ˁ첾1tÙ 㤙Q`9{OF[e6[.*8пln 7X P(7ğ8R#e#,GŊ/}uB8O#g(Ϳݤ{saoFC ķ]){e٘td<8Oy(\ TE|\Q<q Z7gi.g 9>O${ ;NCEv.yw]tӪ ޶ĐjEǣ:ϙ#WT-0x.wCSiߛ;B46fG&߶[ ep'>CJhUP>BA^[RT/G}ͤ$\ʳ\}"6"7 AeG1\LAHrSP -k! G ò8xߌY\͔hQ5e>%8j{zt~)S ?!]28th գ!Tv/T [^!U /jCٓy B%ګ5oX ]% %?gd2OJeӰk8Vcc#aFeCH_q^URU 9a$,4pnXꉰv³dE &!Dr#/K᤮Q nD^tLEThgS4  S ـ4(]l?itN4ceeTc^ItWP 0o'zLhK}lއ+kbS!l5_̝`PťἽ&9|^_%҄ YjRJ;W"݂Oy~[W-\#MNF;Kt-QA-gt_:kw>n(.g$t}W^:pv+S]9y=0I {3̧?aA`PF4fAi3Gy\{8W)Pcw=ˑCL^9_ZMzcg![A1]L%{ U#Xaцvjj]6EUl,<96l u_&zWKhL\GK m3vvDZ7Uox6Ypw%fG'Æ73i[9ҬctSSM?y\(z1"FjMcNN7: 5C Zx&E. Wp^pa#B uCݎih ,o`.IGhPkEG~<4@4<v_R!˫< {FQԡٳ;?)2L۰'Gi>MC+Crkv-B<-@PDYQkaZq4!p#?B?$5xl39@ٽ{͎T+1 w; FZ*/gKU :S_7u7!ݘFCQؤe!?In>h7g]P T+;?#cfx|G"%Ɲc*nQ9Af-m"c٢~ʳglzBzSB.l1ܩ d%[XBflXF'TÖ?lK Kl1kPB>CVЋi8kj?NRQQ´ԣvB Гe+yˍ-[Nf.X3̛ 0N,.$lsV*(?Ayt{jpgaBg0Uf<ش7l{T yJoʏባIO]qD]2a(b|V;!85Jʲj<~>rVE`j)ר*q+آ}hL2uJYq]3n2@vKҏ^:^{l0|%a󧫛<hNdVqE.$F::ⶻ+llJ`a}ʀPd5qxhh'K=%l7.eV ̉!*1[1Lل rJ֕m`^aIߥ#<|$xe,  sJ4-5yXŠx[dxL'`XEYi SpȐtEDkZ*_^b[TVNBg_s3VBx&ZpNb&_#" v$JtPJ/>㗃G!";Y48zpߧ`ZO%KDֹ{Ā4 $>pTzrY3WXe }p8 p1~m"dv44O79DTOir &[v&_{ZQEȌWÔ.%)҂Acn؅k׹ߑ_!{rdFwآD- { OM0uu{YxFbf;=K;s>iDX!ȼ.a%7x]ڣK* d,TnuWV`2է")Ec;$٬WmWi9jIgC.'~h'>o6$G`{`Bc+ŰJ5OMI}&Ӛϣ5"rCa!,-0 W \~Wy=G/kfې;ɲ2sk+P\Jhdg7fi-JQSXZpn}l;Zـ MjNn Wh3( (<VokRImv}r:usӍ*4u8FdS-y:@2W٨~Gt] xevSNVQ5*O`Kv,tWE*#\A p /L/nl*{uPtX}aT\8 Ysӑ%}:L3%z'N;yyk&.Z Q~u`U r7R7N:NeLKFvyk Ab*ad 钖HYf^K0}df 5ob)\o-xaOG D" װH3i▘@G)Kʋ4hJt*ןlR(_PJ;%KELk. 5?2[դpq}r.Njw(4^nyX厪,AYӜ4+b0LLm1y=ef옱:r{X??&2acO(]ϕ0dbjz²GЙ^q{0\O!DwG]_Vhhjnn:-u#p1?!f,4t3sL i ŕ}O^D0VFN{}#H;݃$<-?%#b Cec\ ]ܶOK-wnIMvefv7NGOq[]`p 7'~L7[?u2H Mųs09hCe>-ߣ$sWփt^2 Z+@'l=<읅K+o#6G}ڑct=}o B~Qi0Uxy5@'r_ӑ+)mԷn?9}w(gȋn0'X|ea–wTڄnDxasWWPJ72#rRBVy܄ mjzGKۓP7շq@&@gR5 xU?I4Um :qV+g/Lwu^dG+PY4(68N9qJij jrYŦ]dl,G;1}l"G(T^d&Bf!X2q_?5< KY⊇"BNL7͈u4R&3.10&4PuMJ!Oo .%*[MQx#^?7WEn`[U'Gްk;)]G/lCrXwxQ"`IH/ظE r%]ˁ첾1tÙ 㤙Q`9{OF[e6[.*8пln 7X P(7ğ8R#e#,GŊ/}uB8O#g(Ϳݤ{saoFC ķ]){e٘td<8Oy(\ TE|\Q<q Z7gi.g 9>O${ ;NCEv.yw]tӪ ޶ĐjEǣ:ϙ#WT-0x.wCSiߛ;B46fG&߶[ ep'>CJhUP>BA^[RT/G}ͤ$\ʳ\}"6"7 AeG1\LAHrSP -k! G ò8xߌY\͔hQ5e>%8j{zt~)S ?!]28th գ!Tv/T [^!U /jCٓy B%ګ5oX ]% %?gd2OJeӰk8Vcc#aFeCH_q^URU 9a$,4pnXꉰv³dE &!Dr#/K᤮Q nD^tLEThgS4  S ـ4(]l?itN4ceeTc^ItWP 0o'zLhK}lއ+kbS!l5_̝`PťἽ&9|^_%҄ YjRJ;W"݂Oy~[W-\#MNF;Kt-QA-gt_:kw>n(.g$t}W^:pv+S]9y=0I {3̧?aA`PF4fAi3Gy\{8W)Pcw=ˑCL^9_ZMzcg![A1]L%{ U#Xaцvjj]6EUl,<96l u_&zWKhL\GK m3vvDZ7Uox6Ypw%fG'Æ73i[9ҬctSSM?y\(z1"FjMcNN7: 5C Zx&E. Wp^pa#B uCݎih ,o`.IGhPkEG~<4@4<v_R!˫< {FQԡٳ;?)2L۰'Gi>MC+Crkv-B<-@PDYQkaZq4!p#?B?$5xl39@ٽ{͎T+1 w; FZ*/gKU :S_7u7!ݘFCQؤe!?In>h7g]P T+;?#cfx|G"%Ɲc*nQ9Af-m"cát-BhHXv%Na!q3~,ls3R9j6hy*꿋^Gm%bF%s. f.umZom{\bO5{)OIz v$iSf4ر`3:{*B|B=VeXE9-rQh{^/2Yj.P(Up(y_I"NJN@[%%>b3ˎm$y~_BiE|IK8\ưu#1Kϳ,@/ {iHm..L΄E:\p$or2My!LajVZ˼JP^$wGF)& L ac!k| &e"c thּ?C4x| c+wIygG^RE]4BunaQ5nPfꎸEqSw aY e6{fZ!ߵiYGq-6uѷfVc6 mo׫W_tBTVsΤi4M^kK=m_v(LCbsη1.|dI{Ž5$4 H# L `ҠB;0r>.u"~ؘ#4=3Os\U>m Y5Bq .dE'f4Vj$qiGV VU5YRJbONNG2:,0E2s9+\瀏Ix| j3֥/?f(%qq#ǁj*f W 84{:RAP=lT^ *, -+hyv1TrD 1@1L'2yNonC&q;ˀ yd` \ 蜝X.m^B9X&hWpI*mT!_@eph8F9Q__01TFo;W u薊_dSBavz|y<ࣼeҌ׬6ؐuǤ!F% 3q*̇8 B O 6'tJӹB8k~Ү23jM vd |a[֯'$.KRGZk'*z%7H CʫO췚Jto&]P]m"rQ]0蛆YF62l8q`LwW@鴪LyDH"BBEGnq )w[:m0~{CwGrW/eY~|pf=phqL,vz⿃@ +] ͆O5P~QhJILΦ7Mɑ|Vh7?qg[nF$ 7D߱ ޓiu$1o1-勥7;kCOsB')y RU[0r( ƳP]}K:v *m+2:B׋9VGMkS r#5zua6Cзw5;zr÷^X7Uo>b:V_xT0Kf'T yVEQZνvbZ#i#~NjQL# yuG.~S{uŕqhz󼖇)F}iJ*YCu2 5w"ϧ1z>\H۞.6[OC[xy',4O>$tc}YyvOS`9_ey5gȦ|~$5Z0F# 'x̖Pɒ.Td*qg~ L`JɁZG.)0,4ߓ*^vXK1*H2R&"y)-3:e/~ CV?Å_^cMyMQaozQyF]Jk  \75Ʒ4yK'q$)&P<-s6!őwPX6G6n, X&oH}m%:_\kaT=!ecztHr]|7Ӗ@"2]P&^]-y"yhA@W+z/R!Jz/8٧j&dEaھ```5"ޓl~ ,JKzЉ[v1 7vݙwB|Z9ahzP(N%eQLy*&!3ׁ5Y]h!f:pa/M}/!6S %FPhC]I<$qSf\߲w#8upo &s֭64=%RTH D,ośAo ص.S<Pa96&jer^2p$XOLm 809 `T#!U#v^eW/˰2Gՠx@9 jge f. o5[F_ླB(3D۩!RlRRE)45D>1ri41І>eִN`sxZ8yѴoǽH?{&wt`/-'z7Lxet8b1R0!c/9Njк8/Uso-aΉnO9|h>N ELDUJT :f ʀ)|d.UkHQ-xY\5.8br/zu5y: ~ɳl$d$ς1AC `qaMмN{8kzc92*xB~rDa5QR^*!HW݌*Q3QڒjK0LThpWp s/AҊArNʊoCi$XR>@- S195~Lif?GU*vL&VՀ{RVr o;kޡm_' >3 f>:^%0t؜OYpnŕqX𰟭O4]"›_̹jjnl,ށIġ=@4a ^"!?Z1y9 1_h ұyrDfM蛘'q>*`RL͉};;;b&1=|mQ,UyXKp13eR(=NpC "Ybppm2\^Vhf2* >y,B jdOFy1(-pJr|7&8)WҘj9Y1ߒ:1ƱZ?VVrӍ^Īx-M&giHoÀ*=&7b+.np/TxS f@ʹs a[.LTSdQAJ\ѷ?}A1>Ōu ۽ɉYe8A+tdBY4l ?X {_۪.FL-^>$z PQ8<>kK$r1KҚ8:A.]CuPQq ੷2OϯRbғz0.ͽaɧjs} ӊJ u)Tm',rP ܨ؁bt{8HE0'@&= = pqBBw@\NɜZ5!z./kZ(gJq* "H#гbVRE yC.jCQ^EXsX|kro~[XR忇a _HEY%i)ы:4hI)i> .u֐:\N PyuTA(*&EVS3j ^"51IHZ)쵽V,"I[d*"Αz<:of bko8VF,JxLm @b>?YW>yɻ8p Ce cy.637lψq ^6>NܤZPC[V:r/GՆ3Lm4qrx S^tU+]B{9F`` בie }d8-= bwe L }N,f6 %4VlKA*:#( >UXǷ;4 J0p 9E6'5(unT58ЉQ3*5|_Cs*iPÛ9a(^8.Ϝn krB^[L_;{JfNJXAػ~'o@z[[01S_mS/+WLX&%ʙ!U(MVdRa|ʑNKjm3l j]FiATF"ޛKS?ѯ Z?hcꕙ 38P M FsY% ,!'ɴK l+,Ștne((nAUL|`z~/&iǡZ(Tj\Oi iDS;X,(06',{)gRxo)\\գ<@CgK +d$p ;V'DX© DenXN0uu+K\{[6(puaW챊yz- V$j%!LEv'H%_sӉ=Bvt`k@ƻ'&C֢W ӂ[Wə>eb:' g ]8^zk.9{ dAo㙝o$͆ܤj}FMzaL<s_k,COuMTOaY|+ɐ9.e!w^ ? YWZ2 q'uj]b; v! E3:Z8B ADʭ- )"ߓ ~Fc"_:lOD%1y=γK5g: sFpIX rj w42ҧCIKڦ81Edv&/%{qUh𼂒s6kd 0 SbY6Ӕ`_a%2OV=jK0'Rz( mkYwPy1̏}=md '^Q:HyO9%6+mvщ9#"⋜\X#nG(jUy氄@4#l' SҎ5 %[sw͍\og 8:?=WBLM!#IqOL~NGS'QAzf W!~qTL$A`{:E3H n{[/ }S۽[Ù`,jfNw>=6\iɵ6Y>KͱZ9o[o58_J:hL "h6'x1vph Mb-hW4[]K"I(!-~>Qœ,/%!#NUv'U}=`* z>{"a 4I2֫nq|t[L*"FTI}o8n}r!0 !;T&BV<р%12_ фb OU (fubU{d_AG2,x \T[H2|v-08k} xbǁgsdq{ 44zPGװcءvu}D2BC*>n+ou 3줉`NQ[l7kj `.e$sO >wb~V[%Jhs}џ*$)1_wy4ȫHua!2Ќi)4x֜CDֱ( ,p;F=.DT2y|Eϓ>l6L).pv~GW`r6'TR_1%wtL7p5n*mL~qq}Nt'&?sPfgN(R;qGu|#EE1&#͘܄r1n8j1_ltdA[uoj\Wms/ˑe<3'˥^!]rR6b2qbu?^%N_2" "3V(>n_g)BsZ< ?M/q0@j@ ]ӪO4m$Tv4]p[YtI<^nBc DNd5@&,hl; @~.8nx ()ZǷ nz=- uR խfkVl4J{ w1J&Df% N:EBOitV̶/3L~ٿxWl F"RgV%}G/ǏuqlI3ptn71'1@\L ٛVyD%~xǶN=MXbpgy X-ݒ^VQ;BntN@H|e{"_اlG6T-XI [1nVz~-n̘*fJM',$Mt A7&E۸E #w~˒kװ O"iF]54ZQN?M1#pʩ4Vج~oOo`Y~ez*V/d+0C"5AH$<;&6WQ`rq5AMkKx qCmD ꮤ֘T|O K9mh$HaΉT h=<+.}Lu5\/t1W'&gI# :?nfzE,Y;lHJ<fc^ˍ.20.Z_jJӦ0 ҈ q^.lOQ@oC  1.zFs҉m^ʑH)FХ+ p%btO2ޅx|:;B(މDfrSc<痏f/9;Ct٫2`it7Zk6c+ *x[sDX%*'?G)ލT_NdRCH>9l=Ozg\=IW[VxA)CII U3&2Ӊ1EÅpw@A)^|q5QeS|O{' yƜY5tJIb5 MA 9&̴.AYg\+SsCjQ@À㛃*Ye23+ljmҋtH5 `a@W9U@?@^uiG@%E쬉:r$뜸u",6v/oJN{\x. @D'˲dy5fEҶ?9tBN'cJK]:͟&ʅ?b-#(AsBf&ɵ!|}y|(jX QC Fu(G(O4t%Uؚ9DU5a|agL&WLl$]LBvYtGy _Q$[w,!.3@AVÔd~2O =S@e~&an^-{;:5v}TlHnYfաnzqR@5&)-$h +9 0ac^O?AćH&:^wXl$O+hMA8!+`iUR䊤έ&Z)JL⻇=ݑy}6 蟔 o/1hJGCg i仵JeNbeM!|,8xɜ(G}'-? P㇡Qe0usȟ R@7 yKI,-hMsyyk,$ngSՕL7{2B ^dɏ*.zp9r!~q\AkW߂ y61:o(PS;Qa!gݽb3 U5;B4(8 O}_<"\F*N{XZ31?#{$B ݒJYmyZ ?8\~v3-'h`n 2Byo5=́A> qVx&pOWAݠ %rvso}Ŭg|9`+?&0 %5-FL=إƫuϡN*rD#j Cjm|4nPM7᩵%v.`qrR%PUN|[JS_T=/>E)(f=f:B%1CfS_H:6G׊G!,kZ^4MxQwty8 Vi3a|P:m$"4ǣQNO1n^#'i7M$߰2?k$euRƏEm/J<k*;^c0LH#+S@]%.7)ooNF͗1׹h)EÍY0Ь^/UL)aK,v[ تө;\x7sKcpa G->C 6Q]Bۼ^=D a%jd  |,αVJlIljHeռh\T 8;HC| |n6*3I tc1")icZ$rEw0fՆe㧤21gG/Rj}fί@^{ߟǃrbw2)T6 `e=F`ž EF>2,#4f>d}w}Ք6BO=?2詄zтO(~9 ͬ5QQ4HUIw3l^[?J0rhMt7[Ƃ̵όF&OS,ӛ3ޙfcɁS8 Ȫ&:cvo4Lk 6lb:hKfv|wƭO2Lڶ2̭ ~,WIEiա[~pDzs o}d)RB'PJiO9~vGKJj,Gy20t{{KL|P}|̩ kFUBXO?:4#rܟ.ބ$VmV0HJ_:3Nhu4Qަnϙ$76#ğK8x!D.N3Ag`YP"W͌3W# UitRr)$Iͺq '%VjvsZ$COВ Agi^J5jI` 9,{!բR=" u]Oq Bӭ˅[+˜̻Z둚κ{ |{_Y9,>;*]eX%9ć)ͷe,Y<1.v)()Kgn|Cߔ3GB*Fq nᏬ~ ְn;)VvWo;'Ic =: v$m51kߔş1(lixjBԇ; >Wgq(j(q?5H Ex\Y)=(GLk!H э nA!|Wh6gAIsD'f\t !lK5olɛU3B~5DqP?ZzR4 'M/6qQ _Hı%b:V_xT0Kf'T yVEQZνvbZ#i#~NjQL# yuG.~S{uŕqhz󼖇)F}iJ*YCu2 5w"ϧ1z>\H۞.6[OC[xy',4O>$tc}YyvOS`9_ey5gȦ|~$5Z0F# 'x̖Pɒ.Td*qg~ L`JɁZG.)0,4ߓ*^vXK1*H2R&"y)-3:e/~ CV?Å_^cMyMQaozQyF]Jk  \75Ʒ4yK'q$)&P<-s6!őwPX6G6n, X&oH}m%:_\kaT=!ecztHr]|7Ӗ@"2]P&^]-y"yhA@W+z/R!Jz/8٧j&dEaھ```5"ޓl~ ,JKzЉ[v1 7vݙwB|Z9ahzP(N%eQLy*&!3ׁ5Y]h!f:pa/M}/!6S %FPhC]I<$qSf\߲w#8upo &s֭64=%RTH D,ośAo ص.S<Pa96&jer^2p$XOLm 809 `T#!U#v^eW/˰2Gՠx@9 jge f. o5[F_ླB(3D۩!RlRRE)45D>1ri41І>eִN`sxZ8yѴoǽH?{&wt`/-'z7Lxet8b1R0!c/9Njк8/Uso-aΉnO9|h>N ELDUJT :f ʀ)|d.UkHQ-xY\5.8br/zu5y: ~ɳl$d$ς1AC `qaMмN{8kzc92*xB~rDa5QR^*!HW݌*Q3QڒjK0LThpWp s/AҊArNʊoCi$XR>@- S195~Lif?GU*vL&VՀ{RVr o;kޡm_' >3 f>:^%0t؜OYpnŕqX𰟭O4]"›_̹jjnl,ށIġ=@4a ^"!?Z1y9 1_h ұyrDfM蛘'q>*`RL͉};;;b&1=|mQ,UyXKp13eR(=NpC "Ybppm2\^Vhf2* >y,B jdOFy1(-pJr|7&8)WҘj9Y1ߒ:1ƱZ?VVrӍ^Īx-M&giHoÀ*=&7b+.np/TxS f@ʹs a[.LTSdQAJ\ѷ?}A1>Ōu ۽ɉYe8A+tdBY4l ?X {_۪.FL-^>$z PQ8<>kK$r1KҚ8:A.]CuPQq ੷2OϯRbғz0.ͽaɧjs} ӊJ u)Tm',rP ܨ؁bt{8HE0'@&= = pqBBw@\NɜZ5!z./kZ(gJq* "H#гbVRE yC.jCQ^EXsX|kro~[XR忇a _HEY%i)ы:4hI)i> .u֐:\N PyuTA(*&EVS3j ^"51IHZ)쵽V,"I[d*"Αz<:of bko8VF,JxLm @b>?YW>yɻ8p Ce cy.637lψq ^6>NܤZPC[V:r/GՆ3Lm4qrx S^tU+]B{9F`` בie }d8-= bwe L }N,f6 %4VlKA*:#( >UXǷ;4 J0p 9E6'5(unT58ЉQ3*5|_Cs*iPÛ9a(^8.Ϝn krB^[L_;{JfNJXAػ~'o@z[[01S_mS/+WLX&%ʙ!U(MVdRa|ʑNKjm3l j]FiATF"ޛKS?ѯ Z?hcꕙ 38P M FsY% ,!'ɴK l+,Ștne((nAUL|`z~/&iǡZ(Tj\Oi iDS;X,(06',{)gRxo)\\գ<@CgK +d$p ;V'DX© DenXN0uu+K\{[6(puaW챊yz- V$j%!LEv'H%_sӉ=Bvt`k@ƻ'&C֢W ӂ[Wə>eb:' g ]8^zk.9{ dAo㙝o$͆ܤj}FMzaL<s_k,COuMTOaY|+ɐ9.e!w^ ? YWZ2 q'uj]b; v! E3:Z8B ADʭ- )"ߓ ~Fc"_:lOD%1y=γK5g: sFpIX rj w42ҧCIKڦ81Edv&/%{qUh𼂒s6kd 0 SbY6Ӕ`_a%2OV=jK0'Rz( mkYwPy1̏}=md '^Q:HyO9%6+mvщ9#"⋜\X#nG(jUy氄@4#l' SҎ5 %[sw͍\og 8:?=WBLM!#IqOL~NGS'QAzf W!~qTL$A`{:E3H n{[/ }S۽[Ù`,jfNw>=6\iɵ6Y>KͱZ9o[o58_J:hL "h6'x1vph Mb-hW4[]K"I(!-~>Qœ,/%!#NUv'U}=`* z>{"a 4I2֫nq|t[L*"FTI}o8n}r!0 !;T&BV<р%12_ фb OU (fubU{d_AG2,x \T[H2|v-08k} xbǁgsdq{ 44zPGװcءvu}D2BC*>n+ou 3줉`NQ[l7kj `.e$sO >wb~V[%Jhs}џ*$)1_wy4ȫHua!2Ќi)4x֜CDֱ( ,p;F=.DT2y|Eϓ>l6L).pv~GW`r6'TR_1%wtL7p5n*mL~qq}Nt'&?sPfgN(R;qGu|#EE1&#͘܄r1n8j1_ltdA[uoj\Wms/ˑe<3'˥^!]rR6b2qbu?^%N_2" "3V(>n_g)BsZ< ?M/q0@j@ ]ӪO4m$Tv4]p[YtI<^nBc DNd5@&,hl; @~.8nx ()ZǷ nz=- uR խfkVl4J{ w1J&Df% N:EBOitV̶/3L~ٿxWl F"RgV%}G/ǏuqlI3ptn71'1@\L ٛVyD%~xǶN=MXbpgy X-ݒ^VQ;BntN@H|e{"_اlG6T-XI [1nVz~-n̘*fJM',$Mt A7&E۸E #w~˒kװ O"iF]54ZQN?M1#pʩ4Vج~oOo`Y~ez*V/d+0C"5AH$<;&6WQ`rq5AMkKx qCmD ꮤ֘T|O K9mh$HaΉT h=<+.}Lu5\/t1W'&gI# :?nfzE,Y;lHJ<fc^ˍ.20.Z_jJӦ0 ҈ q^.lOQ@oC  1.zFs҉m^ʑH)FХ+ p%btO2ޅx|:;B(މDfrSc<痏f/9;Ct٫2`it7Zk6c+ *x[sDX%*'?G)ލT_NdRCH>9l=Ozg\=IW[VxA)CII U3&2Ӊ1EÅpw@A)^|q5QeS|O{' yƜY5tJIb5 MA 9&̴.AYg\+SsCjQ@À㛃*Ye23+ljmҋtH5 `a@W9U@?@^uiG@%E쬉:r$뜸u",6v/oJN{\x. @D'˲dy5fEҶ?9tBN'cJK]:͟&ʅ?b-#(AsBf&ɵ!|}y|(jX QC Fu(G(O4t%Uؚ9DU5a|agL&WLl$]LBvYtGy _Q$[w,!.3@AVÔd~2O =S@e~&an^-{;:5v}TlHnYfաnzqR@5&)-$h +9 0ac^O?AćH&:^wXl$O+hMA8!+`iUR䊤έ&Z)JL⻇=ݑy}6 蟔 o/1hJGCg i仵JeNbeM!|,8xɜ(G}'-? P㇡Qe0usȟ R@7 yKI,-hMsyyk,$ngSՕL7{2B ^dɏ*.zp9r!~q\AkW߂ y61:o(PS;Qa!gݽb3 U5;B4(8 O}_<"\F*N{XZ31?#{$B ݒJYmyZ ?8\~v3-'h`n 2Byo5=́A> qVx&pOWAݠ %rvso}Ŭg|9`+?&0 %5-FL=إƫuϡN*rD#j Cjm|4nPM7᩵%v.`qrR%PUN|[JS_T=/>E)(f=f:B%1CfS_H:6G׊G!,kZ^4MxQwty8 Vi3a|P:m$"4ǣQNO1n^#'i7M$߰2?k$euRƏEm/J<k*;^c0LH#+S@]%.7)ooNF͗1׹h)EÍY0Ь^/UL)aK,v[ تө;\x7sKcpa G->C 6Q]Bۼ^=D a%jd  |,αVJlIljHeռh\T 8;HC| |n6*3I tc1")icZ$rEw0fՆe㧤21gG/Rj}fί@^{ߟǃrbw2)T6 `e=F`ž EF>2,#4f>d}w}Ք6BO=?2詄zтO(~9 ͬ5QQ4HUIw3l^[?J0rhMt7[Ƃ̵όF&OS,ӛ3ޙfcɁS8 Ȫ&:cvo4Lk 6lb:hKfv|wƭO2Lڶ2̭ ~,WIEiա[~pDzs o}d)RB'PJiO9~vGKJj,Gy20t{{KL|P}|̩ kFUBXO?:4#rܟ.ބ$VmV0HJ_:3Nhu4Qަnϙ$76#ğK8x!D.N3Ag`YP"W͌3W# UitRr)$Iͺq '%VjvsZ$COВ Agi^J5jI` 9,{!բR=" u]Oq Bӭ˅[+˜̻Z둚κ{ |{_Y9,>;*]eX%9ć)ͷe,Y<1.v)()Kgn|Cߔ3GB*Fq nᏬ~ ְn;)VvWo;'Ic =: v$m51kߔş1(lixjBԇ; >Wgq(j(q?5H Ex\Y)=(GLk!H э nA!|Wh6gAIsD'f\t !lK5olɛU3B~5DqP?ZzR4 'M/6qQ _Hı%b:V_xT0Kf'T yVEQZνvbZ#i#~NjQL# yuG.~S{uŕqhz󼖇)F}iJ*YCu2 5w"ϧ1z>\H۞.6[OC[xy',4O>$tc}YyvOS`9_ey5gȦ|~$5Z0F# 'x̖Pɒ.Td*qg~ L`JɁZG.)0,4ߓ*^vXK1*H2R&"y)-3:e/~ CV?Å_^cMyMQaozQyF]Jk  \75Ʒ4yK'q$)&P<-s6!őwPX6G6n, X&oH}m%:_\kaT=!ecztHr]|7Ӗ@"2]P&^]-y"yhA@W+z/R!Jz/8٧j&dEaھ```5"ޓl~ ,JKzЉ[v1 7vݙwB|Z9ahzP(N%eQLy*&!3ׁ5Y]h!f:pa/M}/!6S %FPhC]I<$qSf\߲w#8upo &s֭64=%RTH D,ośAo ص.S<Pa96&jer^2p$XOLm 809 `T#!U#v^eW/˰2Gՠx@9 jge f. o5[F_ླB(3D۩!RlRRE)45D>1ri41І>eִN`sxZ8yѴoǽH?{&wt`/-'z7Lxet8b1R0!c/9Njк8/Uso-aΉnO9|h>N ELDUJT :f ʀ)|d.UkHQ-xY\5.8br/zu5y: ~ɳl$d$ς1AC `qaMмN{8kzc92*xB~rDa5QR^*!HW݌*Q3QڒjK0LThpWp s/AҊArNʊoCi$XR>@- S195~Lif?GU*vL&VՀ{RVr o;kޡm_' >3 f>:^%0t؜OYpnŕqX𰟭O4]"›_̹jjnl,ށIġ=@4a ^"!?Z1y9 1_h ұyrDfM蛘'q>*`RL͉};;;b&1=|mQ,UyXKp13eR(=NpC "Ybppm2\^Vhf2* >y,B jdOFy1(-pJr|7&8)WҘj9Y1ߒ:1ƱZ?VVrӍ^Īx-M&giHoÀ*=&7b+.np/TxS f@ʹs a[.LTSdQAJ\ѷ?}A1>Ōu ۽ɉYe8A+tdBY4l ?X {_۪.FL-^>$z PQ8<>kK$r1KҚ8:A.]CuPQq ੷2OϯRbғz0.ͽaɧjs} ӊJ u)Tm',rP ܨ؁bt{8HE0'@&= = pqBBw@\NɜZ5!z./kZ(gJq* "H#гbVRE yC.jCQ^EXsX|kro~[XR忇a _HEY%i)ы:4hI)i> .u֐:\N PyuTA(*&EVS3j ^"51IHZ)쵽V,"I[d*"Αz<:of bko8VF,JxLm @b>?YW>yɻ8p Ce cy.637lψq ^6>NܤZPC[V:r/GՆ3Lm4qrx S^tU+]B{9F`` בie }d8-= bwe L }N,f6 %4VlKA*:#( >UXǷ;4 J0p 9E6'5(unT58ЉQ3*5|_Cs*iPÛ9a(^8.Ϝn krB^[L_;{JfNJXAػ~'o@z[[01S_mS/+WLX&%ʙ!U(MVdRa|ʑNKjm3l j]FiATF"ޛKS?ѯ Z?hcꕙ 38P M FsY% ,!'ɴK l+,Ștne((nAUL|`z~/&iǡZ(Tj\Oi iDS;X,(06',{)gRxo)\\գ<@CgK +d$p ;V'DX© DenXN0uu+K\{[6(puaW챊yz- V$j%!LEv'H%_sӉ=Bvt`k@ƻ'&C֢W ӂ[Wə>eb:' g ]8^zk.9{ dAo㙝o$͆ܤj}FMzaL<s_k,COuMTOaY|+ɐ9.e!w^ ? YWZ2 q'uj]b; v! E3:Z8B ADʭ- )"ߓ ~Fc"_:lOD%1y=γK5g: sFpIX rj w42ҧCIKڦ81Edv&/%{qUh𼂒s6kd 0 SbY6Ӕ`_a%2OV=jK0'Rz( mkYwPy1̏}=md '^Q:HyO9%6+mvщ9#"⋜\X#nG(jUy氄@4#l' SҎ5 %[sw͍\og 8:?=WBLM!#IqOL~NGS'QAzf W!~qTL$A`{:E3H n{[/ }S۽[Ù`,jfNw>=6\iɵ6Y>KͱZ9o[o58_J:hL "h6'x1vph Mb-hW4[]K"I(!-~>Qœ,/%!#NUv'U}=`* z>{"a 4I2֫nq|t[L*"FTI}o8n}r!0 !;T&BV<р%12_ фb OU (fubU{d_AG2,x \T[H2|v-08k} xbǁgsdq{ 44zPGװcءvu}D2BC*>n+ou 3줉`NQ[l7kj `.e$sO >wb~V[%Jhs}џ*$)1_wy4ȫHua!2Ќi)4x֜CDֱ( ,p;F=.DT2y|Eϓ>l6L).pv~GW`r6'TR_1%wtL7p5n*mL~qq}Nt'&?sPfgN(R;qGu|#EE1&#͘܄r1n8j1_ltdA[uoj\Wms/ˑe<3'˥^!]rR6b2qbu?^%N_2" "3V(>n_g)BsZ< ?M/q0@j@ ]ӪO4m$Tv4]p[YtI<^nBc DNd5@&,hl; @~.8nx ()ZǷ nz=- uR խfkVl4J{ w1J&Df% N:EBOitV̶/3L~ٿxWl F"RgV%}G/ǏuqlI3ptn71'1@\L ٛVyD%~xǶN=MXbpgy X-ݒ^VQ;BntN@H|e{"_اlG6T-XI [1nVz~-n̘*fJM',$Mt A7&E۸E #w~˒kװ O"iF]54ZQN?M1#pʩ4Vج~oOo`Y~ez*V/d+0C"5AH$<;&6WQ`rq5AMkKx qCmD ꮤ֘T|O K9mh$HaΉT h=<+.}Lu5\/t1W'&gI# :?nfzE,Y;lHJ<fc^ˍ.20.Z_jJӦ0 ҈ q^.lOQ@oC  1.zFs҉m^ʑH)FХ+ p%btO2ޅx|:;B(މDfrSc<痏f/9;Ct٫2`it7Zk6c+ *x[sDX%*'?G)ލT_NdRCH>9l=Ozg\=IW[VxA)CII U3&2Ӊ1EÅpw@A)^|q5QeS|O{' yƜY5tJIb5 MA 9&̴.AYg\+SsCjQ@À㛃*Ye23+ljmҋtH5 `a@W9U@?@^uiG@%E쬉:r$뜸u",6v/oJN{\x. @D'˲dy5fEҶ?9tBN'cJK]:͟&ʅ?b-#(AsBf&ɵ!|}y|(jX QC Fu(G(O4t%Uؚ9DU5a|agL&WLl$]LBvYtGy _Q$[w,!.3@AVÔd~2O =S@e~&an^-{;:5v}TlHnYfաnzqR@5&)-$h +9 0ac^O?AćH&:^wXl$O+hMA8!+`iUR䊤έ&Z)JL⻇=ݑy}6 蟔 o/1hJGCg i仵JeNbeM!|,8xɜ(G}'-? P㇡Qe0usȟ R@7 yKI,-hMsyyk,$ngSՕL7{2B ^dɏ*.zp9r!~q\AkW߂ y61:o(PS;Qa!gݽb3 U5;B4(8 O}_<"\F*N{XZ31?#{$B ݒJYmyZ ?8\~v3-'h`n 2Byo5=́A> qVx&pOWAݠ %rvso}Ŭg|9`+?&0 %5-FL=إƫuϡN*rD#j Cjm|4nPM7᩵%v.`qrR%PUN|[JS_T=/>E)(f=f:B%1CfS_H:6G׊G!,kZ^4MxQwty8 Vi3a|P:m$"4ǣQNO1n^#'i7M$߰2?k$euRƏEm/J<k*;^c0LH#+S@]%.7)ooNF͗1׹h)EÍY0Ь^/UL)aK,v[ تө;\x7sKcpa G->C 6Q]Bۼ^=D a%jd  |,αVJlIljHeռh\T 8;HC| |n6*3I tc1")icZ$rEw0fՆe㧤21gG/Rj}fί@^{ߟǃrbw2)T6 `e=F`ž EF>2,#4f>d}w}Ք6BO=?2詄zтO(~9 ͬ5QQ4HUIw3l^[?J0rhMt7[Ƃ̵όF&OS,ӛ3ޙfcɁS8 Ȫ&:cvo4Lk 6lb:hKfv|wƭO2Lڶ2̭ ~,WIEiա[~pDzs o}d)RB'PJiO9~vGKJj,Gy20t{{KL|P}|̩ kFUBXO?:4#rܟ.ބ$VmV0HJ_:3Nhu4Qަnϙ$76#ğK8x!D.N3Ag`YP"W͌3W# UitRr)$Iͺq '%VjvsZ$COВ Agi^J5jI` 9,{!բR=" u]Oq Bӭ˅[+˜̻Z둚κ{ |{_Y9,>;*]eX%9ć)ͷe,Y<1.v)()Kgn|Cߔ3GB*Fq nᏬ~ ְn;)VvWo;'Ic =: v$m51kߔş1(lixjBԇ; >Wgq(j(q?5H Ex\Y)=(GLk!H э nA!|Wh6gAIsD'f\t !lK5olɛU3B~5DqP?ZzR4 'M/6qQ _Hı%_{L=/Ź[q 'nTV[ \$ԕJB Y Gչa bMs3X m`Ufy#awo(2DznՇGZ" ~ dOe\S/2E1M]-J+;r~f>v09udbX3[xa?7>sw啪fߵ2*mMxnW @lz__2 QIu#Z`* xzDm6GP[0񙪇B24D >MTĝW\y{HN<*F5q@!H_E#D-ZsNP~^e4ѭGJvm9Q#Ah9EPv 9c:h0épj[0bx6U/2z$B- brץO??PNV'+R"[FsuhS4zKPj)Ѣ'I? kkPb/V aP=o:k egpKǜHB~}ΩGoOJpq^fJT(P(p#X[)C;jos B`ĪP܌B "Tû5ߔ>8k@`Mvl`Na.4cTQM*\#>Zf(r>4c+ABLLfߑ'M]_u~J{uz9EDo/BwxQgTZ,g~.YtL㷲% cg&{`©[/4%ЬՋB&3x%Eݔ=%%#7pbjPOaэ(9+3lFw ~M7SZfRHtc8TkBK]R<#p5B%9e=;>Ob?l@b`oEG ދɕq}1k <#&|C'O ǧd@],\YĭN;hPp^ ZgY|g옩J NVO_GM*V i| JS앹Z6 OFE'j߀LF(F~<'ٺ&MȂn41+5W.bR| _ }^KAidno )cJY$yI19D[?ؗ[ߛ!7`8g; -.et>T/ΛG %T>R0K%xv*hp]{C矃0>#c?Chr!~ "& fgEw6Ā1~_ 4(pxgk/NCߖpĉde&|Ktn(uS*b&k=(Viٵy{f5!ȗa >_ x=5%ӭJ{?R]>{9XNel-OZ iA.{»Ā >t7Cú`Zj_]ei9pBJʖwC=`Wwh}ܛ9,I󹜒v&Yά,u"z|{*u\1tOf_Ps;AADy\U:=-|]t j;g[ӡkZ+Է,)I‹f.4(?z,[>ۄU<tJaBr >)ESY^ zZ Dj p}> &2vj5'O j} U˫UwgT؃[^LC8v'IO~ &iG2"lc0jХȮc4OXv#qlR?P6>M7, pц.<|916oOi =cnIYaPBk֐@+hnlfާ1oc7%,ʵ1XXpu b[|i00w9Z; OVȥ"R2n°vWf@k63vN$X FՐ(5O@I2vοqǼ;ͤk/_4A쏿C+oS~ EJtj$mOI QaGk< =\y*7 iYN)SE݃K<= ̍S,C|uN:v R\N(*t-ɷzI8Ə+0^cĦP>J标@ Wx)`SqP<` wn9qt!(bdƷ7W@{@3Y|S@? y41VV&ov͸F):,n<1*O;y Onflw+} H y!F{ J'@}m[4v6oi!͢lͨKg*ZWoʀgSfFȴ0m]ь n?XQ+UYHRɀI\fb`$kOvEk=; 6 N234 >>g%YVx#Q .˿$!/:VB~C#,6H.5; X E`Lϐqpze<#R CvAU^zv7O۪_?ӷaޢ75zcw5u֯ه{d>~~;Ow>~~vn޳7`8nޮmm z7_ۻݻ?۱vR:-ͯ:>bo-0YmU3abLDQ,Vvk:eyί܎E1 t:oCW*U4dgm8`N9B$Uz3ݠn[ ts?K ҏ.Xj@ {G촟VTS#uNM|kQ󊿝lS;!*63'n$h< o j04%%[(׷rV/UoMWg8b 2TCW5H/vo'OVr9)%)fkzbvB Ӕ.(TǷ9!V;"@(D۶9PjW++a^i,y}\vL1(uWgi2&% JLdHcNj͟^_qomBDEcUl L'}̲1eJo MC^yj[rVZ.4`1GOŘ{b<}E^G4Mռx ɡpqG/"r }l Ύ9^̱\d%6Pb1TW8׉X !QPX]Q0!Yh N#bXͨaW~0@2IZv8~ٙ \uUSP~yo~K+C5eu9FBڞcY"}\:Oudt}7um)גerFT_`]y "`4._`yٽm\GOXֲXхL9ʨ8 %u Gkg~|hzx\n--v<ۅrZ^)}GO( Fj[7kLls{E~yl6kR!Yf#(}@P[zw< % Ccs_fOx2hP]s+92-!A&ϒޝHd֣zGu~oqiq 3kψH%6-8tm4E]ljN!pܘ[LesO]j)O #-PQ F!ص\ߝw`o!\X -53*1_ƌ `G!ٹ@IJ``q U`ecnNHbJ;=vŲqYk\:ۚl^ݡg9 <9@GDM T/%Ƞ=Μ_ߘ^lj 2=Љt_-=ǵVH!?'HKJ-<\QD)/tu\FaU=)Կ÷i&ghm4m20N`4}X*K#4{ Xt "W ly,iW\oGq^#piԦ-HɰeSf?d,f؆&I]0ӡxpșƂ1FR*@=8q`~GtM"jMv"`."~d=xtͶ\;ե`XWa+DDj|ޓ̽Fj"0(1DRmRyz!=Aoe/nvyb 4׎"GWL=}{"MvzKi$`+EQ/>|_?y>smR%lc %[N^~I6(Avh̏еGjg?=@2?tepA29u[n9y )΅3el̺;.CQ_IozQ08-I\n'Úg|DЋ_,D br ~4j^uRDAT/Nۺ^E ;pK['MwȪQ7i'8jS A RrrI 3RiX ɍ?m$}}6US -6~iUASFDY|n'A{浀^ h*]q @{@ɹ̐"6VXgFpH~ؘɠJwQݳ%=ukj!l OVA {y!|#vt}@E`{ u,FNHJ|!*b6!&d:c✄V2.[RHmƮ0^2yV B 0S1BQ痄5 ڶ:H{0; ~BC7` hd ?QM˝Wzp?P"5 T 2U Bf|OI5j+ KbPFgư(`O+oS 00j/ԅ1tK9v7oLVoCh7O=Z 8M'{G45yXUf+nno\ '9}%I&۰΋T 3}TA[&CR|E\Pv4nhHK|iӆ-tqyɢ 'x1NYdTbgҏVrCs# }I{zuXC}(x23 M]nޕ~' 7c& }Sm84 rgǜ Bp2E0. &Z۬ܓUae E5 rn &!5e;qlh45:Χ)&#BǸ!n9G+~)*D nT`+}mAw)ŖvB/u+c]]S_,٨n@Ɩj):Go~4[z1<P^x}1}N"e|ĠjaʔﵚVbCE0'2|ui_ 9XA/S J]eiS}\} :uXѨ"ȼ H]]NW3&7 E{旉te!{9M7 #Ep\p _E~B J^s-d@QDsM9gB,+~St4@f5 p+dLm!ةϺ&HGēV7O Zc.0s Z_ȘV7Km삎a1. Vs9gdSod\ DtHߎAzk{jjݲgOa뎵/z"tqvvD;H'] k`UE({KA3L 7m:.՜2 /q7bԽˆw/`|dѫnWM@p}V_vh-傓ൌ )0>*,}9;f)M֋Ҽ\@`O-j<D,tRk㜑X`#Zu6'8 |*yEe -DC6ng]Ҙtt3Q,nl}/Σ7o=zi_("ȱ?eZ@զ ߩ?j}ad0룊H!X( Ҳu*r-NQTA`+]jtf_[ƘF^}{!/n~ Z=1Q` .Tkg@>GsRC`&0X(p$\?]'jlϽx\DOZF$e(f Z)Y0P;‘E&%Ѯ9St&@N[l)!PM0}JO>ny3HT50.S@PG M<*2nb*00v 7h!Q nWD@ܢfV':PSuIª;X]q%Vncc=0qQhF(u1a0>邋w<\ISoEK;Vʮ$[ qՂQLm m s`a-65rIFT~t)ڀc611|af>~ o.^_>(@lcFߕjV \i,9As$v3ֽ0qE Y,DE%7)ȅ/^A7q駽oI.H)Z."@\-+(=dVMmfX v9 ukv`HCr?mQ-6k`]['()n\À `%":hsqp󅅍 _}Rw8~OW޿c 踧|y} 58`y#1n[iK7]_ B utG`~?1 hrVXB`VRI$i6H_B崺 OR`Z&焅ZRR^,B'ihٵuס!* -=v8Q&T%7Ӂ@-t!%?. k1ER??a P.["W,ޏXԴA fxϯN֌KCUr&ۮ.f@#s,Fk7Ҧh@De/*E8Vߜr@p-[Zs4-}_0V o`1%Mg`c5IB9mI/RTCv i,HlslIծGzU5.l za ibܿJ2/2ǠϽYa|ɣxVYejM5)敂J_Jmw}.m=(Sc<$lPk3WQHxbވE*ahbv?LN(X(,DZҾI(xM(H;!%W;EO~[{69/rzMe[|&Ƹx2l3'A GGD unVϡ:E`NE tU &rGC @U>Syfٓ`{m=ӏs HS9dOp,ʫr7rQ&Dp:+|T2Ӱ1!?`}63E1Boy= 1M s *xUH~VLԱKx?J$ dKڛ~I ΂? ec9m7GŃ\A"zۛ"ِ)g xmH!\0`El̔;nW :cӍ"Sq  T1ބKK.tE~0K86 ocjŽ(Czd{Eee-`R*V2Pz C`fyF"` _6YxOB\G9ʎ"v?se׈qSZ/;ha]HlA9yF!iy@ћx0(WwlqJ`E MI ]-._lK@kY飼TE+j'N[$ X wJٓ$[1 eMJ%ՅC8ST\u}s ij +@%uKdK?bi(3z vNY0U rQ5uphJ\PwOЖ>!umr{McPFi[8K[K5I]?& ?ks\s~u|aS*"dRcKsxU!x4qE/- <M "GN&~d,ͲP ;>)Č0$}>*[S]lbe-3 jsoLD5m7\ ᵳ>xfN"'G'IJ3ZF}X41OSYhZUqNGWzj6^I16b[?Q~$[x!ȶ m<`*S`U RՔ0>L2TnXjVn2YW7hyiJrzF̽yw^/Fҭ}(u 7;Ap=q5S5}֣S "<k j'3|R%fS 4$:dĮf ONgbUJq7G1V@/C`1ߧP( 0jGV4hЁcaԍ]/%N kW Ne h{_kPYާ/FNC,+h5#['w s1iK3f%< L_#Bӣ I~W JؾsVݶaζO3q "-1Q5ߝ} {!2bDG_̈Dj!},&nz ){"̥ؖ=iZ_e-Ma"*a(lK3#MPazWӮ$V ;f3[i G\\~ +#nAc5/aA6'Ho)!jyyܴA+j rж9ruk7eEI0'.ɘO8ĈM{~aAY)-EZާ;a^/x8+e@vGVImԫ9;M;?ql.c`e2A5H%5KːrWl1G3ݒ=fg̪ u/gG 4A8ԾGQ·{M-s)m`E`doqH122-cEiQ܌2B~P&) Eq^C*ni~)z)&|ah`'D.2~IFb>3LZkz< K,uO:Ii|,6TmrPJ=^ {P2C :/Pk aHI_Gxb쌠t+96$m`̱ TVȝ@ 8O5[~ajKnWI2!JK= WSDC\2;_39>ty%C18PǕI5-kG^~L_"9 o:3nyFvTw4X3[z+]פxɞf},?0FpsM _MSlC FS ` |l-d𑿔7he  jAMU Gpş#yCuЮmqL3xJ/IF0 葓E*BƘ` nXЋ^!tw^ Tmf٣SmZ従HP<~g%Ȑ͋> %.:liT3љTӬ(͖v:7tYCNтB0iH]$"!ii 2׾ʯ7@AFāM&G־7 Z^ a77ocQ >m7xP=1Hc:eq\8/p+tItU.$bmR9PstK[vL!9|,+մ/&$0i)! 8< +]t,`܍?>5׉=٬6K2t OSU&jR? 4f%:xu6h (!ex>-"G7`myR+!q70xϏHk:z/F8:465[js&ucVs{>߈[,Z;b-ATjȗvEea#2c  M.2șfܜt:p Qjl>gm#Iĸjg^[ X^f`r`^=z!\ru7A_p-ƣ*D̛y* h<{\.# yS$(? WuxF7iV`M Q|4dv&Qu3BS .D됞w2^N@ڿR*.fDkE f+}zƬ+-[c$e z=ҦX_&7saEfػwPa Z8aa#sLwa*Ģʺe| `!̻zDec~>Wn3k-,UaØvxP:lHYuJ,k|O+$;jPhͬ̚g+Nq+N =Q̲Fnх]-XnXPm5WR t}*㱟jm_ 3dN#ԜO5>=?ZȊe-&_=CFJZ(@S]V;ME#" lg-=IfPE}/aNZ1T{kO<=H@+j)FF#,7:@W%f߃S4 A>D"Y6 ߾d+L!㩊SM<95\&&PU}EKQ4&m^Lga'Ich k2x'V~^(rړ0|.)\ P|[wƶyK{P `md ; *\Z)>]g) #khu̺)#kG{C.T{-`n`Fƴ+wp g Hқ s YbK1 GIw!!Y-;$ĤQ/U!bHh\C> l3f4"vf.qZڐfpjk'r:d,hH%Cgn76^'ndRABD{ג;G;ȑR (&W.Kź / ԕ)~XJUHxLZ~ !>rR)33Ȼr u}eAcֿ?>c- WqNDRX)pF9a|6 ˨E:V$ O]QXVTe=z{ ͼ|=cŬ1 Kj5o TDhA&lN8L{I /Q7-C#MEH 'yUǣ`{W}p+.N#gC򮣹f$B[=b3z`KꮸU0ڞ#{On J@ͳ[U*ֶe 䇲h@f3Cb(AY߸7ۥ!T&wW;N}va=nI܀fk*tİs%u)%)Uvёuyo)a!+ҁ#˭N݋Lȗ3Ils}(RZ{ YTH+zo<[Oe>4l* RWeWK4Sh/b#c@vtk"/!"I'hׅɍXspQݓn;L*i@>p)s2%V\C?N axR0 JlirA}PrBp]ZW1RߗUG.rLW5FVb<.srㄫQZI%/*!0Br%PKq=$'@[9`f̈(I )JUFj.mv[ovd 뢤T="@ ZFXO"F 9*B}B@Jb9DckB!e1Il8ޥRn7XїH \]gw994wcvK̆ _8qghfEq>ʝ\z@Ss I q2;1z- y^oU2?"%MJ/p׬_Q:h;+~Qa$?{c9iʧPըL剰p ʥ&ĴS\II!a3x~4{h띬UơZzR]EAuQI!١v dFW&1 0$tFw?=8e06 .o,|6Z14)ъ-",w:wg7V+Es>nF QnLQWTyq!eB%82Kx载-((P/\:1֔A@^. }m15u Yv HLSSD]pjg DAͧ2pgI$ݡHlT@rLlG>s I'4 Ipt_"Eѷ0^c @GA,#9_/c";n׶ȉmZ11b=Jȋ 4 C dc+2ͺ&+ғ0CeJ^zFMIDhSgT;wԮաeo}w !ĕ!("%'${^_ӓ/ȋ} u U;xJm~X%E"Lp|d6Lܳ%Dlt"mqH?¦0B>j83$I@_/QW#fIޟx'Rb>}E:}AD`$L'M?JsZv 6~YB()%k:>XS@bv ?8`y v%Xƾm\&"BUfbJ %ҹ-cvxkR<ڞ-cIXHP> ӝ`8^YSE-:8">I5⊯9 r'OǼBZ8KްmܥǢf18FtfQ9wqYG5rs鲺nE{k"is..ĸ_qŸw3P:;V6'+01{mTv<?3fIfM="q?"oĆ(1C'q+}|K/@ h++ v3MWo_/zEscp+WmTDJL%$S'XdJRX 7?) A *+C\J˲nQ"T'f$>';F_N Ҟ}3 g b/C}c A'fi*^/B#fRS{ih+HFCQw&Rc@`DeyעJlq2~x DG2+$9++nOM*ιt/ ,yO#`ܟ})gOj#u?}U +x/9 up< %NgqR2tf"~Yh l(_Vuk EɵTV#=@Nz乪 4˜Rɑ^89nґ$+yy誧PFOs~G/Q7)r$ݣMË% )_qX$R0;Ѣӎ<¦EW.c\ 5YpW5]v*ἵ(6H-Gm?r{qwoNNZ=%q$9V?_eP_9Bԍ-f/jmď9!":{r1[EP&24ltdBؚ[XNpgCbVWŁS5Ky,-O+o9Fs̾fGmW?5s*"a4lͼ/2$7i1ʍ4u/r 2nQ5_eHFOS<?"Xql1"y>;2oT2'5hy y>FƲa݉\FeS~I׃nnH;'`I.a=B` g*T4Gf7Q}sC6g[guJ3tÚ Gךfaqnr!K%ƠE*DYjA'J\n+~:>NmaÅ==n&>Oª^Vamﰌ${*#4ϛRQ]젶yn1?BtzRsKp##hLrŦ?Me1PY_3pR6я&w(|142&?p+E.ӭ\սmN~ r,z,f/Ҝܙ.bʵ5a"U+_;oK9^ ,uC$Ԟ:>ȒT>LQ~qX饲]$D$hI(>cy@5 a;Ωs.W.AC7SۊZt7_ 7ukL:R>eVI Q#Uo8ANv[.#Z#;ȴnk\.N]<=w+L\=AY) qad $)Þ''& zb"*6W_P.iXfXwFf!ܐ.s mc0[%<b&τ8D//{tƙO,=ϧ fӣPjlh%8"e$*wxՖos(|k6߬uy'?KNh4& 4?і2DP`\-'K#PK~ӈ@LbDdoj|ԵoD2o^8>X XW@ᶞIKYs)o !G<| :\:yo_Pz;/qI!1/I`P&\Seejミn fdžIM>*!ri<Q*tն"?,O7^ ;yy46̹vpRtmHޒjL'V?'H8Yy8 *(MqOKL{cZs`%}T u#Lěk\zj>BA#lt~n;J$U{-^m~IB n݇:}hij`sE숈pӒf$J#ViOOF-61w^f~C۝DtEPy^ 6-iloH{IFxv93@VNNBma;% ;/ڴ/0ꄴjm^R[||upVq6q</\9W:Ӱ|tt*?ϩ evo(p@QQǖmԍܟ9Ĭ@O*O_`P9$_+Zzd"ȳG/ .L!("׈e դşMz7i DFs;A"׃dͦ>΅/ { ; @m' 286pSO4[KV3̠^oB5ȈIo:Uז09~!:?߃2KSetURh4n H8* H~o5}L]]ȢL&s*赫?aY_8+|XMݙ c|o߹BTK:6{>0зBCc 7BN X{lKFőEH7OZP~L_YBN ɱ ]j^?}כf.^gLݓ,PSzG#Z^P-v*fYፄ,/X+ G|`/x7dWQ֦Tf \DLs B ߉#-K |= @BZUAHm֥բReo<ٷ]Ʒ0!?!9K ;bV;S 6X!#q`h݆SX>vذ]62pi4H 6=B0`z> H?k9D+Ntn%ۜxrS,KzӘɘ*EܿlTgB@A+ņL@R mU ByY~QܲbU6 [ℝ=ފG,uyn>#8D1f/8rM]F$6’[\j'ŖJfkZ@$`.;ė0Y<>]f.Tl&1b-"MMgD f 3m|MKEpwNV ]Mm..|Y[\u^ 6J#X߳ȝ+J#|Q2oҏsjoLjV8|q(&SX 1+ ܥш, [yQFuT5Vڗ,j|TfD0ΔmU@Gte1 xH8W,Ʋ4˻fb & c6"jT6f!lwM}ɋ8݁NYn,sƛB~{1$ ])ũꯨl%joLdu-iud~X5ὖ975OĘc!v*Əy N&-$(IGA⻀`8bp{!2Dc`|x&&*n@v׿tf=e8qx>*F~+񣋊=^%cEj}SxdSL22(V>ybYsݡi;uJ s,lKXzBpI 4XАOp&>XZ7CN]>`V>:,Bvd,Gj^7٢a?hY*]8rOeiUeS byVkⲕ*? V|VΈAP{(NAiwjn~x]#zUTxP^ ZRoXU(@аpq" jhAq^]!ת! g9<@$rh6ˆ@j$;o&a#/ (gTW/Y+z{Gk4S`$mY6AcO_p2JAYor])=\.vc$5gi8mSvCm{C% CI/GĞlO<ᵻcs*H쇂=G=9d/d: 6`hLSƜp<]W*j;h]?6%ysFaNer{Z5'ӗ7}-iڟ۶_Ֆ 5s$rJDȁSn' /RsA诅?&A-Pvl his,Y2%UVނI: Oag{ A_s|vXYI-awWʽ Ѹ#(Nb s{*1\:h_د;ñͭ@-a0:52 Mv(zX=&˝/8#Y#30J5KJgHͻbn" F}z GXR%A=8 \|ɐ9 >&!;IHYSЫFW zSj> tY-3Lxqz0Vq0ۨRcyM:nQo<n i=šLL%@Xpdo6v/n1 PQ]/_\|52Wz-.U# K A~8Rc25лwda,b6 Eײw!I.K /I[.+6u)LI3M[BDoJϕ;Xjznm& HZ2Gn ɥ>AeQby[aHīfʑwI$)p/afU@:!H쭘HCSXTT?:6ArVx>N ; -p5F xK^_1O$"Ν t$|q{MȦ)]JOʭF\q~o?s"q!6T5e%hnmF Rإ.9 u-EC喭6MH\D^& +cchf#4S!xv7R Jw>p# 0$DDG*r`X &cHH[*YvIu IwմDQt50;] 51b"|7*:}(B$C{+|bt\I<@r{&9b˘H)o k Ϧ O $_ .=_FXzi=(^ȶ0Sog`y U G~I:0Jgu[Cg ewb[oPz}2Ip[W%qnFT[UN?=YSTBis8?C [We 1\bqJhFX!yE> d'8҄Z \٥{)L$8B6yn+t%1q Tk޼o+Ӽg+VpflqG00z?iO髟ugw oD Q|A'{=Sr @}o# v,8xu?O-顀sgn4vF y KWaDѷ1F$Ц+^!9Z/Tu}y-S\eM:$ TaSMmW䋼#~QYx̎h/ N: \Q#TGMN1N|/櫺G)bppXeg pĂ YꗃI3i I^_nm_B bRZQ9Pz㤟HGճs"G۟Gm# ֏bEXp԰wx* 4jV;D~Ѹd6{3H#ۥ"Q #O)LmӻVGO,U`?i{n[OVXwqnbR 8&W+B!0?(lv#-sE7,zLw\}hFSw9`4ߨO"8a4r]Z4X>=mz]p:  CXf 5k2(.)h; [ ZD͗ŒVmE! t3K|εǷ m%ո/܋*Zw2!vdŨēJ#28_5Uj#qފVF9 |Q`H Y}o&g_oH(zF㩘 !ԡZF;! ++sCj2O$~~agm7xdgn(. xl@I1Bnľ8f8%fE%nWU a|Mf@DBGe݈!S' MY8$Eʒ.^3oJK"0(hoݙ(?,nD nj}_5rPMVFH9b6ӞkKVUiJ+Ϯ`"*K zIC~͉]>? YѦ=wn+-68׎ UP毃.4;BN`my ̊2 .Ő|mUlo"xk6)nX55(l<˴}Sm&zx`r'jg؄vT -?L%<أ(/p3YXdR"82~RK [¸߸;أOYNE+#3ZgYYJvKkW 6@.2!xO P0/&cM@H6&4YS<9OpJm ͋r]Xi{.UP%熴$!2ɽw+8LD.߈ ƂiΉ'"T~^io gW[7T\*]hWql0,Xd^:x8uK"ep~!BS˓LL_u|e٧7g1g߹Ys!)4-~ʸҁ5Se_1c0_TljaF>h_/wy$coVX c_&f\Q7 ӗ1_g2}++l8C@!5S\/K2l%̓Hbir}KyZPߥ"@nvW^Q=t/Q8_6$PSPݪ;,$0x<2oRR;8F^1ge* 65Cܑ2>Na{tmK0ZpW6M)Xy3jƣ#+7%Bkg`j\:`LG_HAȌ>>{Ĉ?D[nIwH:jGu1-M?PQ" u~\.giA c)x(>BY"5.W{Y]Y x= j9.H~ ^Go(=𯎺g ;wΧ%~ 9QcCױ6h$)꫸܉SA?/6c3-{Gw?5/"X͒lAڻK|r~kU3@tUpt,[IZ~Jk^ WQ4ŵaGdk\ ڜ#p~LJ5J*1"&Oߕn{|7YڈA1£ULVriC3Hen"}7[Қ Iߖ(a&灕ht ^ p0vԫBxE~[쥣?s2hD5PI̴E>_ʲt-Lv+ IJ=bSs|Bc e#ԍ&\p_:lleڇ0CTa{vnsApOiWsTj,Udc~~e@@J}*D&/>~O+Z.%K vK?-jV~̶ kqz +JD]0a6Eꭿduk MD3D1PU@}xQ䂴zr)}Q]+ߐCoxӶ|TclmgZ\{<%CbJ O-g{pB?zv7O۪_?ӷaޢ75zcw5u֯ه{d>~~;Ow>~~vn޳7`8nޮmm z7_ۻݻ?۱vR:-ͯ:>bo-0YmU3abLDQ,Vvk:eyί܎E1 t:oCW*U4dgm8`N9B$Uz3ݠn[ ts?K ҏ.Xj@ {G촟VTS#uNM|kQ󊿝lS;!*63'n$h< o j04%%[(׷rV/UoMWg8b 2TCW5H/vo'OVr9)%)fkzbvB Ӕ.(TǷ9!V;"@(D۶9PjW++a^i,y}\vL1(uWgi2&% JLdHcNj͟^_qomBDEcUl L'}̲1eJo MC^yj[rVZ.4`1GOŘ{b<}E^G4Mռx ɡpqG/"r }l Ύ9^̱\d%6Pb1TW8׉X !QPX]Q0!Yh N#bXͨaW~0@2IZv8~ٙ \uUSP~yo~K+C5eu9FBڞcY"}\:Oudt}7um)גerFT_`]y "`4._`yٽm\GOXֲXхL9ʨ8 %u Gkg~|hzx\n--v<ۅrZ^)}GO( Fj[7kLls{E~yl6kR!Yf#(}@P[zw< % Ccs_fOx2hP]s+92-!A&ϒޝHd֣zGu~oqiq 3kψH%6-8tm4E]ljN!pܘ[LesO]j)O #-PQ F!ص\ߝw`o!\X -53*1_ƌ `G!ٹ@IJ``q U`ecnNHbJ;=vŲqYk\:ۚl^ݡg9 <9@GDM T/%Ƞ=Μ_ߘ^lj 2=Љt_-=ǵVH!?'HKJ-<\QD)/tu\FaU=)Կ÷i&ghm4m20N`4}X*K#4{ Xt "W ly,iW\oGq^#piԦ-HɰeSf?d,f؆&I]0ӡxpșƂ1FR*@=8q`~GtM"jMv"`."~d=xtͶ\;ե`XWa+DDj|ޓ̽Fj"0(1DRmRyz!=Aoe/nvyb 4׎"GWL=}{"MvzKi$`+EQ/>|_?y>smR%lc %[N^~I6(Avh̏еGjg?=@2?tepA29u[n9y )΅3el̺;.CQ_IozQ08-I\n'Úg|DЋ_,D br ~4j^uRDAT/Nۺ^E ;pK['MwȪQ7i'8jS A RrrI 3RiX ɍ?m$}}6US -6~iUASFDY|n'A{浀^ h*]q @{@ɹ̐"6VXgFpH~ؘɠJwQݳ%=ukj!l OVA {y!|#vt}@E`{ u,FNHJ|!*b6!&d:c✄V2.[RHmƮ0^2yV B 0S1BQ痄5 ڶ:H{0; ~BC7` hd ?QM˝Wzp?P"5 T 2U Bf|OI5j+ KbPFgư(`O+oS 00j/ԅ1tK9v7oLVoCh7O=Z 8M'{G45yXUf+nno\ '9}%I&۰΋T 3}TA[&CR|E\Pv4nhHK|iӆ-tqyɢ 'x1NYdTbgҏVrCs# }I{zuXC}(x23 M]nޕ~' 7c& }Sm84 rgǜ Bp2E0. &Z۬ܓUae E5 rn &!5e;qlh45:Χ)&#BǸ!n9G+~)*D nT`+}mAw)ŖvB/u+c]]S_,٨n@Ɩj):Go~4[z1<P^x}1}N"e|ĠjaʔﵚVbCE0'2|ui_ 9XA/S J]eiS}\} :uXѨ"ȼ H]]NW3&7 E{旉te!{9M7 #Ep\p _E~B J^s-d@QDsM9gB,+~St4@f5 p+dLm!ةϺ&HGēV7O Zc.0s Z_ȘV7Km삎a1. Vs9gdSod\ DtHߎAzk{jjݲgOa뎵/z"tqvvD;H'] k`UE({KA3L 7m:.՜2 /q7bԽˆw/`|dѫnWM@p}V_vh-傓ൌ )0>*,}9;f)M֋Ҽ\@`O-j<D,tRk㜑X`#Zu6'8 |*yEe -DC6ng]Ҙtt3Q,nl}/Σ7o=zi_("ȱ?eZ@զ ߩ?j}ad0룊H!X( Ҳu*r-NQTA`+]jtf_[ƘF^}{!/n~ Z=1Q` .Tkg@>GsRC`&0X(p$\?]'jlϽx\DOZF$e(f Z)Y0P;‘E&%Ѯ9St&@N[l)!PM0}JO>ny3HT50.S@PG M<*2nb*00v 7h!Q nWD@ܢfV':PSuIª;X]q%Vncc=0qQhF(u1a0>邋w<\ISoEK;Vʮ$[ qՂQLm m s`a-65rIFT~t)ڀc611|af>~ o.^_>(@lcFߕjV \i,9As$v3ֽ0qE Y,DE%7)ȅ/^A7q駽oI.H)Z."@\-+(=dVMmfX v9 ukv`HCr?mQ-6k`]['()n\À `%":hsqp󅅍 _}Rw8~OW޿c 踧|y} 58`y#1n[iK7]_ B utG`~?1 hrVXB`VRI$i6H_B崺 OR`Z&焅ZRR^,B'ihٵuס!* -=v8Q&T%7Ӂ@-t!%?. k1ER??a P.["W,ޏXԴA fxϯN֌KCUr&ۮ.f@#s,Fk7Ҧh@De/*E8Vߜr@p-[Zs4-}_0V o`1%Mg`c5IB9mI/RTCv i,HlslIծGzU5.l za ibܿJ2/2ǠϽYa|ɣxVYejM5)敂J_Jmw}.m=(Sc<$lPk3WQHxbވE*ahbv?LN(X(,DZҾI(xM(H;!%W;EO~[{69/rzMe[|&Ƹx2l3'A GGD unVϡ:E`NE tU &rGC @U>Syfٓ`{m=ӏs HS9dOp,ʫr7rQ&Dp:+|T2Ӱ1!?`}63E1Boy= 1M s *xUH~VLԱKx?J$ dKڛ~I ΂? ec9m7GŃ\A"zۛ"ِ)g xmH!\0`El̔;nW :cӍ"Sq  T1ބKK.tE~0K86 ocjŽ(Czd{Eee-`R*V2Pz C`fyF"` _6YxOB\G9ʎ"v?se׈qSZ/;ha]HlA9yF!iy@ћx0(WwlqJ`E MI ]-._lK@kY飼TE+j'N[$ X wJٓ$[1 eMJ%ՅC8ST\u}s ij +@%uKdK?bi(3z vNY0U rQ5uphJ\PwOЖ>!umr{McPFi[8K[K5I]?& ?ks\s~u|aS*"dRcKsxU!x4qE/- <M "GN&~d,ͲP ;>)Č0$}>*[S]lbe-3 jsoLD5m7\ ᵳ>xfN"'G'IJ3ZF}X41OSYhZUqNGWzj6^I16b[?Q~$[x!ȶ m<`*S`U RՔ0>L2TnXjVn2YW7hyiJrzF̽yw^/Fҭ}(u 7;Ap=q5S5}֣S "<k j'3|R%fS 4$:dĮf ONgbUJq7G1V@/C`1ߧP( 0jGV4hЁcaԍ]/%N kW Ne h{_kPYާ/FNC,+h5#['w s1iK3f%< L_#Bӣ I~W JؾsVݶaζO3q "-1Q5ߝ} {!2bDG_̈Dj!},&nz ){"̥ؖ=iZ_e-Ma"*a(lK3#MPazWӮ$V ;f3[i G\\~ +#nAc5/aA6'Ho)!jyyܴA+j rж9ruk7eEI0'.ɘO8ĈM{~aAY)-EZާ;a^/x8+e@vGVImԫ9;M;?ql.c`e2A5H%5KːrWl1G3ݒ=fg̪ u/gG 4A8ԾGQ·{M-s)m`E`doqH122-cEiQ܌2B~P&) Eq^C*ni~)z)&|ah`'D.2~IFb>3LZkz< K,uO:Ii|,6TmrPJ=^ {P2C :/Pk aHI_Gxb쌠t+96$m`̱ TVȝ@ 8O5[~ajKnWI2!JK= WSDC\2;_39>ty%C18PǕI5-kG^~L_"9 o:3nyFvTw4X3[z+]פxɞf},?0FpsM _MSlC FS ` |l-d𑿔7he  jAMU Gpş#yCuЮmqL3xJ/IF0 葓E*BƘ` nXЋ^!tw^ Tmf٣SmZ従HP<~g%Ȑ͋> %.:liT3љTӬ(͖v:7tYCNтB0iH]$"!ii 2׾ʯ7@AFāM&G־7 Z^ a77ocQ >m7xP=1Hc:eq\8/p+tItU.$bmR9PstK[vL!9|,+մ/&$0i)! 8< +]t,`܍?>5׉=٬6K2t OSU&jR? 4f%:xu6h (!ex>-"G7`myR+!q70xϏHk:z/F8:465[js&ucVs{>߈[,Z;b-ATjȗvEea#2c  M.2șfܜt:p Qjl>gm#Iĸjg^[ X^f`r`^=z!\ru7A_p-ƣ*D̛y* h<{\.# yS$(? WuxF7iV`M Q|4dv&Qu3BS .D됞w2^N@ڿR*.fDkE f+}zƬ+-[c$e z=ҦX_&7saEfػwPa Z8aa#sLwa*Ģʺe| `!̻zDec~>Wn3k-,UaØvxP:lHYuJ,k|O+$;jPhͬ̚g+Nq+N =Q̲Fnх]-XnXPm5WR t}*㱟jm_ 3dN#ԜO5>=?ZȊe-&_=CFJZ(@S]V;ME#" lg-=IfPE}/aNZ1T{kO<=H@+j)FF#,7:@W%f߃S4 A>D"Y6 ߾d+L!㩊SM<95\&&PU}EKQ4&m^Lga'Ich k2x'V~^(rړ0|.)\ P|[wƶyK{P `md ; *\Z)>]g) #khu̺)#kG{C.T{-`n`Fƴ+wp g Hқ s YbK1 GIw!!Y-;$ĤQ/U!bHh\C> l3f4"vf.qZڐfpjk'r:d,hH%Cgn76^'ndRABD{ג;G;ȑR (&W.Kź / ԕ)~XJUHxLZ~ !>rR)33Ȼr u}eAcֿ?>c- WqNDRX)pF9a|6 ˨E:V$ O]QXVTe=z{ ͼ|=cŬ1 Kj5o TDhA&lN8L{I /Q7-C#MEH 'yUǣ`{W}p+.N#gC򮣹f$B[=b3z`KꮸU0ڞ#{On J@ͳ[U*ֶe 䇲h@f3Cb(AY߸7ۥ!T&wW;N}va=nI܀fk*tİs%u)%)Uvёuyo)a!+ҁ#˭N݋Lȗ3Ils}(RZ{ YTH+zo<[Oe>4l* RWeWK4Sh/b#c@vtk"/!"I'hׅɍXspQݓn;L*i@>p)s2%V\C?N axR0 JlirA}PrBp]ZW1RߗUG.rLW5FVb<.srㄫQZI%/*!0Br%PKq=$'@[9`f̈(I )JUFj.mv[ovd 뢤T="@ ZFXO"F 9*B}B@Jb9DckB!e1Il8ޥRn7XїH \]gw994wcvK̆ _8qghfEq>ʝ\z@Ss I q2;1z- y^oU2?"%MJ/p׬_Q:h;+~Qa$?{c9iʧPըL剰p ʥ&ĴS\II!a3x~4{h띬UơZzR]EAuQI!١v dFW&1 0$tFw?=8e06 .o,|6Z14)ъ-",w:wg7V+Es>nF QnLQWTyq!eB%82Kx载-((P/\:1֔A@^. }m15u Yv HLSSD]pjg DAͧ2pgI$ݡHlT@rLlG>s I'4 Ipt_"Eѷ0^c @GA,#9_/c";n׶ȉmZ11b=Jȋ 4 C dc+2ͺ&+ғ0CeJ^zFMIDhSgT;wԮաeo}w !ĕ!("%'${^_ӓ/ȋ} u U;xJm~X%E"Lp|d6Lܳ%Dlt"mqH?¦0B>j83$I@_/QW#fIޟx'Rb>}E:}AD`$L'M?JsZv 6~YB()%k:>XS@bv ?8`y v%Xƾm\&"BUfbJ %ҹ-cvxkR<ڞ-cIXHP> ӝ`8^YSE-:8">I5⊯9 r'OǼBZ8KްmܥǢf18FtfQ9wqYG5rs鲺nE{k"is..ĸ_qŸw3P:;V6'+01{mTv<?3fIfM="q?"oĆ(1C'q+}|K/@ h++ v3MWo_/zEscp+WmTDJL%$S'XdJRX 7?) A *+C\J˲nQ"T'f$>';F_N Ҟ}3 g b/C}c A'fi*^/B#fRS{ih+HFCQw&Rc@`DeyעJlq2~x DG2+$9++nOM*ιt/ ,yO#`ܟ})gOj#u?}U +x/9 up< %NgqR2tf"~Yh l(_Vuk EɵTV#=@Nz乪 4˜Rɑ^89nґ$+yy誧PFOs~G/Q7)r$ݣMË% )_qX$R0;Ѣӎ<¦EW.c\ 5YpW5]v*ἵ(6H-Gm?r{qwoNNZ=%q$9V?_eP_9Bԍ-f/jmď9!":{r1[EP&24ltdBؚ[XNpgCbVWŁS5Ky,-O+o9Fs̾fGmW?5s*"a4lͼ/2$7i1ʍ4u/r 2nQ5_eHFOS<?"Xql1"y>;2oT2'5hy y>FƲa݉\FeS~I׃nnH;'`I.a=B` g*T4Gf7Q}sC6g[guJ3tÚ Gךfaqnr!K%ƠE*DYjA'J\n+~:>NmaÅ==n&>Oª^Vamﰌ${*#4ϛRQ]젶yn1?BtzRsKp##hLrŦ?Me1PY_3pR6я&w(|142&?p+E.ӭ\սmN~ r,z,f/Ҝܙ.bʵ5a"U+_;oK9^ ,uC$Ԟ:>ȒT>LQ~qX饲]$D$hI(>cy@5 a;Ωs.W.AC7SۊZt7_ 7ukL:R>eVI Q#Uo8ANv[.#Z#;ȴnk\.N]<=w+L\=AY) qad $)Þ''& zb"*6W_P.iXfXwFf!ܐ.s mc0[%<b&τ8D//{tƙO,=ϧ fӣPjlh%8"e$*wxՖos(|k6߬uy'?KNh4& 4?і2DP`\-'K#PK~ӈ@LbDdoj|ԵoD2o^8>X XW@ᶞIKYs)o !G<| :\:yo_Pz;/qI!1/I`P&\Seejミn fdžIM>*!ri<Q*tն"?,O7^ ;yy46̹vpRtmHޒjL'V?'H8Yy8 *(MqOKL{cZs`%}T u#Lěk\zj>BA#lt~n;J$U{-^m~IB n݇:}hij`sE숈pӒf$J#ViOOF-61w^f~C۝DtEPy^ 6-iloH{IFxv93@VNNBma;% ;/ڴ/0ꄴjm^R[||upVq6q</\9W:Ӱ|tt*?ϩ evo(p@QQǖmԍܟ9Ĭ@O*O_`P9$_+Zzd"ȳG/ .L!("׈e դşMz7i DFs;A"׃dͦ>΅/ { ; @m' 286pSO4[KV3̠^oB5ȈIo:Uז09~!:?߃2KSetURh4n H8* H~o5}L]]ȢL&s*赫?aY_8+|XMݙ c|o߹BTK:6{>0зBCc 7BN X{lKFőEH7OZP~L_YBN ɱ ]j^?}כf.^gLݓ,PSzG#Z^P-v*fYፄ,/X+ G|`/x7dWQ֦Tf \DLs B ߉#-K |= @BZUAHm֥բReo<ٷ]Ʒ0!?!9K ;bV;S 6X!#q`h݆SX>vذ]62pi4H 6=B0`z> H?k9D+Ntn%ۜxrS,KzӘɘ*EܿlTgB@A+ņL@R mU ByY~QܲbU6 [ℝ=ފG,uyn>#8D1f/8rM]F$6’[\j'ŖJfkZ@$`.;ė0Y<>]f.Tl&1b-"MMgD f 3m|MKEpwNV ]Mm..|Y[\u^ 6J#X߳ȝ+J#|Q2oҏsjoLjV8|q(&SX 1+ ܥш, [yQFuT5Vڗ,j|TfD0ΔmU@Gte1 xH8W,Ʋ4˻fb & c6"jT6f!lwM}ɋ8݁NYn,sƛB~{1$ ])ũꯨl%joLdu-iud~X5ὖ975OĘc!v*Əy N&-$(IGA⻀`8bp{!2Dc`|x&&*n@v׿tf=e8qx>*F~+񣋊=^%cEj}SxdSL22(V>ybYsݡi;uJ s,lKXzBpI 4XАOp&>XZ7CN]>`V>:,Bvd,Gj^7٢a?hY*]8rOeiUeS byVkⲕ*? V|VΈAP{(NAiwjn~x]#zUTxP^ ZRoXU(@аpq" jhAq^]!ת! g9<@$rh6ˆ@j$;o&a#/ (gTW/Y+z{Gk4S`$mY6AcO_p2JAYor])=\.vc$5gi8mSvCm{C% CI/GĞlO<ᵻcs*H쇂=G=9d/d: 6`hLSƜp<]W*j;h]?6%ysFaNer{Z5'ӗ7}-iڟ۶_Ֆ 5s$rJDȁSn' /RsA诅?&A-Pvl his,Y2%UVނI: Oag{ A_s|vXYI-awWʽ Ѹ#(Nb s{*1\:h_د;ñͭ@-a0:52 Mv(zX=&˝/8#Y#30J5KJgHͻbn" F}z GXR%A=8 \|ɐ9 >&!;IHYSЫFW zSj> tY-3Lxqz0Vq0ۨRcyM:nQo<n i=šLL%@Xpdo6v/n1 PQ]/_\|52Wz-.U# K A~8Rc25лwda,b6 Eײw!I.K /I[.+6u)LI3M[BDoJϕ;Xjznm& HZ2Gn ɥ>AeQby[aHīfʑwI$)p/afU@:!H쭘HCSXTT?:6ArVx>N ; -p5F xK^_1O$"Ν t$|q{MȦ)]JOʭF\q~o?s"q!6T5e%hnmF Rإ.9 u-EC喭6MH\D^& +cchf#4S!xv7R Jw>p# 0$DDG*r`X &cHH[*YvIu IwմDQt50;] 51b"|7*:}(B$C{+|bt\I<@r{&9b˘H)o k Ϧ O $_ .=_FXzi=(^ȶ0Sog`y U G~I:0Jgu[Cg ewb[oPz}2Ip[W%qnFT[UN?=YSTBis8?C [We 1\bqJhFX!yE> d'8҄Z \٥{)L$8B6yn+t%1q Tk޼o+Ӽg+VpflqG00z?iO髟ugw oD Q|A'{=Sr @}o# v,8xu?O-顀sgn4vF y KWaDѷ1F$Ц+^!9Z/Tu}y-S\eM:$ TaSMmW䋼#~QYx̎h/ N: \Q#TGMN1N|/櫺G)bppXeg pĂ YꗃI3i I^_nm_B bRZQ9Pz㤟HGճs"G۟Gm# ֏bEXp԰wx* 4jV;D~Ѹd6{3H#ۥ"Q #O)LmӻVGO,U`?i{n[OVXwqnbR 8&W+B!0?(lv#-sE7,zLw\}hFSw9`4ߨO"8a4r]Z4X>=mz]p:  CXf 5k2(.)h; [ ZD͗ŒVmE! t3K|εǷ m%ո/܋*Zw2!vdŨēJ#28_5Uj#qފVF9 |Q`H Y}o&g_oH(zF㩘 !ԡZF;! ++sCj2O$~~agm7xdgn(. xl@I1Bnľ8f8%fE%nWU a|Mf@DBGe݈!S' MY8$Eʒ.^3oJK"0(hoݙ(?,nD nj}_5rPMVFH9b6ӞkKVUiJ+Ϯ`"*K zIC~͉]>? YѦ=wn+-68׎ UP毃.4;BN`my ̊2 .Ő|mUlo"xk6)nX55(l<˴}Sm&zx`r'jg؄vT -?L%<أ(/p3YXdR"82~RK [¸߸;أOYNE+#3ZgYYJvKkW 6@.2!xO P0/&cM@H6&4YS<9OpJm ͋r]Xi{.UP%熴$!2ɽw+8LD.߈ ƂiΉ'"T~^io gW[7T\*]hWql0,Xd^:x8uK"ep~!BS˓LL_u|e٧7g1g߹Ys!)4-~ʸҁ5Se_1c0_TljaF>h_/wy$coVX c_&f\Q7 ӗ1_g2}++l8C@!5S\/K2l%̓Hbir}KyZPߥ"@nvW^Q=t/Q8_6$PSPݪ;,$0x<2oRR;8F^1ge* 65Cܑ2>Na{tmK0ZpW6M)Xy3jƣ#+7%Bkg`j\:`LG_HAȌ>>{Ĉ?D[nIwH:jGu1-M?PQ" u~\.giA c)x(>BY"5.W{Y]Y x= j9.H~ ^Go(=𯎺g ;wΧ%~ 9QcCױ6h$)꫸܉SA?/6c3-{Gw?5/"X͒lAڻK|r~kU3@tUpt,[IZ~Jk^ WQ4ŵaGdk\ ڜ#p~LJ5J*1"&Oߕn{|7YڈA1£ULVriC3Hen"}7[Қ Iߖ(a&灕ht ^ p0vԫBxE~[쥣?s2hD5PI̴E>_ʲt-Lv+ IJ=bSs|Bc e#ԍ&\p_:lleڇ0CTa{vnsApOiWsTj,Udc~~e@@J}*D&/>~O+Z.%K vK?-jV~̶ kqz +JD]0a6Eꭿduk MD3D1PU@}xQ䂴zr)}Q]+ߐCoxӶ|TclmgZ\{<%CbJ O-g{pB?zv7O۪_?ӷaޢ75zcw5u֯ه{d>~~;Ow>~~vn޳7`8nޮmm z7_ۻݻ?۱vR:-ͯ:>bo-0YmU3abLDQ,Vvk:eyί܎E1 t:oCW*U4dgm8`N9B$Uz3ݠn[ ts?K ҏ.Xj@ {G촟VTS#uNM|kQ󊿝lS;!*63'n$h< o j04%%[(׷rV/UoMWg8b 2TCW5H/vo'OVr9)%)fkzbvB Ӕ.(TǷ9!V;"@(D۶9PjW++a^i,y}\vL1(uWgi2&% JLdHcNj͟^_qomBDEcUl L'}̲1eJo MC^yj[rVZ.4`1GOŘ{b<}E^G4Mռx ɡpqG/"r }l Ύ9^̱\d%6Pb1TW8׉X !QPX]Q0!Yh N#bXͨaW~0@2IZv8~ٙ \uUSP~yo~K+C5eu9FBڞcY"}\:Oudt}7um)גerFT_`]y "`4._`yٽm\GOXֲXхL9ʨ8 %u Gkg~|hzx\n--v<ۅrZ^)}GO( Fj[7kLls{E~yl6kR!Yf#(}@P[zw< % Ccs_fOx2hP]s+92-!A&ϒޝHd֣zGu~oqiq 3kψH%6-8tm4E]ljN!pܘ[LesO]j)O #-PQ F!ص\ߝw`o!\X -53*1_ƌ `G!ٹ@IJ``q U`ecnNHbJ;=vŲqYk\:ۚl^ݡg9 <9@GDM T/%Ƞ=Μ_ߘ^lj 2=Љt_-=ǵVH!?'HKJ-<\QD)/tu\FaU=)Կ÷i&ghm4m20N`4}X*K#4{ Xt "W ly,iW\oGq^#piԦ-HɰeSf?d,f؆&I]0ӡxpșƂ1FR*@=8q`~GtM"jMv"`."~d=xtͶ\;ե`XWa+DDj|ޓ̽Fj"0(1DRmRyz!=Aoe/nvyb 4׎"GWL=}{"MvzKi$`+EQ/>|_?y>smR%lc %[N^~I6(Avh̏еGjg?=@2?tepA29u[n9y )΅3el̺;.CQ_IozQ08-I\n'Úg|DЋ_,D br ~4j^uRDAT/Nۺ^E ;pK['MwȪQ7i'8jS A RrrI 3RiX ɍ?m$}}6US -6~iUASFDY|n'A{浀^ h*]q @{@ɹ̐"6VXgFpH~ؘɠJwQݳ%=ukj!l OVA {y!|#vt}@E`{ u,FNHJ|!*b6!&d:c✄V2.[RHmƮ0^2yV B 0S1BQ痄5 ڶ:H{0; ~BC7` hd ?QM˝Wzp?P"5 T 2U Bf|OI5j+ KbPFgư(`O+oS 00j/ԅ1tK9v7oLVoCh7O=Z 8M'{G45yXUf+nno\ '9}%I&۰΋T 3}TA[&CR|E\Pv4nhHK|iӆ-tqyɢ 'x1NYdTbgҏVrCs# }I{zuXC}(x23 M]nޕ~' 7c& }Sm84 rgǜ Bp2E0. &Z۬ܓUae E5 rn &!5e;qlh45:Χ)&#BǸ!n9G+~)*D nT`+}mAw)ŖvB/u+c]]S_,٨n@Ɩj):Go~4[z1<P^x}1}N"e|ĠjaʔﵚVbCE0'2|ui_ 9XA/S J]eiS}\} :uXѨ"ȼ H]]NW3&7 E{旉te!{9M7 #Ep\p _E~B J^s-d@QDsM9gB,+~St4@f5 p+dLm!ةϺ&HGēV7O Zc.0s Z_ȘV7Km삎a1. Vs9gdSod\ DtHߎAzk{jjݲgOa뎵/z"tqvvD;H'] k`UE({KA3L 7m:.՜2 /q7bԽˆw/`|dѫnWM@p}V_vh-傓ൌ )0>*,}9;f)M֋Ҽ\@`O-j<D,tRk㜑X`#Zu6'8 |*yEe -DC6ng]Ҙtt3Q,nl}/Σ7o=zi_("ȱ?eZ@զ ߩ?j}ad0룊H!X( Ҳu*r-NQTA`+]jtf_[ƘF^}{!/n~ Z=1Q` .Tkg@>GsRC`&0X(p$\?]'jlϽx\DOZF$e(f Z)Y0P;‘E&%Ѯ9St&@N[l)!PM0}JO>ny3HT50.S@PG M<*2nb*00v 7h!Q nWD@ܢfV':PSuIª;X]q%Vncc=0qQhF(u1a0>邋w<\ISoEK;Vʮ$[ qՂQLm m s`a-65rIFT~t)ڀc611|af>~ o.^_>(@lcFߕjV \i,9As$v3ֽ0qE Y,DE%7)ȅ/^A7q駽oI.H)Z."@\-+(=dVMmfX v9 ukv`HCr?mQ-6k`]['()n\À `%":hsqp󅅍 _}Rw8~OW޿c 踧|y} 58`y#1n[iK7]_ B utG`~?1 hrVXB`VRI$i6H_B崺 OR`Z&焅ZRR^,B'ihٵuס!* -=v8Q&T%7Ӂ@-t!%?. k1ER??a P.["W,ޏXԴA fxϯN֌KCUr&ۮ.f@#s,Fk7Ҧh@De/*E8Vߜr@p-[Zs4-}_0V o`1%Mg`c5IB9mI/RTCv i,HlslIծGzU5.l za ibܿJ2/2ǠϽYa|ɣxVYejM5)敂J_Jmw}.m=(Sc<$lPk3WQHxbވE*ahbv?LN(X(,DZҾI(xM(H;!%W;EO~[{69/rzMe[|&Ƹx2l3'A GGD unVϡ:E`NE tU &rGC @U>Syfٓ`{m=ӏs HS9dOp,ʫr7rQ&Dp:+|T2Ӱ1!?`}63E1Boy= 1M s *xUH~VLԱKx?J$ dKڛ~I ΂? ec9m7GŃ\A"zۛ"ِ)g xmH!\0`El̔;nW :cӍ"Sq  T1ބKK.tE~0K86 ocjŽ(Czd{Eee-`R*V2Pz C`fyF"` _6YxOB\G9ʎ"v?se׈qSZ/;ha]HlA9yF!iy@ћx0(WwlqJ`E MI ]-._lK@kY飼TE+j'N[$ X wJٓ$[1 eMJ%ՅC8ST\u}s ij +@%uKdK?bi(3z vNY0U rQ5uphJ\PwOЖ>!umr{McPFi[8K[K5I]?& ?ks\s~u|aS*"dRcKsxU!x4qE/- <M "GN&~d,ͲP ;>)Č0$}>*[S]lbe-3 jsoLD5m7\ ᵳ>xfN"'G'IJ3ZF}X41OSYhZUqNGWzj6^I16b[?Q~$[x!ȶ m<`*S`U RՔ0>L2TnXjVn2YW7hyiJrzF̽yw^/Fҭ}(u 7;Ap=q5S5}֣S "<k j'3|R%fS 4$:dĮf ONgbUJq7G1V@/C`1ߧP( 0jGV4hЁcaԍ]/%N kW Ne h{_kPYާ/FNC,+h5#['w s1iK3f%< L_#Bӣ I~W JؾsVݶaζO3q "-1Q5ߝ} {!2bDG_̈Dj!},&nz ){"̥ؖ=iZ_e-Ma"*a(lK3#MPazWӮ$V ;f3[i G\\~ +#nAc5/aA6'Ho)!jyyܴA+j rж9ruk7eEI0'.ɘO8ĈM{~aAY)-EZާ;a^/x8+e@vGVImԫ9;M;?ql.c`e2A5H%5KːrWl1G3ݒ=fg̪ u/gG 4A8ԾGQ·{M-s)m`E`doqH122-cEiQ܌2B~P&) Eq^C*ni~)z)&|ah`'D.2~IFb>3LZkz< K,uO:Ii|,6TmrPJ=^ {P2C :/Pk aHI_Gxb쌠t+96$m`̱ TVȝ@ 8O5[~ajKnWI2!JK= WSDC\2;_39>ty%C18PǕI5-kG^~L_"9 o:3nyFvTw4X3[z+]פxɞf},?0FpsM _MSlC FS ` |l-d𑿔7he  jAMU Gpş#yCuЮmqL3xJ/IF0 葓E*BƘ` nXЋ^!tw^ Tmf٣SmZ従HP<~g%Ȑ͋> %.:liT3љTӬ(͖v:7tYCNтB0iH]$"!ii 2׾ʯ7@AFāM&G־7 Z^ a77ocQ >m7xP=1Hc:eq\8/p+tItU.$bmR9PstK[vL!9|,+մ/&$0i)! 8< +]t,`܍?>5׉=٬6K2t OSU&jR? 4f%:xu6h (!ex>-"G7`myR+!q70xϏHk:z/F8:465[js&ucVs{>߈[,Z;b-ATjȗvEea#2c  M.2șfܜt:p Qjl>gm#Iĸjg^[ X^f`r`^=z!\ru7A_p-ƣ*D̛y* h<{\.# yS$(? WuxF7iV`M Q|4dv&Qu3BS .D됞w2^N@ڿR*.fDkE f+}zƬ+-[c$e z=ҦX_&7saEfػwPa Z8aa#sLwa*Ģʺe| `!̻zDec~>Wn3k-,UaØvxP:lHYuJ,k|O+$;jPhͬ̚g+Nq+N =Q̲Fnх]-XnXPm5WR t}*㱟jm_ 3dN#ԜO5>=?ZȊe-&_=CFJZ(@S]V;ME#" lg-=IfPE}/aNZ1T{kO<=H@+j)FF#,7:@W%f߃S4 A>D"Y6 ߾d+L!㩊SM<95\&&PU}EKQ4&m^Lga'Ich k2x'V~^(rړ0|.)\ P|[wƶyK{P `md ; *\Z)>]g) #khu̺)#kG{C.T{-`n`Fƴ+wp g Hқ s YbK1 GIw!!Y-;$ĤQ/U!bHh\C> l3f4"vf.qZڐfpjk'r:d,hH%Cgn76^'ndRABD{ג;G;ȑR (&W.Kź / ԕ)~XJUHxLZ~ !>rR)33Ȼr u}eAcֿ?>c- WqNDRX)pF9a|6 ˨E:V$ O]QXVTe=z{ ͼ|=cŬ1 Kj5o TDhA&lN8L{I /Q7-C#MEH 'yUǣ`{W}p+.N#gC򮣹f$B[=b3z`KꮸU0ڞ#{On J@ͳ[U*ֶe 䇲h@f3Cb(AY߸7ۥ!T&wW;N}va=nI܀fk*tİs%u)%)Uvёuyo)a!+ҁ#˭N݋Lȗ3Ils}(RZ{ YTH+zo<[Oe>4l* RWeWK4Sh/b#c@vtk"/!"I'hׅɍXspQݓn;L*i@>p)s2%V\C?N axR0 JlirA}PrBp]ZW1RߗUG.rLW5FVb<.srㄫQZI%/*!0Br%PKq=$'@[9`f̈(I )JUFj.mv[ovd 뢤T="@ ZFXO"F 9*B}B@Jb9DckB!e1Il8ޥRn7XїH \]gw994wcvK̆ _8qghfEq>ʝ\z@Ss I q2;1z- y^oU2?"%MJ/p׬_Q:h;+~Qa$?{c9iʧPըL剰p ʥ&ĴS\II!a3x~4{h띬UơZzR]EAuQI!١v dFW&1 0$tFw?=8e06 .o,|6Z14)ъ-",w:wg7V+Es>nF QnLQWTyq!eB%82Kx载-((P/\:1֔A@^. }m15u Yv HLSSD]pjg DAͧ2pgI$ݡHlT@rLlG>s I'4 Ipt_"Eѷ0^c @GA,#9_/c";n׶ȉmZ11b=Jȋ 4 C dc+2ͺ&+ғ0CeJ^zFMIDhSgT;wԮաeo}w !ĕ!("%'${^_ӓ/ȋ} u U;xJm~X%E"Lp|d6Lܳ%Dlt"mqH?¦0B>j83$I@_/QW#fIޟx'Rb>}E:}AD`$L'M?JsZv 6~YB()%k:>XS@bv ?8`y v%Xƾm\&"BUfbJ %ҹ-cvxkR<ڞ-cIXHP> ӝ`8^YSE-:8">I5⊯9 r'OǼBZ8KްmܥǢf18FtfQ9wqYG5rs鲺nE{k"is..ĸ_qŸw3P:;V6'+01{mTv<?3fIfM="q?"oĆ(1C'q+}|K/@ h++ v3MWo_/zEscp+WmTDJL%$S'XdJRX 7?) A *+C\J˲nQ"T'f$>';F_N Ҟ}3 g b/C}c A'fi*^/B#fRS{ih+HFCQw&Rc@`DeyעJlq2~x DG2+$9++nOM*ιt/ ,yO#`ܟ})gOj#u?}U +x/9 up< %NgqR2tf"~Yh l(_Vuk EɵTV#=@Nz乪 4˜Rɑ^89nґ$+yy誧PFOs~G/Q7)r$ݣMË% )_qX$R0;Ѣӎ<¦EW.c\ 5YpW5]v*ἵ(6H-Gm?r{qwoNNZ=%q$9V?_eP_9Bԍ-f/jmď9!":{r1[EP&24ltdBؚ[XNpgCbVWŁS5Ky,-O+o9Fs̾fGmW?5s*"a4lͼ/2$7i1ʍ4u/r 2nQ5_eHFOS<?"Xql1"y>;2oT2'5hy y>FƲa݉\FeS~I׃nnH;'`I.a=B` g*T4Gf7Q}sC6g[guJ3tÚ Gךfaqnr!K%ƠE*DYjA'J\n+~:>NmaÅ==n&>Oª^Vamﰌ${*#4ϛRQ]젶yn1?BtzRsKp##hLrŦ?Me1PY_3pR6я&w(|142&?p+E.ӭ\սmN~ r,z,f/Ҝܙ.bʵ5a"U+_;oK9^ ,uC$Ԟ:>ȒT>LQ~qX饲]$D$hI(>cy@5 a;Ωs.W.AC7SۊZt7_ 7ukL:R>eVI Q#Uo8ANv[.#Z#;ȴnk\.N]<=w+L\=AY) qad $)Þ''& zb"*6W_P.iXfXwFf!ܐ.s mc0[%<b&τ8D//{tƙO,=ϧ fӣPjlh%8"e$*wxՖos(|k6߬uy'?KNh4& 4?і2DP`\-'K#PK~ӈ@LbDdoj|ԵoD2o^8>X XW@ᶞIKYs)o !G<| :\:yo_Pz;/qI!1/I`P&\Seejミn fdžIM>*!ri<Q*tն"?,O7^ ;yy46̹vpRtmHޒjL'V?'H8Yy8 *(MqOKL{cZs`%}T u#Lěk\zj>BA#lt~n;J$U{-^m~IB n݇:}hij`sE숈pӒf$J#ViOOF-61w^f~C۝DtEPy^ 6-iloH{IFxv93@VNNBma;% ;/ڴ/0ꄴjm^R[||upVq6q</\9W:Ӱ|tt*?ϩ evo(p@QQǖmԍܟ9Ĭ@O*O_`P9$_+Zzd"ȳG/ .L!("׈e դşMz7i DFs;A"׃dͦ>΅/ { ; @m' 286pSO4[KV3̠^oB5ȈIo:Uז09~!:?߃2KSetURh4n H8* H~o5}L]]ȢL&s*赫?aY_8+|XMݙ c|o߹BTK:6{>0зBCc 7BN X{lKFőEH7OZP~L_YBN ɱ ]j^?}כf.^gLݓ,PSzG#Z^P-v*fYፄ,/X+ G|`/x7dWQ֦Tf \DLs B ߉#-K |= @BZUAHm֥բReo<ٷ]Ʒ0!?!9K ;bV;S 6X!#q`h݆SX>vذ]62pi4H 6=B0`z> H?k9D+Ntn%ۜxrS,KzӘɘ*EܿlTgB@A+ņL@R mU ByY~QܲbU6 [ℝ=ފG,uyn>#8D1f/8rM]F$6’[\j'ŖJfkZ@$`.;ė0Y<>]f.Tl&1b-"MMgD f 3m|MKEpwNV ]Mm..|Y[\u^ 6J#X߳ȝ+J#|Q2oҏsjoLjV8|q(&SX 1+ ܥш, [yQFuT5Vڗ,j|TfD0ΔmU@Gte1 xH8W,Ʋ4˻fb & c6"jT6f!lwM}ɋ8݁NYn,sƛB~{1$ ])ũꯨl%joLdu-iud~X5ὖ975OĘc!v*Əy N&-$(IGA⻀`8bp{!2Dc`|x&&*n@v׿tf=e8qx>*F~+񣋊=^%cEj}SxdSL22(V>ybYsݡi;uJ s,lKXzBpI 4XАOp&>XZ7CN]>`V>:,Bvd,Gj^7٢a?hY*]8rOeiUeS byVkⲕ*? V|VΈAP{(NAiwjn~x]#zUTxP^ ZRoXU(@аpq" jhAq^]!ת! g9<@$rh6ˆ@j$;o&a#/ (gTW/Y+z{Gk4S`$mY6AcO_p2JAYor])=\.vc$5gi8mSvCm{C% CI/GĞlO<ᵻcs*H쇂=G=9d/d: 6`hLSƜp<]W*j;h]?6%ysFaNer{Z5'ӗ7}-iڟ۶_Ֆ 5s$rJDȁSn' /RsA诅?&A-Pvl his,Y2%UVނI: Oag{ A_s|vXYI-awWʽ Ѹ#(Nb s{*1\:h_د;ñͭ@-a0:52 Mv(zX=&˝/8#Y#30J5KJgHͻbn" F}z GXR%A=8 \|ɐ9 >&!;IHYSЫFW zSj> tY-3Lxqz0Vq0ۨRcyM:nQo<n i=šLL%@Xpdo6v/n1 PQ]/_\|52Wz-.U# K A~8Rc25лwda,b6 Eײw!I.K /I[.+6u)LI3M[BDoJϕ;Xjznm& HZ2Gn ɥ>AeQby[aHīfʑwI$)p/afU@:!H쭘HCSXTT?:6ArVx>N ; -p5F xK^_1O$"Ν t$|q{MȦ)]JOʭF\q~o?s"q!6T5e%hnmF Rإ.9 u-EC喭6MH\D^& +cchf#4S!xv7R Jw>p# 0$DDG*r`X &cHH[*YvIu IwմDQt50;] 51b"|7*:}(B$C{+|bt\I<@r{&9b˘H)o k Ϧ O $_ .=_FXzi=(^ȶ0Sog`y U G~I:0Jgu[Cg ewb[oPz}2Ip[W%qnFT[UN?=YSTBis8?C [We 1\bqJhFX!yE> d'8҄Z \٥{)L$8B6yn+t%1q Tk޼o+Ӽg+VpflqG00z?iO髟ugw oD Q|A'{=Sr @}o# v,8xu?O-顀sgn4vF y KWaDѷ1F$Ц+^!9Z/Tu}y-S\eM:$ TaSMmW䋼#~QYx̎h/ N: \Q#TGMN1N|/櫺G)bppXeg pĂ YꗃI3i I^_nm_B bRZQ9Pz㤟HGճs"G۟Gm# ֏bEXp԰wx* 4jV;D~Ѹd6{3H#ۥ"Q #O)LmӻVGO,U`?i{n[OVXwqnbR 8&W+B!0?(lv#-sE7,zLw\}hFSw9`4ߨO"8a4r]Z4X>=mz]p:  CXf 5k2(.)h; [ ZD͗ŒVmE! t3K|εǷ m%ո/܋*Zw2!vdŨēJ#28_5Uj#qފVF9 |Q`H Y}o&g_oH(zF㩘 !ԡZF;! ++sCj2O$~~agm7xdgn(. xl@I1Bnľ8f8%fE%nWU a|Mf@DBGe݈!S' MY8$Eʒ.^3oJK"0(hoݙ(?,nD nj}_5rPMVFH9b6ӞkKVUiJ+Ϯ`"*K zIC~͉]>? YѦ=wn+-68׎ UP毃.4;BN`my ̊2 .Ő|mUlo"xk6)nX55(l<˴}Sm&zx`r'jg؄vT -?L%<أ(/p3YXdR"82~RK [¸߸;أOYNE+#3ZgYYJvKkW 6@.2!xO P0/&cM@H6&4YS<9OpJm ͋r]Xi{.UP%熴$!2ɽw+8LD.߈ ƂiΉ'"T~^io gW[7T\*]hWql0,Xd^:x8uK"ep~!BS˓LL_u|e٧7g1g߹Ys!)4-~ʸҁ5Se_1c0_TljaF>h_/wy$coVX c_&f\Q7 ӗ1_g2}++l8C@!5S\/K2l%̓Hbir}KyZPߥ"@nvW^Q=t/Q8_6$PSPݪ;,$0x<2oRR;8F^1ge* 65Cܑ2>Na{tmK0ZpW6M)Xy3jƣ#+7%Bkg`j\:`LG_HAȌ>>{Ĉ?D[nIwH:jGu1-M?PQ" u~\.giA c)x(>BY"5.W{Y]Y x= j9.H~ ^Go(=𯎺g ;wΧ%~ 9QcCױ6h$)꫸܉SA?/6c3-{Gw?5/"X͒lAڻK|r~kU3@tUpt,[IZ~Jk^ WQ4ŵaGdk\ ڜ#p~LJ5J*1"&Oߕn{|7YڈA1£ULVriC3Hen"}7[Қ Iߖ(a&灕ht ^ p0vԫBxE~[쥣?s2hD5PI̴E>_ʲt-Lv+ IJ=bSs|Bc e#ԍ&\p_:lleڇ0CTa{vnsApOiWsTj,Udc~~e@@J}*D&/>~O+Z.%K vK?-jV~̶ kqz +JD]0a6Eꭿduk MD3D1PU@}xQ䂴zr)}Q]+ߐCoxӶ|TclmgZ\{<%CbJ O-g{pB?BW|OY?UU[җϢU/bU`~q|+}ϡ*V'*>}KϦ镾}7Ϩ*tǍhum#s] +k)I2zc:aQDT+0嗈=y2H>r@eZC ;$d[2؄T7%ߥUۘ潀ؐ ]lDr]_C<>w]oW CX΂<;63aѣ.sY7 Z!!]-;^FU" }N?4kmCI&VhWBU? 3ۚ=-/ɛvs~B\,;!JiT7:_T> k~g)<ІT`ϙQ(Zs>F+ǯTp52w%* ճ4հ@oQ!쀽֣,U3/MsYᮎͱ_K(mfV7M#~0^[wvd1Az"WeYf X1oD>u -u0ٓ,(E^ x:צp{|5_g^5GHfx>tc_p˾)=őoJD!E(dLE˛< '$Æ\ ^,~GHَdb@x2oh 8+p^ YV`uB"WAuKvD7 hI߇7mXh5xЇqfvPixe΢ѐ3\"LR6eO憜. ƴn śFLtjq{A0>8U9zC/ݠBm$A(v+-:NZ=B{d2nw:t=+T> ~ ;:D pgg^=ХFg,nX4Ϙ {D^v+\8 $yL4v0 x?RU\"5EkÉp5 Ǻ75_زns)¥qǗfnnCm%cI}Œ/evC@f&ML"*i*%A~?E`ΒIǦ }g EEuiP=.uJI,'?!d~r]/j;َvZJJB"G_"9*Q_$S *̃wؾ^ WZuzi`&q] ^UuV5n=.p6[qYm=f[>yRfS@n8Y MHwBi蝪LIPu.mz@@?F)_3ȭ`jukZ0*s°~5P|_L"M|Qy}_(]e 7̧qFx]#İsԾ3 MlVۊ/.{;ZQq,JO^>Q95͐TIdyH]8bYQ{Kx\9G[Uo~ ,gsA@}ز==| 0K{0KdD1dōZbxEY8rlؖ; զ,6I1P B$ާ]e)^Aƴ/:DdÞ}w՞(/So52 YH 1V8؅1TN5$n/@}̋ "aR583 V3ݙm MyQ͂ͷ܉?]4QcTmR&`]f4UAeHd}RJ ?:gR ) M_ռ(BR;aTr`G :5'lScs1 [_olofFw}"+g (䷖@+)f;/HA#) ,%qd.TQ"Cǚ},)A NTV[xi:R?Mwfk`( y,T+1tZd(ҁy({6Fħ . ڐp2=/9wr sًhI02)c}۫I7cڗsҲFL'rZu3C3W:f,*r}뾹 9AN9Q"9[}0ɮE W$(!kqOd4T[oPz̘D7(R'Ǻ&?ذƄxsYhpem ݁ŢTס%G"(l&qzhyY! P**r[]wvpЩclLH I&X>!t^T¦]7"tThd"h@]){,N}N )X[IBc Myݹ#ɓ1?lwZ=[5)`jqrJvT8P"kJ*.DRZgwIvY; @iAWJ@7j f)@%YAV-L S ө ㊣c Q3rґ|auc?wª@Dn@В40q*`v,ŕP4[=m .{Y#E!_>3Ow/p!堀A{^-Sj" &_=>q+%eyI uM4RqX$p.ߩ4LQd00̋WeKz?5/_< ƟN3JNuK(bjC|T,,>Gw6Oc]J,{Ve0ȧfC4c Yjq0uAH*₈0L vjޑ%{0;:oi?-h `#٬E%ղ7^J/ZџIBtje|KJ8sZy4pDp!Ve!:V [%>j;in*gU hU#cE@!-uo‘7!4z,4>JJ2L=Jmu璍m7V*˲5{ ,F†n<ݦOԸa? idFπuS#3Fˇ _딥:74=[f7sK 1/ ,_( NhE1CaqAKކKHǀjh'̱%!D .r1!{|fqap9eH{(鍭s仮/}d:{ej.sؕ;3'`@K&B5hކ֠ +jڒ:zJO'_N-'4B e㌄N /jW4U}=0S}6nAfH& `~oIh)^/i1iƙUoE B^9/fҫ&g(~ wiwVuaeur,MZƠc>"J|wpGJmʹ{nH&9cChB4E8}5-jnFbƙpV  @Z'|2ʴMl 27TJfW2jg9ʓ0aϥ2J7&~7p`m[>0')]/I3{ʸUi1v%1Ap*ކ<)Ay̴|O%RH;|۰NxWHZ_ hrMy{LB#4Xp9"n0X/#+Mhx:*PRJMh󥺎{Vf#~>@4&| kʽ0J\:y!Ţ#'~5.&w_nFW2°J{Naƶ 9Z'ù_b͐C}p(`"P9 Fbv^(|(̎OmfˤԜ:$_W[*Ca{=xuי07:]NOLW+vMK]V6@TĎif>!{qCi(^N4MmK gS`UfAAzvtϽm$wyfhb\]˰7~eo|0"AL3qF+1›_u%JJpN{:|v Ɯ$述 R`ۼO ox7 6sdv̕05Q_^Bz9tuNfuԛ, Jz2xe@{#ixK{aVR/׺܆/;Ppzk_8Q9J.]GdOXov,)7 , q)B10cͪ Lcȝ>ᆒBڪ)U4 !4:Ъժ=-RB4̙:uBlTM&vНMY`;p-tvnv>E΀O}E93W$:;8z(KJF:X NשK|bӟYˇ3~A5$͖t(r)% Ln哩URNowtNީ=QOB:e`y&,lάzuW(}+'A6͌%^?^m" {93f&e R? v]g5eUl16fQ'CٺgV}S5L6f~No~NImINB .D1gټl>cĖcuN=O K?սrˆ{g`F9a_J~˭_ o`ŗT'v&0ljG<1Uz 6YvKfsvA>}Y0JxxKEnfxY6; =.+*Y#t}ERp7[gRK&3R}4#&(uf_X ·># cҥ؇d5pJkG;X L?,98:T2XpNaɑޱw5wŋ,tDx6f{f> H~_ʳ(+a9|Q]r lX n\s rΝvȋcpXNJ0;)XPl?jj8քr L1 ݇HBIѼQmwa_l&~twtiш9,~N PfT95){yس$-AMqVn?P^tޠµ4k,ҁ {PUMNm.gƐ]S}3csVҤ&`Qؠ`_6TLlwoK}兿^'$ W vZDR0vE)͌ I," qme\-T{ @n:4vbJx"[q)W!HѥyTҧ@lDE Ь00 >_dļFzݚRi+- W^s8~}ESTv׊y+g񫱛5-e7PXnj0>~V`y7XP # }E{c.E n1@6B0M'18>[7- 1{Kv5.e Mؓ6 "ETNe]( 岺,C?.H'9+wsK?7u9Uh&lq#y|'pбJdt?qTox/img/icons/qtox.svg000066400000000000000000000016171415623743500154330ustar00rootroot00000000000000 qTox/img/icons/qtox_profile.icns000066400000000000000000002072751415623743500173200ustar00rootroot00000000000000icnsTOC hics#Hics8is32Zs8mkICN#icl8il32l8mkit32,t8mk@ic08+ic09`ics#H??????????????????????ics8+++++VVV+++++++++++++++++Vis32ZHRNPNUOj>H÷;RY< Fǃ< E< Eov< EXRr~Q]< EPSPS߽< EPXTT< EJg]N< E󉁋; Gՙ>UMPNPNUHRNPNUOj>H÷;RY< Fǃ< E< Eov< EXRr~Q]< EPSPS߽< EPXTT< EJg]N< E󉁋; Gՙ>UMPNPNUHRNPNUOj>H÷;RY< Fǃ< E< Eov< EXRr~Q]< EPSPS߽< EPXTT< EJg]N< E󉁋; Gՙ>UMPNPNUs8mk8orrrrr[]AJMLZL`L_L_L_L_L_L`MRA `rrrrrrrr[ICN#icl8+V++V++VVV+VV+VVVVil32YWQRVV_SUY[ZTU\R`\~߅OU?RXL\STzL\TWzL\USiUL\ T[]WWVL\QMnSTu҃L\UMNg^YނL\WN~]QL\WNdnyL\WNelfyL\WNк`PzPl׀L\WNNOTUOJ\TTNRL\WN~OUUS_XTUUML\WNPUUPOUUNL\WNPUUQNUUNL\WNPUUPzhRUUNL\WNPUT[SUUNL\ WNPUP hSUNL\ WN~PUO jRUNL\WNKRQXvxxqSRRIL\ WN{lmli mmkL\UML\UO߅OUUT[ZTUUVRVYWQRVV_SUY[ZTU\R`\~߅OU?RXL\STzL\TWzL\USiUL\ T[]WWVL\QMnSTu҃L\UMNg^YނL\WN~]QL\WNdnyL\WNelfyL\WNк`PzPl׀L\WNNOTUOJ\TTNRL\WN~OUUS_XTUUML\WNPUUPOUUNL\WNPUUQNUUNL\WNPUUPzhRUUNL\WNPUT[SUUNL\ WNPUP hSUNL\ WN~PUO jRUNL\WNKRQXvxxqSRRIL\ WN{lmli mmkL\UML\UO߅OUUT[ZTUUVRVYWQRVV_SUY[ZTU\R`\~߅OU?RXL\STzL\TWzL\USiUL\ T[]WWVL\QMnSTu҃L\UMNg^YނL\WN~]QL\WNdnyL\WNelfyL\WNк`PzPl׀L\WNNOTUOJ\TTNRL\WN~OUUS_XTUUML\WNPUUPOUUNL\WNPUUQNUUNL\WNPUUPzhRUUNL\WNPUT[SUUNL\ WNPUP hSUNL\ WN~PUO jRUNL\WNKRQXvxxqSRRIL\ WN{lmli mmkL\UML\UO߅OUUT[ZTUUVRVl8mk%OTSSSSSSSSSSS/c  | K   $ # # # # # # # # # # # # # # $ c9SSSSSSSSSSSSSSSSSS/it32,UVUTUVUVW\WVUUXUVVUUVVXVUVXVTSUWWUVVSUVXUVVUSWVUROTUXU?SWUQ]RUVSUVVUSRUN`SUYUSVVUQctSUNOUXUVVWUPn߭NUNRUXTSVWUQhNUNSUXUPVXURbNUNSUXUQUXUSY΀NUNSUXUOUXUSUNUNSUXU_UXUTRNUNSUXUUUXUTPNUNSUXUSWUONUNSUXUTVVUONUNSUXUUVVUO~NUNSUXURVVUPtNUNSUXUUUVUPiNUNSUXUUUWUQbڈNUNSUXUUUXUR[ЉPUNSUXUTYUT\ˉTTURΰSUXUSVURxUTURgSUXUUUWUTOPTUNSUXUUWUPmSUXUUTXUQ_ղSUXUSUSNjӳSUXUVXUROQbSUXUTWURy ǝ}e_``bsSUXUXUVUUTUؚkROPSQOOZSUXUWURjeNRUTPSSUXUUWUPPRUTOg͘SUXUUXUOyOUQ[ǗSUXUUXUOOUROPQPOQTUR_ؖSUXUUXUONU QSo~[PTUPtSUXUUXUOZTUPq۔WRUNSUXUUXUOOUQbRUSbSUXUUXUO^SUP]SUOSUXUUXUOOURkQUP|SUXUUXUOOUQiSUS^SUXUUXUOtQUSfOUSΓSUXUUXUOdSUPPUOSUXUUXUO]TUNTUNSUXUUXUOYTUNWTUNSUXUUXUOYTUOWTUNSUXUUXUOVTUQ|SUOSUXUUXUOTUTYڍPUOSUXUUXUOTUPOUOSUXUUXUOTUSiRUOSUXUUXUOTUTVRUNSUXUUXUOTUTQtuQUNSUXUUXUOTUQQ_vPURxSUXUUXUOsXRPOPUSQPPITTUSOPScSUXUUXUORQTUTYUSUSN~SUXUUXUOOTUPׂQTUNSUXUUXUOfRUT\\PUQLjSUXUUXUOOUYZPTUOSUXUUXUONUSRRTURiSUXUUXUOOUQR_fbVPSUS_SUXUUXUOPU SSΘ]QUTYSUXUUXUOPUS[tQUTYSUXUUXUOPUTUpQUTYSUXUUXUOPUOVTUTYSUXUUXUOPUTXڈzQUTYSUXUUXUOPURpOUTYSUXUUXUOPUPOUTYSUXUUXUOPUP|OUTYSUXUUXUOPUSgOUTYSUXUUXUOPUSňnRUTYSUXUUXUOPUQxQUTYSUXUUXUOPUP_SUTYSUXUUXUOPUR`RUTYSUXUUXUOPUPWiMTUTYSUXUUXUOPUTRTSUTYSUXUUXUOPUTSZSUTYSUXUUXUOPUQVTUTYSUXUUXUOPUQwPUTYSUXUUXUOPUSnj[TUTYSUXUUXUOPURl}QUTYSUXUUXUOPUONUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNNUTZSUXUUXUOPURp{QUS_SUXUUXUOPURvRUS`SUXUUXUONUQPQURjSUXUUXUOOUOSUXUUXUOfRUPĈSUXUUXUOPTUNSUXUUXUORQTUSNtSUXUUXUOrTO\SUXUUXUOҲSUXUUXUOSUXUUXUOSUXUUXUOSUXUTXUOSUXUUXUORUXTTXUQrOUXUSTUR`SUYUmUWUTSĻ]RUVSUXUPOTUXU?ETXUVVUWVXUVVTSVYVUVXVUUVXWUVVMSUWUVUTUVUVW\WVUUXUVVUUVVXVUVXVTSUWWUVVSUVXUVVUSWVUROTUXU?SWUQ]RUVSUVVUSRUN`SUYUSVVUQctSUNOUXUVVWUPn߭NUNRUXTSVWUQhNUNSUXUPVXURbNUNSUXUQUXUSY΀NUNSUXUOUXUSUNUNSUXU_UXUTRNUNSUXUUUXUTPNUNSUXUSWUONUNSUXUTVVUONUNSUXUUVVUO~NUNSUXURVVUPtNUNSUXUUUVUPiNUNSUXUUUWUQbڈNUNSUXUUUXUR[ЉPUNSUXUTYUT\ˉTTURΰSUXUSVURxUTURgSUXUUUWUTOPTUNSUXUUWUPmSUXUUTXUQ_ղSUXUSUSNjӳSUXUVXUROQbSUXUTWURy ǝ}e_``bsSUXUXUVUUTUؚkROPSQOOZSUXUWURjeNRUTPSSUXUUWUPPRUTOg͘SUXUUXUOyOUQ[ǗSUXUUXUOOUROPQPOQTUR_ؖSUXUUXUONU QSo~[PTUPtSUXUUXUOZTUPq۔WRUNSUXUUXUOOUQbRUSbSUXUUXUO^SUP]SUOSUXUUXUOOURkQUP|SUXUUXUOOUQiSUS^SUXUUXUOtQUSfOUSΓSUXUUXUOdSUPPUOSUXUUXUO]TUNTUNSUXUUXUOYTUNWTUNSUXUUXUOYTUOWTUNSUXUUXUOVTUQ|SUOSUXUUXUOTUTYڍPUOSUXUUXUOTUPOUOSUXUUXUOTUSiRUOSUXUUXUOTUTVRUNSUXUUXUOTUTQtuQUNSUXUUXUOTUQQ_vPURxSUXUUXUOsXRPOPUSQPPITTUSOPScSUXUUXUORQTUTYUSUSN~SUXUUXUOOTUPׂQTUNSUXUUXUOfRUT\\PUQLjSUXUUXUOOUYZPTUOSUXUUXUONUSRRTURiSUXUUXUOOUQR_fbVPSUS_SUXUUXUOPU SSΘ]QUTYSUXUUXUOPUS[tQUTYSUXUUXUOPUTUpQUTYSUXUUXUOPUOVTUTYSUXUUXUOPUTXڈzQUTYSUXUUXUOPURpOUTYSUXUUXUOPUPOUTYSUXUUXUOPUP|OUTYSUXUUXUOPUSgOUTYSUXUUXUOPUSňnRUTYSUXUUXUOPUQxQUTYSUXUUXUOPUP_SUTYSUXUUXUOPUR`RUTYSUXUUXUOPUPWiMTUTYSUXUUXUOPUTRTSUTYSUXUUXUOPUTSZSUTYSUXUUXUOPUQVTUTYSUXUUXUOPUQwPUTYSUXUUXUOPUSnj[TUTYSUXUUXUOPURl}QUTYSUXUUXUOPUONUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNNUTZSUXUUXUOPURp{QUS_SUXUUXUOPURvRUS`SUXUUXUONUQPQURjSUXUUXUOOUOSUXUUXUOfRUPĈSUXUUXUOPTUNSUXUUXUORQTUSNtSUXUUXUOrTO\SUXUUXUOҲSUXUUXUOSUXUUXUOSUXUUXUOSUXUTXUOSUXUUXUORUXTTXUQrOUXUSTUR`SUYUmUWUTSĻ]RUVSUXUPOTUXU?ETXUVVUWVXUVVTSVYVUVXVUUVXWUVVMSUWUVUTUVUVW\WVUUXUVVUUVVXVUVXVTSUWWUVVSUVXUVVUSWVUROTUXU?SWUQ]RUVSUVVUSRUN`SUYUSVVUQctSUNOUXUVVWUPn߭NUNRUXTSVWUQhNUNSUXUPVXURbNUNSUXUQUXUSY΀NUNSUXUOUXUSUNUNSUXU_UXUTRNUNSUXUUUXUTPNUNSUXUSWUONUNSUXUTVVUONUNSUXUUVVUO~NUNSUXURVVUPtNUNSUXUUUVUPiNUNSUXUUUWUQbڈNUNSUXUUUXUR[ЉPUNSUXUTYUT\ˉTTURΰSUXUSVURxUTURgSUXUUUWUTOPTUNSUXUUWUPmSUXUUTXUQ_ղSUXUSUSNjӳSUXUVXUROQbSUXUTWURy ǝ}e_``bsSUXUXUVUUTUؚkROPSQOOZSUXUWURjeNRUTPSSUXUUWUPPRUTOg͘SUXUUXUOyOUQ[ǗSUXUUXUOOUROPQPOQTUR_ؖSUXUUXUONU QSo~[PTUPtSUXUUXUOZTUPq۔WRUNSUXUUXUOOUQbRUSbSUXUUXUO^SUP]SUOSUXUUXUOOURkQUP|SUXUUXUOOUQiSUS^SUXUUXUOtQUSfOUSΓSUXUUXUOdSUPPUOSUXUUXUO]TUNTUNSUXUUXUOYTUNWTUNSUXUUXUOYTUOWTUNSUXUUXUOVTUQ|SUOSUXUUXUOTUTYڍPUOSUXUUXUOTUPOUOSUXUUXUOTUSiRUOSUXUUXUOTUTVRUNSUXUUXUOTUTQtuQUNSUXUUXUOTUQQ_vPURxSUXUUXUOsXRPOPUSQPPITTUSOPScSUXUUXUORQTUTYUSUSN~SUXUUXUOOTUPׂQTUNSUXUUXUOfRUT\\PUQLjSUXUUXUOOUYZPTUOSUXUUXUONUSRRTURiSUXUUXUOOUQR_fbVPSUS_SUXUUXUOPU SSΘ]QUTYSUXUUXUOPUS[tQUTYSUXUUXUOPUTUpQUTYSUXUUXUOPUOVTUTYSUXUUXUOPUTXڈzQUTYSUXUUXUOPURpOUTYSUXUUXUOPUPOUTYSUXUUXUOPUP|OUTYSUXUUXUOPUSgOUTYSUXUUXUOPUSňnRUTYSUXUUXUOPUQxQUTYSUXUUXUOPUP_SUTYSUXUUXUOPUR`RUTYSUXUUXUOPUPWiMTUTYSUXUUXUOPUTRTSUTYSUXUUXUOPUTSZSUTYSUXUUXUOPUQVTUTYSUXUUXUOPUQwPUTYSUXUUXUOPUSnj[TUTYSUXUUXUOPURl}QUTYSUXUUXUOPUONUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNOUTYSUXUUXUOPUNNUTZSUXUUXUOPURp{QUS_SUXUUXUOPURvRUS`SUXUUXUONUQPQURjSUXUUXUOOUOSUXUUXUOfRUPĈSUXUUXUOPTUNSUXUUXUORQTUSNtSUXUUXUOrTO\SUXUUXUOҲSUXUUXUOSUXUUXUOSUXUUXUOSUXUTXUOSUXUUXUORUXTTXUQrOUXUSTUR`SUYUmUWUTSĻ]RUVSUXUPOTUXU?ETXUVVUWVXUVVTSVYVUVXVUUVXWUVVMSUWt8mk@Ald) O2 bg=ko9}:iVD4&s`M<*O W1|:WhoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopiU.:i 9)j.f2Oc)ic08+PNG  IHDR\rfiCCPICC Profile8UoT?o\?US[IB*unS6mUo xB ISA$=t@hpS]Ƹ9w>5@WI`]5;V! A'@{N\..ƅG_!7suV$BlW=}i; F)A<.&Xax,38S(b׵*%31l #O-zQvaXOP5o6Zz&⻏^wkI/#&\%x/@{7S މjPh͔&mry>k7=ߪB#@fs_{덱п0-LZ~%Gpˈ{YXf^+_s-T>D@קƸ-9!r[2]3BcnCs?>*ԮeD|%4` :X2 pQSLPRaeyqĘ י5Fit )CdL$o$rpӶb>4+̹F_{Яki+x.+B.{L<۩ =UHcnf<>F ^e||pyv%b:iX'%8Iߔ? rw[vITVQN^dpYI"|#\cz[2M^S0[zIJ/HHȟ- Ic2؆kFj֮Zu# ZJO^~41,ARhX!i7bMM#}]5o޼!fHU(~Izɫ;PL62J{TXl!dk$_TϑdgH#遆 %97 .`hFb`iҤIm5jtļUBo4 1s?@@,l͝%NgŽcNgaKfI~SL(CRkUz_/j֬Y1cw}J&M,:i7-5("sfKu]R>(8\vĬ'%ٳ䦛nR&R[ &qCu;OzD'2~fRxFV R;v̟9 8A>}?0@Bl-2 1b{U*&//#ݢEoo,uï,B@zTKpR5j\%[|k/J (//[{]k.*-E]H&i0W?Wv(W~'|(?J%ݺu{ilXglC@nϘ1zȑc}ۏ@6\Q~\āmC(@jرǏǘՔ͂ I8K*DxQ~,I={vXTBі&e97YVQ:thx5t TB*RAebTû ׯ_ͭZtS!P0CQ"y+fܺqwI;نp몛3(EI+Lro׮]*? Q.=y^_" jfLI"xq?(n'_J ˏﵕ9kHtA8"=?sMA%(m,P۰%f>ӭ#>}CMEEEb1 e1X -_.?$f!u5T]}Չ¸D qk(?//+PF/d6z4"n(<>|xZN.tsSBH  ϓ'O٭n3gNCOhgD c\㐶?H~-/ʋy9zܹEM6u?"`@aU8Os7ĞA8g^xaz:$r#<n{@A͛7^~=zdV @` ?Y~%mϏ ?9/-wd/P 29i|ܙ2A@?(#JrAC'3 %%KaÆj!۷7638 ;K2=\)J/?y.%{_^}ռEI&le"RnjeÈ@J?$7RrgɓK(2|%K8 Y-.Ő[ʄbW/^03# wN:HttfxfԨQ0pko~k-Z~뭷J\(jԨQs"O21="P!'_[ւ,U0O(w G~G5k׮-ڻwotdeIF.Mƀ46BMP/R=)I'Z*:qℒW(3җ^ziZV'hBA   )ћzU`Q~!Ћ;!^_A+ʟOk泮R͔ h8ʙ-\iQTǎ%([K9Ao.]@$V'AR D L'qZ|z0u$[kT6mMɈq@=XMCT5b.C RSL #D@YM u4ğ_V&RrIiZ0ø?qg+Ll)^,=,_#/,5%-WliqJn㰿4β[K05OҸaH.I_$nY8#`mC)=d([xLޑCOˮKE ~8L\KD} ;v,'l]u?AsnD@Bi WYnJdL}R&ށ"VOXgQa)=DرCmܸQڵK+=&8Ro qA} sۑ܌.%ǧT+ @^_!8NR ٳG_^+>z{(=m wEzQ[oj߾0`PPCщ"`%H4\$dfFLU}rjժU J=e*4kw41|:1c!Bg<ަd]$..ӇczozWuό"e!8d!?nݪd2d47 >2qC?SJ"[W$g}V'etoQٽ{?>?Ur*5-&<)̢bc`#K=^fZl tDAU{SmleI?uf1雉bcZx%lae0 ǴCᜱ-BҶmC=,ĬˍM9,4Eˤ3Ldꤼ04rZ|y&`/P>"OO8g#` c[h|g!<֒U[1( ,x.X@;v GiB: qwz(Y_z饟z$W_'xBSL~m6D+{.]~?dLA.x~7!^t@rz'W3>cE+Ok}GNƍq~a\=%7)9E9rD{YL3 [oŊM> @TIb ~]l=V}Q]vŦ$g*6,N +gEC`V^zꥆH|. *d?ڛ3x?by;MӦM E*7oKzp@Z@rZb),o#@SO)BI0S˪oJEe]]i5<wAw<"Xz[P~(-vzn^d<,1+9Tu]ɅjӦMz0sΏ && pzDǏw;BVTTs&V#G!_72Ihݺunc܀  Iǿeכ{c}\2`oԓyݒM;@`A=RI6(TٳU&MB) NY&| ;wyqD &Icn=2AeCfғnHV9-؍0^ #@Y8Wa}'7+d dǰs #@f 73["Ǭ!7psx(pH6Laq?!@LBd 'C"n,ąWC[C@5c6 @ba)@ɰx0g !ZH⏻0odk\ p@x~HnP &. \Rl%4$C5`vp! MIa@l;Y9ttʚa*Lm j?k^oS. F53;` ~ @Ӏ.)"қ{yƦr'A@07J̞FL@9^F$ 0v]-]. dh D$5*++#tPb Vեz;z7?H3-oB"D^ x-ڇ ]˖-7D) . _[O(efާ "VpDgG IgU0K%XZ1Ă \믻S+}aڵ˓f "@raۄW\w啠ˇ7nܨeٱ!ZHw؏%|g?it!p:zSl-~&宼J}O`v9df@s:iŋ5k{O¹}8Mȏ{Uv%C-$GLr(LDE Dzjܾ֭[f͚)9o>Ѓ.@g}V۷] #O ]0KqN 7@eG#CYPCxh~Xʝy T$( e˖Y qVX~";dCcx饗rqr[@f^^dN3$5 eWpÇ뜅ֻ;QeB=dUA /M?nmvpCHq/!C9#@/  GerrCJv0UN/`Q PSN՗xIw$5/}wu^1{2.%:d ~ǎ$0f̘35|ɬ!dϟ6oެ? E zj=gQ!ɓ#'L`XKH_{~L%%`?p@թSIM6^m۶jK+ø+~*?F 9-9wYwy'Q= z(UW]@jcl F֭vK$c-ZW 81hIX9~03b5JuA2jŇG9}lQ\؅?0c3n3@vg di?2q C,֣G(FeM( zK{n_OC\+7v]XrB:| o|ۯ_?uddĪ2CB܆ y,#IJڲ hl3SU=rƀk׮,? lLb ӧOW佈Dp-MA&b4P',f?6~Ϸ8%k܅< $_?#O5k/55Cr $.;`͸q4h u Acǎ47>N% 8$xSV)a ,%v҅6# ^u?|տ~ , ay0p ﹁ڶmnݺa $A@$)c0n)$0>@s.&HZCKe}a"@X X|CIH^P8 \֐#rj>$<Wx9y]\ӄIA#N$pp,("|  I;'Q"۷{N̓[lѧ$pqI֍dPDLl…Pl Mx3}ҥ|ꩧIg&5̆N~s&W\ǚ1c޸8gѷ#c! +>Gns/g!HXBaq8ȠATEEVd߁=>@WV//伓Rܹ8q/Z<\-6i$ս{w׿U'`jIx͗υ \ /WM~ ;S[X] @׎4Hvԃg)=z9s‘]~+pO>>L$`D-&9ف(#'G̙ 䠃LI1?}a t& @]$ObK.ָb.K4_2%c8nۇA.D¼@$Fq XQG4nXD-?܄!dc$3a:"8̱<&zY(MPE8Yg ?$!K@<#@ $G:d gHD bT8 CL|!=*uرB˒< tNVĂ-\~!k|ljAKzq /ΪIf̓`5`޽[=#j1`-׆{T6kZ{Ax/@Ћ`˶qFUYY! ?Hdf&e`ҏqH9±[6 t]6tt X#)? %GS$ֶm[=Fo[cjkUz™ -[As|{F+8y槿z  ͏?k4(ނ(K~F=CY"brN4h-834ץ^j8`PMC Q-Z`UQ&ݫW/}yr)oAma`ȑ$L7N5l0*1b/ 0=qDճgOxcz տswv$k֬3g I זDsHXiss4}Uwq D'IpY01^mjbhWXK(><aWRڵ3|z֭ի믿ۧOy Sy@!Xcf~Νѣõ_Ft~t XQG`ҤIjjӦMjzs1NNg68spw3 uƉ D p0D@@8 @C-QF"$eD PK `,$8e$!@X&K u <N#n0"- XG"?nyu\%oJ**_cj@޶m[՟gl.6m+[3I8EErknJn~87&I6S`O.CMY&cw ڛIIPᐴ䙰X$roVȔkRUrbLA1o I%'^}U]N}*T BŋɿVZV>}J/~Su@86wӳ-Zζwk^@l @%NV'90#Z{ĉl.sl֭_E-E!l:!i'-[lͥ6͈i?IXTsBxOL瞫6 T"`;謤#׼UVYn?pO޽j@2| S=%K|d|unu…E %PW58ۤ]fG-i.Q3f\+C;5PJ*T0 m5LYW02-}yCP/"ex#G/m|~Hw;V) |.sp`J^m"?qǻ(y=' ~c(řRipu]R3JZ$:Gxg YESOH[y2Cև-1`+E^| t&"(v9 B@yJ"@|rlYg D"@ D"@ D"@ D"@ D"@ D"@ D"@ D"@ D"@O?Q3tIENDB`ic09`׉PNG  IHDRxiCCPICC Profile8UoT?o\?US[IB*unS6mUo xB ISA$=t@hpS]Ƹ9w>5@WI`]5;V! A'@{N\..ƅG_!7suV$BlW=}i; F)A<.&Xax,38S(b׵*%31l #O-zQvaXOP5o6Zz&⻏^wkI/#&\%x/@{7S މjPh͔&mry>k7=ߪB#@fs_{덱п0-LZ~%Gpˈ{YXf^+_s-T>D@קƸ-9!r[2]3BcnCs?>*ԮeD|%4` :X2 pQSLPRaeyqĘ י5Fit )CdL$o$rpӶb>4+̹F_{Яki+x.+B.{L<۩ =UHcnf<>F ^e||pyv%b:iX'%8Iߔ? rw[vITVQN^dpYI"|#\cz[2M^S0[zIJ/HHȟ- IcK@4իzŪC, &E곫Ͽ́v,A A EX[ΎcKO%ĝ_^(\ѱ?L~dvL 2 ѽĎٳg;x`1@ c~3f̘jBm=F|?GH89.צLѣ (a-!Lʛ@F~[Ю;1:H*M.=C_ڵK J@ɢ e7֢}E˥H:M̷)='N@ z\; (/]4 =^@`8m?ojj:f'h qJi٨㿶,w2N`=7CjCn2k-߳SD&]Y<f5\8z ǘ (` 8~\ǚ?cMnq 4Z?5_ v0ܾ'*m[6a„&4uYYq =KCtu{R.*L@>bKD;v~#c @`d씟bmG5X—߾}{sMM͟SOT Rhg˱iӦ8|0Vb(H c¿wl #^ $`?gaȓNZиdXuf {v ~4%+ 7&m#?=+[!0ϛUy ͝;wkvf셿ZkNn˖-,7_!"/_vUP]ϟ4!\ky@=kVf Ɩn4a?KHpB6$|t0$_.uر#Gt:tAZnq˿/ 0+_UUf }lt|Ĵ@,3_S3gN^{ǎ[a A)eM 2+n[qܹs(yt. @8up&L_egF裏Ʒ0g<7qĊٳgK)vZYJ໥sτίY/ξ6[_O𿯹|F MF}{+7o^)ҰaܸquV5ߕQb]Ѫ9U#"_BjGm 0 ؜|;e6i6L?GSP):ہ=<{8AZ+-; X%l?[SS~`߆*t  Q0sU 0_%7~my(yT@6g޿xwZ?s:3+DwSSa@9!Q *{fX!2-sI8yӍF [7k֬e˖U޸q#c@ :fwnݺUilq+>3MO g޾6(O2>* H@Va enD+ͧ5OcA,laoKKK 5k֔0B@/^}lgrGaDg36Eo̙3; QrJ*ޜDԪS RtD/"Y̻v9Kè{-׳>[dɒQf  Am|'O`Y&C'oK{^x T%+m 8LD"Xl?0(;aЧL@ PO~lS?C'? D. 0g VS!~jm%ʟ)}79 |2$wT[[Ǧ7Vm4Y=?q(P @:{fv9ݿoBXO=O?9g@E@(^.(ʟ\eCCzm޼"z+ D'q֢zoܸWBq%`\{D~ܹsgYD=?x_%@#0$ڄB k0jwۼm&!a42!DHwS?Fw3+qJ:g g}?@?F<իWW^xqyهW޼y6Q>c BFz@$OmWnvf_sDSI&IoG@` 7X~_>/w緿˗/z 5RB %-}c)oC?_,3Q fB$@$ʘ𯯮~9(֢~[k&+KH2 *gf~uwwS,7SO=UD!$` I9H[5)5fg}M |!GH cLnoׯcq 00h~¨[^SDǽF'P& !`L6_es+L֎h͝;[Vfԩ0:2! C`8qI!~ޑ /͜9q3 x( !~Z/472eJw @ GfsӶlRaKf]~~` <;5'_gybϚ7C_sssBQГ4@pPcWI>qԨQ܄MO𫠡2ԯe u*C@DHGXy7LEO0G!~7nX/' @ IQXM%;\>O>dO<@"}z&P'pfZk(_ccc|wҥKeϟU_lzה&%C@ l(/~3f4@wujR]]]-_4 S__;/ fv9ւ{l?[>}zg^wO?;gK%co-yC$~}OFC5F}&;^|^|U/_k۟br$AH,>v:e6s+ǢZ!~O~$o޼gm۶jѰUcX=(` c(u^f„ 6+b )}to}l͚5`&ݍ7o[o"E6{mC[@1&OgGyVB~HD_5ڵޏ?g֭ǎu6}eJS>O m_ilGE Yzwƍ jįHɓ};vs|*ܸqŋ{ @(}f_^?vn8pӿ-s6mr'# [el2e7ǿ~ox J8shVˆ$`#^[e 26̾l@ 8(E~˫72ETKp];rc`@ ;E716}qú,OXnKVGP.  ,bҿEv#fe!7ާz|Ŋ!p]KL@(LplKYW.Ɇ}+ׯ_#(aҤI.hkΟ?ӄ'H@ RPQd 3[jժ>͛́׃!DO<{db y^Q߽aÆj޷+EA4-&^/z.O >=TW%7~7nܸ M2<-sYa/ݾ}; eo!-uub(E@GV)+ ?<м+ ~&3L}9sX(_7k֬2#)ٷuVg~{{vw\r]|]zݹsN* SƎ:;!X_`)(2B \~R=hh} ;{n} ~aB&#a>&zM6љ?c~RݏA gOO7vׯ_w.] | 7nxK,H`(Wת &ig~" A@b޽{>+K :MjѰiW2f4!?ZBQƄ} ɓ3)"K˔/MwyhkkFŎID\x;gB E@VS\SSwdPR=G >yW*-[~4_&TO(2cJPFK +eϝ;?Μ9  RgϞN"#%2,4nԩ޴OS$@ ZPrWyct[yŒ@vLv'OӧQT .xinS (H` =IF 73ǮN3jPPIߺuGqNrǎ-IQ:$:tț">})u@SZeU nK}F ޽{#ZKtdﰠ55eE! 9}ވ_B_&u)QY L2pg'ps|`ȇ*BtPr`oky;73'el؆C ~9c_&~ D৥|INRl>sϧAJS,fW~(@ȡW|Ya9Z)6?b  ܗ5KkD| ﻘ7-wttxJV3rtlmmu A@ȁV)ӲN-3٨`ѼP~ =92Ro>wQe|iNx7p|IwH %B (#0hL _υ@<[bV ˖+L`7nF-ѓ`ۣ`0B9fٕ )ocA("K>i#\vۺu+sS,ͅHHO@ ?g)$P{ys8{H[AI)6 ȝYqfJ ĬuZ'@' %@Q"ɓ'<0n1"@:C@25Zqu[5hU)0Ɨ5FgϞδc]zת*\&9¡iWqĉnŊ޾Q3@DC$ QG}䙚%$-'kjjt萷O4җЗpW, Q555y?;/.'9[fӨX^^)>:h̙3fGRtDi3۹ӛNwD>2( @.4mkks|/JHޢGzo={ӫ &xfB5ECV)JR h .xǿ D* %M0Dze˼Dߨ F j=B}"A@d޽nݑ*! ,pZ kHRX;wȘBt)wa'e@S DAЖ̚iii` Ϥ@HJOҎ hWߺMb2K/Z-Y͚5,k@PIee(*}B rѿ ;IJi/A !%@~RFZ"g< 0)E fӦM#6`w0(@ ;>ǾL˗/w+Wt3fT7Y}k.OP0rC>uuus"( u Fjn;$aA% u84v1P Mm Dvp <Ł @z:N@a eZ K|9~>p(̥~!PBqMd%ts7oޜ8eUF97G H{ {P]J hd od`<6򿟣ڪxR8HԿh5 G ޥkuy˯_[Go|oq…?bQ;1 $uʊ:6+6Qx4QצEjAAN h% P"qi hD9 Z 9_ qIKSm, G`8:xA 53hn@P FJP:#."ģG) 5 @)1oNadD @;ş@__ĿiG q]JF@[* @D7K" t(i _)ʕ+ݠ _B7(%c|IÝ?#'K0he@(PA@ٳ;Iv  @h)8LZ.D ݾ};l4LgSji@2噯muСUA[ @? ;|$I~8| l!0N0P|KH@'N֠#K.9 A@5%V\zm߾@I$J(@PL#GG}۳_ѣNI@PMi)' +}'޲z5-ra/Po  N@1N>};j3gpLB vx߿OV5):[B;\(T|vڕJ%@EG  @)z;~T*R1MvUUU@&O5C :$n|r7vTЧHQLEG /ƕ>l!('j`y/]ԍ?ޕ'/mcщ @Iq #U`AVrnԨQeN:<2g2Sz |9OH Xr1c7n\FKK 3$  @8)vSsι%KV0f̘;eۇw_B 8($D@Bڵk^`풧3g'E ZWJfar!0 @@hwF9y湩Sz<# wCJbT%(`PN@s%sHɳH!hllt555Q]]iˀ}gc@{A < HX9Pǽ'O)'NtNi ;I_svoP>$\|MK PWWЪ)Y)#,-ǤIJ,Gyj?(D @4ZG@$8%M:GuNI&'YܹR$5ڗ@B³uѹE$|G=E`GMvO{hb`t@KgAoϞ$b 4(&g@L^2g>+fߚ$u~k-#%l  LRr*M-;G/]mu%BǒMd. ~Ydq„ ,M20}PFcVgzBC(dKiҭb,i.n ~ ) bW\~3o߾ 7s:J4ώe )ӦM9ojbJU/pPoP)^1-s(_#GTL%XIV;wz-e,֚Ph=NS:d (,---nΜ9! |6P}P&4 CQrHLZ$ }'g0ŵgTX$T/ܿB k~:פ>[~9YK8,c^P'qVPgHC {mݺ՛wh zޞ !KeG;ʙ.r3 hJ@!q̝!gNpv\9 _&amqK6yϞ=M2%jN[t|駞Y;9A$?4)?%`N|]Z(InF@zi-9)pIʝVn1.u0Vu a;XRjԯFykRi HLK1W\E,m / p'L;xdc%Kd>9)ְ;:ZqW29 @zʣY~d{R H̚5-]k wd>/ou3__2RN APJM4eGz`ir8S3gzL9iN__#}eY}|if?>KşPR7R#~XAR^5/~?ykpTb,h#!m(D@) fJ_sܿZJĢZﯹ9jn_&~5,SnW"h*J|5n$thXƒ(R#JF4ڗ?"pE~5X>T,cN +"1Bo^sy _qPݿNy/ׁ?&eM>'N$.@+65,MMh$ HxD)qR39jEk_q% @\z*ȑ#<"?T+9PӗQi @Tz"ЈDd$@_Zquo-K"@HKONY`"Au_̩ U]JP ?)sZJ@HKO ,!099* @AظHHH@0 @0P @z٧6'd Ah&H(RPLi()xQBß. ArJpRR^2V3 ![YYmSQQm*#SPE9vf9/ԁ, @`_ @ؿAdH!PVV |mS__Ə7n~Jwuuy^᜵'@zs9|n P (HF&Mrqz$e U@)$Y @mmm^gz=~gnGcJGHZҞT辥͞=͙3M`ɒ&p vP{F3fܹs=%@ʀF$Y&7uT`"]<)lLS e@Pjh ˗;  ~YJ (oM%hJAiݻ= 4]@I FSJ@sk֬q˖-sSL! 4Š=5駟:Yo=@(=3%GB@&yuy„ 3/ ,*wŊ_.ؾ}(j!5"J C 0@CC[j[zg򯫫sﵲ@NRJTm۶#Gx  B@(A 8_v{F)%%tFJ ML$KiH$3iJ hIJǏڤmI@D?MJ huIJ 0qĵ+I}D[K }O#D@#d OZ0a$]q' :2L"3*X hI)-Kr9 -+Nt$͈7MM`? "S4/Iҫ HE ZAmRJ A @P)v===ÜOJK< r]E !P5@ #Iʍ -(jRΝ;l۷GE ZAmRJƍl[ -(jR%_|9W+Wgi6K }CRDIKRlP֫' PЋ!Ο?t$)ɯiD @ڤsܩS@Ӊ]ە' @:&$N5L!aUxX&NJS.*&]MMB PbiTծ͞=s5k7@y.^S0ZZZݻ)MC @oQ_Khhhp̟ @$% [v)nVhe 5N! w z-_V),Bh{bK E@9!>!h@vP;xw̙nŊnٲe?HlM;֫ВD3K }CRN@~9իW9sx^Wfխ-ZM8577{{hi" @~QwL|r䮑wԒ} R@'|Y.]Ju}PGM͛7O ?1ZR@uE࣏>fJ`8rp ˟!5oٲ3볼F#qޣi$^͑$O@ee~+W撎@?ࡇL%0\B2kS=/)t f$GV-$A@?՜FQGh#QC 8(Ȏ׭[M3 ІGH]@&vP~jh[5M{24gΜNi4DE (>~kkkGٮJ)S8-$A@9%溵m)lM_p|%+^25'NRd!A@9%FEJ[;?$ ` T[A $mΝN>֭s/v'NLS~i{@b[GjY)7R4ڕ=̙3=%`Ѯ,9A_[["B"۽Y TSք R4SJ@WWҷ˗/;vx kkkh+WR4`@ظb(C Hȕ? ? E)5%R|$Tq%ZQ, WJPr\]kT8) 痶+$@r'KA<% ď(ߒ+R2Pd0qP\ccP?= PBSLq%̑L ͽ_a$Ov /4i1*wTuw+`޼yϘ!1cm_#‡b K0K5kI)hΝ̩* U]!;C@nԨQ@$n/m ࡇ*m|E@ iR67(MOZj*''% Pz+Wtcƌ)}j(4^DIJK@`/-Wrs$4:yGR. M2=i@yy/y@(Dp^pe˖(7n܈D<P΃OEs?*lK`ݺu^-F~@jJR%Y}Q+٥#VùtLP΃OEj͞RYq9RK@g}[? |o8 UW` =G@4Y$E/)W0 {)hzYv{ꩧرcπ+ <`qjhŊgA g@V?nN4$PgnnӦMރ쭷rǏO7Z! _ @ɑ@2cJ 2oC& ׻'|M>݋j 4>0(NoAzjo9Ӷmܮ]ܵk C.-c˖-h@wz SKzsSLq;wtǎsBSK@19:ROZQBC+++]ss{ݜ9sܞ={ܾ}ܩS\gggІ>fr˗/Ll 2 FLW" j9O( Çݑ#Gӧ˗V@}}F .F'UA.IG\fP;Yh'O8Μ9.\)~z:r>Y4җ׫ ݱl @,-9Z͛M h*ٳҥKի[\{{q n߾$$B+^ SN}---Ni?]F$+=u!)ٳg~n޼);9J) A J$e֗ _UUr/J=E]P$x $)U)|$%@ Q"(Ս@`0(QHȎ Sӎ bCk!@1%ӎ bCk!@1%ӎ bCk!@1%ӎ bCk!@1%ӎ bCk!@1%ӎ bCk!@1%ӎ bCk!@1%ӎ bCk!@1%[uvZgLWr @p(#;uToyyN+羾>włB@q 4!E }1jԨ޶N+gSd._\]p\@ .]tÞw}1jjCWg2nZӈnܸQoG5@$wގ.*o7oĐa`=n,_mw)?pbIqѣxLB0 5twwC"]v_zu i?cǎZE `[n3}xӊ| >\(\ DFofvv}Ҟ(yPFnv(Xo~\2r8@L{GcFBa+N67&X"QMۄN-9PkX0F."D`6}&TW^y Gbipj ёy7_uIr! կ/\Pms_G6j8f(yt~yԙ3gZ/ ɔڵk7߶~?W}? <Z _݀y&}8p* T0Kݷo Po#v.l@(1},~*P Hܞ}XM~g=fU pנ ܔqUUUҊlG-W Ix|s+"Ϩ P&~-[T__M ^f \{a Z @d왦M{N!S޾/X`.oJ(a[,`m/`j`M,0E`y&٫{?*R< z8ky{ {veqe~:oN(a3f(WEUY%7gUP !&OsI{ݴ0&:H"θ*eI!={v4g>@|C4Q3 @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @y)[a|oIENDB`qTox/img/login_logo.svg000066400000000000000000000052561415623743500154600ustar00rootroot00000000000000qTox/img/others/000077500000000000000000000000001415623743500141035ustar00rootroot00000000000000qTox/img/others/logout-icon.svg000066400000000000000000000060661415623743500170730ustar00rootroot00000000000000 image/svg+xml qTox/img/settings.svg000066400000000000000000000037731415623743500151720ustar00rootroot00000000000000 qTox/img/settings/000077500000000000000000000000001415623743500144375ustar00rootroot00000000000000qTox/img/settings/av.png000066400000000000000000000143301415623743500155540ustar00rootroot00000000000000PNG  IHDRdIЃbKGD pHYsu85tIME ]ʜeIDATx\{}CV{Hwgϔc &C´EZ2ɴ4$S( 6`4绳}'[>ֽd=NݕmVAC>csߙHZ~~$M$M$M$M$M$M$}oimmp8gϞկ ]z5SSS@$y0z9GKX,1=N٬(P x9a4qPXo*5}t,YXMM͡H$@e˖yDwÆ RUUE $PUd|BB|Mi eY, A<8fq$ ,ˎXƍyUUw.\P. w˖-zaL&, a!I^/)+(PU3f`Ywst]ifh6mht/ vի~EQf(ǁ2P{"VfyS \Lf}5 <#~ѣ=z^YjEԈAӴ)!Y4Ms4mBY[KkRyʕE8خV y[AxkY-S_D&ΏFxwk.ܹ]]]$I$ fE]JLjC9@m?[n|$Xi0 x7waJkoW6 YD]`fر (ىR]&!=P---p:dWYnaeO=lقH`03fӧOq8woߎp8K<4=4q( գGNL#ȟ\&`5ҕ u]]]شiBDQĢEPUUUfAmmyر>~bHR LJ y]p!@f"ƉF-7|i::a LbD"hllu]rFH&HRy>555Xp!f̘{ַގ:q\l`ڴi( ǏǶm=ߏ{ /`J]>/SyWܪ0kK/sUrȑ].׸ja` x 70<#6"vHo>DQnL:ȑ#VB!:u }}}Jb ittt(,^BwaH12Fݎb(%aYEQ$nZWeePF]ױk.@mm-A4,ˠitP˵Bn6ԀeY8pk׮,e[ CQyx@t6uP/j6 @ a\UGG)Y5r BI*BMM jkk1<0)o6ESSE`@ƫE{`L S\P(5 MAʯ( -e144UUp 7ĉvNNv iQpϗ`Xbt]n/P`AdlrDH@oii9*TU5bUU:$I7 EܹUUU aΜ9… /|Xp!,YyA$Gww7~}0 ^ sdd_W WDp{EEbqYO'`"%Vjjj­ފ:tfrryR):u {/~ hkk3 -X%Ce!L"H`hh$+BI ǎ{#=JJv@](DE3W5sLxcʩ,HR汵k_w8V8=N$H&8-\&†Lv']Hdncc#Q[[*~Ӆu8p8e9&FT*={ ###p\ܹsq7WU0? "!bttp ܹҸu*v#:HPn4 *Z%kV^o;A0;+\.\.gl6t:t:xĉch =ϼ_6m /?nx<"R,$!N(vW*5ވn;v.&p)8.]Sbʔ)P4ؓzjiA:kMjezuL&122H$0~ ݥ{vJ3gάt:ʶQ٭Nvf2n#$l ܿn\h˔TքҦDnkuuEk'md2Mf7ּU,G}A2b+|qݸ,066V V9r6iVnDQ@"@L&L&t::0:td U16]?k1v IH*az*#1q,XpjklG2D.3#|g0 3Av ؈f<ϴ-Z$vvv,Z%*I˖-~V\93H cttcccq2Ly8PeSUq5ߟ|18Qoy֬Y/B~__Ybgi@O?4{Gn喧[ZZCCC ?}ippP--$iӦsq̙3[n}_[83?jhh/ `dg?'_|7&%B>퍍omܸ!aa>}p?/o߾t:#*f s}L$$I;]+h4?v؎L&3KFR={gҥ_ȑ#1MՆ|k_;uԿkkkkpd2~WYХED$&yRD+;Hˎ^KƶmXy6͌b뮻~/E\_|ϕlذ{ 7pݺurM7͉ R~S f Qm(b```__߶wX:,erp\ʕ+GN'={샽ūanCWկެ*mB9s${)R- bGp6WYfFݲ,/r,ܝw޹Ph+ yxDۂ?^s5sΝ;gylf.7u]^$IOΓt:]8w,$6mSLA0ɓ'nz L/DT x7]wuUSN5 ;[n^M Qp8i366o<#?!N^yL6 ~UUUTuub]d2)]\qx<ז/_V__A022ϣ;i#"n݆ap6v 9𓎎`2\T,q ~?q\oRh<7_eY[n-x5Ľ5J+KrVWZQޭ[>s$j|"WhOe/NHa+w%ڐfϞxhVL&3 X,`afRI'.0P,<<](Ş>sܙP"1% ,||pL&sͲ3$=J v J]=k;q>_òe,ꊢ"ks2_ɔ"e4MgvӸqhVZeՓj.BU02  P:6ZH$RJ`%'qK 0q..!(Cus%+8!bhfggAXX\. hE8s4M(yfX7??Ѐl6lvySc4i"(ީ*.V$8,˂,4 $}VR(CQ8+uX U3PaʤHUd2qqexգJ4,0*n!B|0J4º&y۶a:bf?N:D!"&_L\׃aPU*/EA,0Uub"88<QD:a/8 Wrue}8c CUgIƦu׭+s^u !c uZ҄Dl )C7&N]`. rApB)C&A{{{]r]PZ} wJ\WRöW!"$"@%0e)ȇD৪X HH rє.?ʄX%yaHhhh(?"Ôe6>SPJL&L&A nd!FXc BT:Q(9u41<<n~(x<IVEQP,!B7xPsss:7n`dd{DZo>K.!a׮]}RmOO\Yc{۱cǞݻw~៍O#nn$133+W瞃(<Νiعs't}esjyLOOP(,ر#ׯʁɩz"nnh1MgϞ :|Z<眃R Ap_}~4JJOLL'0:̲,X֭;J)|!뢷={<[/>©SJ 28e瘟8`!s;w@E2 A:uԩS굲0w^ 8fff*R4t]ahiiA2(mۡxu1444~ȑ_2e< ,6qyN;ʯEQDss3JA\.Փ!/^DOO*D"-2X.򟚦aii< D:>8lۆm0M۶m+\ׅ(} ޽{ :.*$6K0>""g#-%LH(1 Q!I"DQ fggmغu+t]ymRȲ EQ( (rc̾}/U8sΡ=t$A@d.//knnnX,.n) ^8dxdsvh - ._yY[Ȅ&_xsΧy8tchm̈́p@DS+G4 B꫿pgA2<>׮]Ovx<{QW,4 4772XAqرՓR%+,cj5Z`:TMMMX^^(PU5 iXE~V)6M3\,+Jm`%kX6-[~w{zzv#;bdYFkk+4MC,  H@4B3ٳK.):]('#$*Ї'߻ߺuԵzW$kkS$i vßӔUSض]W__O3L2^:fsmmmɳ}M6eA955uȑ#n߾}3";4ūWzf˖-CCC'^{-,,hؼykρxϟ9zo|gΜsgg珺/]tDT*9xcMAgtt1>::[ff<8JZ*^a'4ͅ'N}bN[EBuwC|`eYa4eYL/騚>s&BcSSSkgggfgg]yVm+sZIENDB`qTox/img/settings/identity.png000066400000000000000000000112431415623743500167770ustar00rootroot00000000000000PNG  IHDRdIЃbKGD pHYs^tIME CiA0IDATx[i\uνt*h4Ș " .;rHNJ+H lRT HLx6P $,!!FF3l={ɏ5ĢoLtO~<&h&h&h&h&h&8гKi]yA|{GokDgv,k?6_v< {Dp$5{T/u#c! uߖۚ&w~A(%9WNꈭ0QDWoڱqd'w-\sQHچr WAJ{xMBNjI& $6rH!t1 ](N599u!OvU&7 hӝuGg6A7|↽f .\}2Lx v)щE91Vs::?~#jLS^#[GF]﬽"-~CXDhD_7ͺkr,=w%V<g,[w-|R#!.SO>6)]mg}O*/7~mOl("-JkA?!/X*֎r[ wN9/i)@kw[w)6 Ns c3:W_V˳ʇkSwt RZxACTBb j( !87— y7\X 7ҩϠAPDj(U.4J-oQy!!Cҧ FQLFJmڙ(}RPryHAj%;}enş5G̱>\.w^&t+y(21Q<0%`|l>)hЮrAZ=5Pҝ\ ۤG!"T;Ya8 !=ukkΟ>;uJ@ qDOaLDO)l<.\+NG.; i/lO0i/vIU:s_"bH 3KBzy|d#B||4Q80) aMp]0P0"A A=fo???O:B`$Ʃ$qxX T54はMgS2Pޑu^hb+\enbnj G3A 1y<08TLJ1k9lH*h.wCܝ'%!s~BF l.`O=7EgU#!=S1؞!&P^Zܔ$D49 !@ 1 G$v{lwwo۶M;U5ڼ3(5PJAiNǒ-j>%N}@ոæ<5i1(D ;!qtKv68 \Wd-AGAB-f_!ɥrSSOo?poxByًBԒC.Em?",Ɉ3` CT&_0Z0 (x|P7y;g&qN9alaua2""h:+gœ O Nbl~!"|OўҚT#GaT8vSaZ8:9Y=9Vٳhl':Ń#=a]''6Nnmo+vtiE>17V܅N;93Y PE^uMW;Zu˵M!x$xt':[NQRf!7p<^+5F,"p,ОLIp\$WI s|R~rg|{P 9Y^5dqBdy/Y;::BDdgRmସǒޞFDIC͛J?8Bołwv']ig!䰶d͎@dos^O.:qGQFB wq, n 3y#"-  ${.R&pc8t̕0LUDmG=LahW_e7^0%z"B>̎ۉPdFٱ+ , 2ȒBLJU4ѤRT `ǍѨInjFM4DM4AIENDB`qTox/img/settings/privacy.png000066400000000000000000000035331415623743500166260ustar00rootroot00000000000000PNG  IHDRdIЃbKGD pHYs  tIME 7c54IDATxmLSWZ:h*(2$FQt(n2.S'EMDbts(asaS+BƜYDDR m5jKmz9ss=^_K!5&T(}&_ G 0*%O# jE#9e"FC`!౵.P (A*Vxg0l(alN qVMǐØ-**#11O`SJjҒpuO A8rC$6:ՙE,ݓRo+Pqb^gg !&@zNLJ' '$$Ԥ^`QnCCËO0+ ybz:>lذNWrYY' pDAA2 RW,vyIځ6R)t8:b[ZZFK\)j9 Kmv^=[p0EWVV.LVeDGGعc%rIv'O`XAYRf7oeޮKvʼ]l$+ #G\YCYCF$HH hC'6 bcchC˗/LJ Ν;7.m(U{vFZ]+3(6wyn^ s6[c,j?j%R qTox/img/status/away_notification.svg000066400000000000000000000030421415623743500203510ustar00rootroot00000000000000 image/svg+xmlqTox/img/status/blocked.svg000066400000000000000000000012301415623743500162420ustar00rootroot00000000000000 qTox/img/status/busy.svg000066400000000000000000000036121415623743500156270ustar00rootroot00000000000000 image/svg+xmlqTox/img/status/busy_notification.svg000066400000000000000000000073771415623743500204110ustar00rootroot00000000000000 image/svg+xmlqTox/img/status/invisible.svg000066400000000000000000000011771415623743500166350ustar00rootroot00000000000000 qTox/img/status/offline.svg000066400000000000000000000011551415623743500162670ustar00rootroot00000000000000 qTox/img/status/offline_notification.svg000066400000000000000000000030421415623743500210320ustar00rootroot00000000000000 image/svg+xmlqTox/img/status/online.svg000066400000000000000000000007511415623743500161320ustar00rootroot00000000000000 qTox/img/status/online_notification.svg000066400000000000000000000027651415623743500207070ustar00rootroot00000000000000 image/svg+xmlqTox/img/taskbar/000077500000000000000000000000001415623743500142265ustar00rootroot00000000000000qTox/img/taskbar/dark/000077500000000000000000000000001415623743500151475ustar00rootroot00000000000000qTox/img/taskbar/dark/taskbar_away.svg000066400000000000000000000021451415623743500203420ustar00rootroot00000000000000 qTox/img/taskbar/dark/taskbar_away_event.svg000066400000000000000000000021451415623743500215430ustar00rootroot00000000000000 qTox/img/taskbar/dark/taskbar_busy.svg000066400000000000000000000047071415623743500203710ustar00rootroot00000000000000 image/svg+xmlqTox/img/taskbar/dark/taskbar_busy_event.svg000066400000000000000000000047101415623743500215640ustar00rootroot00000000000000 image/svg+xml qTox/img/taskbar/dark/taskbar_invisible.svg000066400000000000000000000041501415623743500213630ustar00rootroot00000000000000 image/svg+xml qTox/img/taskbar/dark/taskbar_invisible_event.svg000066400000000000000000000041501415623743500225640ustar00rootroot00000000000000 image/svg+xml qTox/img/taskbar/dark/taskbar_offline.svg000066400000000000000000000025061415623743500210240ustar00rootroot00000000000000 qTox/img/taskbar/dark/taskbar_offline_event.svg000066400000000000000000000025061415623743500222250ustar00rootroot00000000000000 qTox/img/taskbar/dark/taskbar_online.svg000066400000000000000000000016561415623743500206730ustar00rootroot00000000000000 qTox/img/taskbar/dark/taskbar_online_event.svg000066400000000000000000000016561415623743500220740ustar00rootroot00000000000000 qTox/img/taskbar/light/000077500000000000000000000000001415623743500153355ustar00rootroot00000000000000qTox/img/taskbar/light/taskbar_away.svg000066400000000000000000000017621415623743500205340ustar00rootroot00000000000000 qTox/img/taskbar/light/taskbar_away_event.svg000066400000000000000000000017621415623743500217350ustar00rootroot00000000000000 qTox/img/taskbar/light/taskbar_busy.svg000066400000000000000000000047101415623743500205510ustar00rootroot00000000000000 image/svg+xml qTox/img/taskbar/light/taskbar_busy_event.svg000066400000000000000000000047101415623743500217520ustar00rootroot00000000000000 image/svg+xml qTox/img/taskbar/light/taskbar_invisible.svg000066400000000000000000000036641415623743500215620ustar00rootroot00000000000000 image/svg+xml qTox/img/taskbar/light/taskbar_invisible_event.svg000066400000000000000000000036641415623743500227630ustar00rootroot00000000000000 image/svg+xml qTox/img/taskbar/light/taskbar_offline.svg000066400000000000000000000025061415623743500212120ustar00rootroot00000000000000 qTox/img/taskbar/light/taskbar_offline_event.svg000066400000000000000000000025061415623743500224130ustar00rootroot00000000000000 qTox/img/taskbar/light/taskbar_online.svg000066400000000000000000000016561415623743500210610ustar00rootroot00000000000000 qTox/img/taskbar/light/taskbar_online_event.svg000066400000000000000000000016561415623743500222620ustar00rootroot00000000000000 qTox/img/transfer.svg000066400000000000000000000027111415623743500151450ustar00rootroot00000000000000 qTox/io.github.qtox.qTox.desktop000066400000000000000000000004701415623743500172330ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Type=Application Name=qTox GenericName=Tox client Comment=Qt 5 based Tox instant messenger for secure communication TryExec=qtox Exec=qtox %u Icon=qtox Categories=Network;InstantMessaging;Chat;Telephony;VideoConference; Terminal=false MimeType=x-scheme-handler/tox;application/x-tox; qTox/merge-pr.sh000077500000000000000000000037141415623743500141050ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016 Zetok Zalbavar # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Script for merging pull requests. Works only when there are no merge # conflicts. Assumes that working dir is a qTox git repo. # # Requires SSH key that github accepts and GPG set to sign merge commits. # usage: # ./$script $pr_number $optional_message # # # $pr_number – number of the PR as shown on GH # $optional_message – message that is going to be put in merge commit, # before the appended shortlog. # set -e -o pipefail readonly PR="${1###}" # make sure to add newlines to the message, otherwise merge message # will not look well if [[ ! -z $2 ]] then readonly OPT_MSG=" $2 " fi source_functions() { local fns_file="tools/lib/PR_bash.source" source $fns_file } main() { local remote_name="upstream" local merge_branch="merge" local base_branch # because assigning on the same line will break error code parsing.. http://www.tldp.org/LDP/abs/html/localvar.html base_branch=$(git symbolic-ref HEAD --short) source_functions exit_if_not_pr $PR add_remote get_sources merge git merge -S \ && after_merge_msg $merge_branch \ || after_merge_failure_msg $merge_branch } main qTox/osx/000077500000000000000000000000001415623743500126345ustar00rootroot00000000000000qTox/osx/DS_Store-DMG000066400000000000000000000200041415623743500146420ustar00rootroot00000000000000Bud1 ob PctB  @ @ @ @.BKGDblob PctBb.ICVObool.fwi0blobxXHicnv.fwswlong.fwvhshor.icvoblob icv4nonebotm.icvtshor .pictblobbb qTox 1.7.0Դ+WH+backgroundImage.tiffԴ+V  .backgroundԴ'Դ&,qTox 1.7.0:.background:backgroundImage.tiff*backgroundImage.tiff qTox 1.7.0!/.background/backgroundImage.tiff/Volumes/qTox 1.7.0MacxH+DMGCanvasTemp_qTox 1.7.0.dmg Դ+Vdevrddsk TxԴ&2EE^Mac:private:var:folders:9y:zl96m3v94kgcc6wxx0tvqhsc0000gn:T:DMGCanvasTemp_qTox 1.7.0.dmg:DMGCanvasTemp_qTox 1.7.0.dmgMacTprivate/var/folders/9y/zl96m3v94kgcc6wxx0tvqhsc0000gn/T/DMGCanvasTemp_qTox 1.7.0.dmg/ .backgroundIlocblob .DS_StoreIlocblobInstall to ApplicationsIlocblob'jLICENSEIlocblob[MqTox.appIlocblob[j README.mdIlocblob'M E DSDB ` @ @ @EIlocblob[MqTox.appIlocblob[j README.mdIlocblob'MqTox/osx/background-DMG/000077500000000000000000000000001415623743500153605ustar00rootroot00000000000000qTox/osx/background-DMG/qTox-DMG-bak.tiff000066400000000000000000002100761415623743500203330ustar00rootroot00000000000000II*O#!P\!@bb"hDQd]/LfS9m7NgS}.G(\oAJ4*!hU lKdUj$fQT:[$٭>]lMpR6}$Xl& bqXf*ݍlM~N/;%3&XK1M4,7k6|[^al}px\=#׬֫U5:өz:-rvif1|tc-ǶL3: = J®^@\ CD\{đ?v}EP捧ϥ m>Ƌ ѲcY'Dzq4M5n׉CJeݟtSFC_W%ݘ1)3s{1dZg'p4GI=OY! l<׸福gwr_&6QU}wGmU{yěQ_c||f'F];x!"vQk'K-'Upœfm ~=H [[m >ogCP:0搥PmL7_tCCۛ'G"IqibgŶC>ԠpK4G3]d ET8ÅE ݤe-> "~݇ WASvhWHғR{;CBTE"$N`͸Q.98T(' S6(VSL+'&VIHca0V.:S6; +yoDLhy͊@Zu|3jT !A]8!TƬO0WϭCTᘪ6&frB-Y?(図W_E2^l^ƾO,δj2+xȃ/s.~rm 5Pn0hmP.&N>Np)HpO K䫲O$BJ!AAo a ˸@l ̣ ˂,C4ͯ Ⱦ0Oh6FIp nN*~~m;(РQ"GmKQ/FQΰl\pT ",Oʌ-" [ Qq͔͙ ̡ Ю#  F!`%" 01#˺k$QNы&.}^}Nh.ToO:X$OQ7n,QP[Jfd萄dwK2,c&xZ,jr+d)vװ׮?Q%./0 %uL`r!!33/324 I4sJZP$2U100vrtR$+Ң r)+* Y(Ί6r=%ovOS ,N T6Ki~s4 }710GGS@/@0R0B (A/.Qb13"D/# `TY!?#%F``(r/#4TT0P}Bt P1E_`G#ZAHR$aD9t35̤3OMSiC@M%tjLI)Th%*@*]X[5iZtOcꔲ}C:mzu` Ur튫d_ fَ?=^eL%cZd}%Sl]qO-)cd[]uT0u ~KG@-I~o'z^C{CnnK,N24p * ) $D?03n7q)AmlFxF \ҸH Ȱ$jĵ)J-<~q/Ry32<2L40rjR_*J\!O7#1-$@P43tCO >ӄ/HmBS47MM,.oJ, ;,:u\q+Ǹx/[@uuUҲ9dY.c2t! ) #luG,.=7*ۈY5j`:L6F*osqM44>1 LG,@YI5fiy^Y-SjL7U&ƛ35&x5ޤ\o8$م&TsΒ=&ǎ|Ah1%* Pvi>6&d `V"e&РPR@.zQƨ!"*r9(" tPΠƇQk6q~HY RUbPs΀ ]5CB69V*եٽ +0¢>+ sO9 jwӊVZ,@BuNOx5Ao8GWR!֫I;kOZKPZCKd-fRi܆QJnbKqJC^Qo ~Z¸UBP(8DžGʗsȵcF}:Q[瘒"]Bz7+zc,v*+c+=lۖ.KΨ8X5lP( o6:0#֬IUI rbZPY:!\x'C%u\,%tyKq;w\uoǫCe}^2%T^g;ss %d*dWW¡yѡX;Lʴƒ8p߳oCM~ lgz)eĸE|Z#Pl5Ϻc])>r=H+~43<ۑsf{ܷe Q;"m,3X,6 v6&v_y6[#y./'Tn<2"vlx Oq|FLxba_lD؇rk!<=]5.%NW'Bf}qʵmy%^!c%;SwiWwN|{r`/{d=3NEEttoI+tk``"`d-|(╧/7Tc,ӗϧLw<|G^Ôҽf`m:{1ιjgf/cY}t`kYi3R:x? Ѹ7@<4~EߺSKiq p( Â/XƹI>;S:~:@z3+ڧC.C>Cgl5=۾ ߡ{& ޺s+=s:Bk7n0r C#ll+rdS+([h0pJC8(||;$;}rBc9 PR %5>إ5IIĚBTAd@*:', >*TT޽S@ν{+l!>p25V<8Y 4W4,"F#kEhcEj;o2PE`QLJyF4s 0 LN:`()HCP G ĀāU  +|$Ct$}[aQɭa1 4Z=?1E0bȨìyAk9h7@k=,>#7XEDƔL%9eo$JD[zŃSdU sJ;KlfK3+=dnʴŌ A =k (ɳHۗ=2@M+S|DfJ,OK4K;ԱDϜTGA{|L-#۰y2AL\:+c$O;qodz\0mG|̘zQ$ICp8NΤx|BM̘h"K$,Ih΃N@KUJPAƜRJ?{/ż9ӴDFМl4lP]/D|tA `̾Td8Ӝ[X4hL,[L1,kA`ymL0t y?Ԁ`*amq!W="jfXfii$qQB0-RzVPE46pP@LE2:yӰ/eKTYVKm=zu>JJR C.#6Fc*$#59W53*"rP3~W;WBt=VVi[k\ Zu W nZpa%M SLHe,AЄ DJl [۶UX,UKsv1?EuKTT3־CآE\Ae5WX-TυXl،uZԥ\ g5F9:EYeغmQ8llkk;Ix $]#0O *``([d # )RH|[T˵}~=]؍LM]m1KEB dSJ{ETTsOutX͑B ^TmӪk],C5O]dh֤Ie@I"%M`%kk__`b-lbHJ <@7Xン \P,\ `[A[L__`v[˭=7]Ae]+6dʾ; _xF vNVŖœ*V3}$0#1)M(FbYYdPd$b^ɜ~k[B!@WbD2˄㥉^Am3f5O3V`C|ZF^?̬\*|gYd`NM[N`׽E؋G)lg8Ai)Z#enqIg:Xpp\\ZZhkܖ _>~i5MR^~/~~K\<綀S$OdS|^[4gPE?iqjg=란&_v5/5\:dc`Eus ݙ޽V̉%%X%'x' D94du%1IJ ZSTaɊYV5OwH_n WglybXհ n̉C\U XvKN,\k&}PӵL\NPlkAkEF~SU8`6cv&]Fa&p8B8;iR-f8[l qP-3ZPޟṛ XU4^lqUVޛk8 H,q3kXUX\Ѕf ^$_+3%е an5Eddpv`O:͂`OCFwA;7ƁftԤ;~ gI'< :N_$}wV`is6~^M0QhbQ&VN4ul\?q7z KJ(1rE-qT8T[[Y嘋jbPTl4wwTNHNAWV>XǀT~a? @|BPbLOHEgOtl؇Fpok.۞xAε~NJЄ[xGEju6(0gpg[F_s8̂`'`ߤv9?qz Čn@2DH$7}CC,h5nRy3>T@To?`GUQ5@y6xoDWl|΄yBu~؝xˌ`9zx^cntOjkVB_2y$z>Ș9ˆo묾z'&$H$..njI%M%^QtM8BaP@bT1(H@`./80l@i- PhT8CҌ[-V}@t`JWVkUvZ0D!F}*?Pu}TQ~`pX<& A5\.!9l?'d9Kk*ewQm*WZ?T^]W; n^9Apۮ_x9ߏ+K,  kTbsMg}7Y]g7ojXp~u_t=wi_Kg@\3bԊxCDi$A4,t@qA(-IHCJ+!#~9a(wP_NrQ2^Ǹ`:#u@Z$7|#_l}4J.Pnp֌>PJ¨Yp114! 10i-/NP7 j/رL%1]pH@"C0k `RN":&iq- -!Ulm"z:/ X@(!`,ލ/t*ҽ+-70OMx "Ͱ @NQPεl41Rv?2ta#d'/:l#m R[/C)d^2qV*XM1R!pr drܤnfJ/*kk,0 :'@Ke0pM aboh-M8b"D2nORHjv-qjϳ2shSXR2ř2g52%*I'fS&͉2U*3a])R4jMPW"22K;10"@nn`P:+^SlJ+- F@ @-6e$8pB@bfKȼmHz3o73g)V&}%* Wx#'=H73G-C/o3st.zpA:ԼR}Aڅ` t;I1A ` .aiP ܙҹ+Ntb 0/(CUR 5I Tk\QRqТ$EuRۓeI$ҟWS4T%F%Gq5Fq+Y^L3&1UH?VY3w -(1X]*ӟWkF#"*>dgsnmrO_KTj3iujӱ X}"b^JA'p~iwjx{jɎ"%X^A6UV K,e"͌Y\*j- n0 9`n`{ʚ KĘicQy>"Un~vkkKiK%5E}EkmԺvIˋyG $O BaPdbP|$âN9EIAd]/LfS9m7NgSA)t(.>IpjT@TmBF괮{_Ȁ:gHb0Zw^oSY.<OQ,u^i@kX-=hoRF#`7@9{k@.ݶݴ\>'a"|vTdߙn7wLSoRt>ZWe?^'ީ>:hK؎= Œ) $ 0r pQ *?k,ATHE @ti1zsD} ê4k#G&ɈB-()aq/d1C')73Gpxnf}PdAЋŽ @`I,- zQEhZB@Հhb [~ו+ŶSaؖ,+:] F!8ocXEq)nْaIVlx)qC/ȖOpI-Fhܰ}^Sk{[#S߷JG_1. cv=(Ghm&Ӵdm!`&9~g&їfhrg&"6@+%0y\W Hvk![ r ^-rWGb%ߏN5| x\""[Xe[EĐ7#WF9q+Y0Sws|f`\[oE>-ՎyZa|tgԗ?Hgpffd@V5 ,@P,xebtT|H Pm[6` 3@@ N P(=CDN$ ~C{.,7^WtkuE(TO$꿈gN.1TW[Ek!y#S,0X+݋ha%/ $\ri='bH-҂Q>݃rnŒVPk Y䛎 Uj@v lX` /4A!CNWA~/BAkPЄMbZ]âAǕqwkMc ]ŇEͻnZY/4 Bȹ wvL޿3lڜgq4vmڱ q!o Ѽno}̐ 9@!WjTA|u@T TҚ}Rad^}1cHi ZK[,tj 8&mW]1h.҄GݮFٝxtܪoۇܙs^/m5e֣zk݋j)0B L@P;q @CV\&Iה D7\?[IZ\`zo (?:i@V"Ď"!znlx͋s;9Oy$.+؋p툴l0+$fԯX̭O&tF%B !)6F&0((D4-^`|- L`R) lmC@a`ato "9"WB@p2.v"m< ) 7XC,ͅ/ٰ*jmO 릴 +D14& LA0C$ 5A0A0'P!U hOd1hOld5`ѰD48RS L @~~b%u Q&yyNo9: y QNQ /**i #-9 !Gm.(o$% x`||K$ZG^V!:4 4s(e&V rw@I,(86@Q4*¤k뒞+0vU`#0#/,P<%. g$؈X$,[q6!"3 1 S,O?#m//$rT=39 2+"!k6M2c@R0 9#19Oя3xo&@l m;+Hnl-s,V( v`I%3E4b$"4 !W#P/S3A)$s'-@[AD%R"5$d t3^⊻B ojfjhfB0S ԩ=4,0d;"A@t4241Q1<ޓ=Aj0sE58o1br5O-.z13DGȕ-M5:!iP Q\48aSD$@ @ l8i&|NU L\4BD`8c]Y)ܞ J u)IX/ԝipg ,`e~P?3[qD4[@wSMRUK hMBGO5_+@Q?5'@T/9QM]]6 =WAcfXO[V%& plcVdTdvId<%@Q@)h` Vkl"asagvy00f@Jڭ2:֩O&V^C`tt/Aj/1CTnt$UUu⠳ 5v?w 53(>N.042$Vf|!xaxCer>$@ R@H|_ɸIJ$-P?"@.Di[yq/AcV)]&Wj0@fQmeqGmAsorCb2:xrQ@s}W`S]wSw"p7_`%2O|9^pR2B_c3T".!| n(J;Ў`upOճ}I4"dprӆ Xl4``)&UZXv (xMb!iGb4ǀ4UAR=NOvnu?7S}vا.!u?5.X)CÌl+J:X8%S9( N/yz% 1G8Nj7Gt7i&M25cZ pqx3e6gX͖2$@S# ѐaTDqN'uФyK1?XGlvpm-l ͞80q059 Rrb0nIE?2UA}5ѐ)`O5CY.|}fyn+C|wXy7}5妫% !w X-Da՛4E2@vϟ^:O5~@YCb e&5b@9EN]` Rz Pk&, {5LM\3fS[ `toO"wޔps;Nfx6~.ϟr"zRr7 !8~4N"2nx!$ѓYFGӓ AJ}8 |!A6؛J2Wy |A[C˩雁vT BIƣp\s]ׁ2gju9S!%Mnet!;M"%d{Wf[c;[Vo˜ hcP5\p\CC78Vo;-3nj<6:u#rz˺#5^9w)dܝ͂ 8 Y۵YTZ"ڙcD|mXK4;^!a{+:#&dxe|.;nK|zAܠ4LOԇ(vVek+Jry7]k_ڔܽ?ֶ "$}Dyߪ1Ry"\㚯`q[Җw ;6;!.&~C圽L ؛|ϱyT<}hp E#ܫUljX0yվXd5Y@l -K5>;[E y[kҾlW'>=>$q;Oy~5^-GF?7TU^_A)'e%w[ ;An tpViUP) Зܝ48^g_VLQt`t\](qM#z{w~}W9;q{䛋i}B' +  A:! D6F#C Ii@Wţ2|%Eas9y=O2NC@T(c+R&R9U3i/ѭUm[q9@[ }箌31;ϊYO)eQl#&1dYyx!̈́7*Y6@ОqKjGNk'QV`T'Wu4Z&]t?@oo dI̅?C?O "s @$KDBot:@Ϝ:AQC1D2ʹ+ @]EmFS)JrܹKk *1,2LL9a!Qt;N:0@:4e T P`&A؎lx+K75AH+]G:qc ʙSѣ 9QU0VGU<^2pE[WP{\}Vb{ZҐyd96/m[q!=oD6yq[W ?kFw-{^-wJ)YZ p[ź{,]biF/ Ъ/آ1$_G '.Rqcmfkz0 `&yEѴ}"9|#P!@TCQb6եqW=x'"wy'I8-o]sV\X%Y3?q VwBm5uz &}UrpOu12,}va`h]abxr^|rq(i`u`u nP)Hw ~z&єpH6^e?F 4!K[*8TEQXܜV #`XWEо rv t 1$TPq^+P;l] amɷ/N>"{"AXc p3)gxiwF31#;&30P r4) @z _ⓅOrV e!a1hNt%P.xY|_ADQXPf1a3"]L[E"SF_x*tgYX٠M%+m͓n*S9-aC+TȘneO2PˏbҖ]`pPƭr`:+츲p>6t`3@԰ndI2y)JMPK 4`@P?oC8tNjN*Xg!Mi5"tMRX:99fb#CFaPWGW"b!}B;!N+-t%<ǒLv@kY6my1'##Oǥ?"̸Rt8@ ONv\0jP4pp LTrNxMmV֤cm[Է΅a.|KL NXU~~ XJ[[EH}Ӕ6Fً^q5vѭb0b;Ѥ10{|{Rs)>Rc|x@| Ȁ1prU.7%,*Vp%nPK ~na3%작D^ M䖇Z4Ƈ1Hχ>.;ˆz^f'r`7wAgXVO4 `koq=u`ppgi&@e = cip`H[0G ưHcKԔ}@ 7 ۔eroY ǒMe  p T>IY(`+<$e+Άњ 9@ಥ^NU{=Nu&PLӛqZ7q 3jmL_{n@<*ïO%Yc].6.x&Տ# jqhi1~&ͨRxztDx;f?h|lk>G#amH6E) 0$2,pê'*k%]!<[?sz?UɎ_A|0ş 3X 1gY|=R.@ASگC\;I'so[932{;4B(&!3B0$BaD!=J'\Bkm4-APR\,pqq 򉆨nY5$ t64M#DP.u@:j3D[>DB3M9c*s95="ClaDC,Z8`€QCrB 8b8]=!Fżp>k;JF,J "8]D=G 3DZz@0nn8b;-┪[ B8c7`\b=/"rY ܾ̄ 4,L:Y`Udꏘ87ѩс0Ϙ0  ŷl $x zPS~(#(6E ``OOhoD+|8T8ESM&WT S0zөD: >{SE5@R0L*IJ"=%QQWX@ѐ5D8řhGQPR¢*0$+p 0m+nBMqM`MhEm/n4t bũU= eg%fu=oI>MZе!]sMUQ͔&e9cÓ[[S-t [q-PHEp` \ Z½mPVPR|FAэ]\d_;[޳b|=h4Yc 5/U8DŽy`Kd+.l:S,}Aa^0;r(TBfÀ(`9Nako7"f aȵ<@ JWdY͂Qxί7[+UPU'RDRE-0SNͮRz5ۥl!<^zcug4[+}*.leF1lVl̤cbm) pŌh\ml-|+)_fmj ٜq6tzG~SŠJdp8\-rX0R3K; D]IUNf:֥5V%"Soqʈn6"9I۽o_cmfQӍhSp~|c.,d%l E_ lgRckk _dR)\S1ΚhаLfS:*O. 8.Nk6EHEЂ6ު R='sMOQ/2OH$j -a>&fp'/͍Q$q~Z37MtXޥit_ >@զthT" b$t&8$8P5VoU drnj H?r?Xθkxʒ(gpgjoP@b8~wK(O-hrr "(5T^I$ՎqmVEJuWNDK\_?3vc]mPǜEQqqpQfl:,tGDX:ڼUq(.,?(SMTNc(Xn{>Э_@ f?SHH`CabPmp&zqy7=%j97yp?T5=&1ߟZށxxw\mDžmvZƣ`7dNDȶn&?Gب@P&nnoZw -(=jk2 A|(Ĭ.o7[$ CaF%Ebxf5 @~b@+%ѹ43J k)M!ht@hzE&KSi EhXNBϡj^تMo=*6EQsVzVX}CQqqtL#Y,&xfY5RSs9 kuA=4j cv<~EJS  m:nOg׾{lT)Ϧ ;T`z> ]rII=q@`?m ΀ 8Q%Re ;*5H$2GIj^,0넀Ҵ PHr %4~¢2:n ],Rܹ-12+!!)28Š/4.eD. ܗ1R,54D:3Y&5#33 7C@Ӭʊ ByL2ٟbdP/TXuQUVպ|W@n[A`!rWڀ@f`3 _j֨q[@ By@'r X]uxiKlۖjfy_`&xZT(S+5،;̲z ͊~( EPHPԞQĤb>oɕ8fN7Ox1[KGZ51d2C=>KrWG<@n(o@:@/.Kr#Y 5 cPsZSmAD8NivX}X!us]9Q#~e"pZhWQ-kzoΏ{Yl'9>R:WO-'|/'ʼn}jO~%Ʊ~ ). |i%,#lTmv,Ax8@DF@$ 1$$؇>ҀKxC ȔyDy6,Ɋ8Ő\0A3hBA uc h@'&HbNv , c?e>װ\uj h|z%``< FJ2 rilDS(BvD ˟cߔY=f!dݤu.rNV?LA"X$=fa8(Ѹ7@BMxܔY CntHRFLJ07SJ@a, XqtQE>kq!c|@ FH ! iw}R"&(5pxʶJc2I")M lP1SLiS>/|VV*m]vy Jc0 2x  ZL5 >>lphz[DCx6>0V5s@XhF,E(DalԪ%+ 4j-DOWIQ<VI%0vF׳2 ImY Tpnʊq ħYS0e|0>>ٳ5[+} ) >b>p_[#D`q `z]X@A6b`@H Sp6UXgn A,%B T~av$ UyqV8SM7cyfUg Ue2ߓS&eFй[4C*&3C!<ȝhz1}nzvC$?X,xAϤ8 5ih0ӱ:cxBa]i F*גnRvA\2t:Kɓ39Lmc;6`ز8lhȪNr$~%Kڳxv>_]Pi{׀%e{0wy1U<ٳa *gRHyu;d{ܪJPFsuCƿkr{/yKE_Lk"B5ܱ !TbT-,Ai aum .&&s{PH0GˤlnY?~ӷ>ٿ8mW{e%_}߳{'Zzx'WO &jbۯO* "$P"!z $b^ BOBRp0A40@sHE抈<!foր3 ` ` f{^.py MKo M/&l|MC *O BaPd6DbQ8V-FcQv28 chD,I!s44˧lC<ʧtTmdj\ PT-W=+@v!W~-bvʉo\c=^/#uܰX<& |@ tN쥶fs@ `w<=@fZf]~01;Ymv} @Q o&rO4Km /Sdn A0'~? g;9Wy}}JoKALQYo?i2 ? LUŌҢм(?qB5 Ĉ|r"Ib}*(+cd1[)+r4*L 6uL@K2ӵ3ܸT c 3˞|? `ЀC E4"(kĐT|h,|_TpQF;WY5ueM ? W]I!1EgmUϤ?T"s[Qa`׉-_ԶCk"wZyGwІ`IJM@%3 0.b ~ADqGdM(øYO4bCџ"HQs.N@V jt_eH]eGmk7%exEo[ȾVUcV=#ug!֑[wz;X|Okm37 F[\6F}<-j |tTEtٹTS4 -X)xq^y )`R ?O (B.|sxxa `gWpkqX Gkri.UØZ  \t%ɹu+`sm x Uu "H/!9@8"5\& X`1oi\PzJU<BX!1w9ݻ~X1Đ(:A$$' GB_#}t>A!`j7Uґ 1?|phv%wN ȉ\!+QJeġUI l}W(S 4B!8xEIIiᄔMQ˩/hRsQDg˶)SkeTK 􍮔;St{yRj0<:q4Fʂŵ@CxmNIF_|麗{3A8(vEu>"-KsUZI1}.CQ, ΢Z+<׵PTMm0aE5qhfD;cqޭpy\j7>͍ȧNؿfa!,tC(f ,wuhV*HZ% ;8 p ,?a.*Xiæi_=NX J{_Pm5^v_\cl}Rɛ2>%8fitmN-wm-j]X d$PzK;Vcta5 "aY2*8#VsTjAL֚XsWeclnTnR4O1F088P O+||aέ I;#|hHV߀2@2a`a 0 |ٌ>%B:-(*B:,zppRLoK2ϜOPڢ+b MNB2< E:44<Ӄ;Sd @VB(mbR5?*RM0<0#@A >iG=e!Jq:p,-QT;]+=tdӰpbB1!-AXaX >t0T]PLDi gRu4ju5g@$4G3"toHSQJC>03; 2?<$2CΌ3'$oYKV:s,R>/ZQWV//r% Nl!aUB PY%,8a8^ !Ns>.a"+#5>BX @a ZKC1.=HP2#LlJLU`*sL(?2zpO.-Qaӂ5uX"YAiPc -hPa#@[RG,#꩎wc[[3Ny'dE4/P1"8xN!,ٓfbB@Bk _)|Y("68u9'n`n!SO6dx~x OsOsJK_홢 ՘s<$W8p:vt^u`5Ab@o\u<uR29\\HҚ Z]B-e&V+'Z#Jc1pUqIV913!=:%8v1}qz -SWIL9K7e;co_۩/[jrsm:#&~}o%`9瞧LdjvJJ @t^O0($&e`K9<CtS@{31XC7YTŤ,%~7˙M:#k3{,p:Y˷GLqAVEMe8Ès>UP>!%8VjT1p"ǎۤ5xs6/xd\ ~ [H0 q`BlRETqË7eWsכI):`WW=5*0UfzkgnAKWCt}d3{5w !!&yFq'\ê1%.H$*.`(MEܴY1`%=꠲<:ؼƕyh65١U]gBE0~blT\UyMͷp#-Xj az1'̮+֠BAD ,.>qٷ B ^%  pMU]#ܓղڅu;ui;Vb ٭\@T|!oy|w-gf;<-JSz+Aջ\]B%(`\ w߂`..u{\^B"}V ˠ "jGgj8ėݲ|C"gݳ\!;AUo]9UQP51M2U_a^>fDL#(&w;3YKcY+mf sn'UO>Cl>U 91N2BSCP=N?TNG&u=,H VB`wpVe`yrkW B/6q?K~'Bw5 w}:ő t ,qTL@Z8j)!UǏc툝~vAk., %n-KLDtٔՊ=7P5?sՕ]]EUNUkIi{˳9nhv֓5ƱսM:L˴յ_mYf@_GcCe^X {==σ> }"azBqZpvp| ]R`,@  TD`lWǪ`7 ~[Hy4{`b r> ߩ ~/7&T¼i֚A}nCjc; d\\,-h; aT+% j-44ߚPN<(A[an읣-͢':]U,AWt 3a8@ƴLR -6.0dDH !CD("RSdv" p~0h ~e9%(@JSI8b^_숇% aznjZèm UkOo/s>.f;**TX0&k0Ϳhȩfh T▓^mO %qd|z.M zڊ.}o)@KCcto 0b`8>7l@sb Q*ER~Rs#*Ee^$? <[, 6Q?Y:d/`M_RfdϜ[Kӎ3lMRi*263&eԩ7Mr_/PUerkT+s&{^]%c}`P@]WD>ܟXEPM_fb|SKp5,0Fx;@!Y qJsVW(CH9^ݏʸ+Zm5Z Yk64ʘӪ0ɺVMW&U=Xwr R&5pVyh볮`3X̡<,K{|* za)] .EW-$`鱧bhK`\m,|4cZG !r ~BL߻=_%FuMUfjL~ D63Q~Y,Xu\%h~gvm!.Sϒݚ]OLѵ_ 1uRcf;'mA4ƠCaC̈́tO[n{ EMWڴ ^,(]#QPxZ7RjQ)d @*1Lƒ [|pVLg칽n]gB8&-zl09҃/'vgjItnr-E! N*` Bt&tSaԆT5H\'+{*7+]1 w*PjyRaEo|BM xT"^!gx찷;x:37|k>͇Ľb,IgL< >WZ#@hI@"E@&BUWzOT:UdXYg -,=qd{@ 4l ;yO_mT|f) ӯ;js<:g'$"p[Ϳwrr?.S=D   Q g5.@$@-٭n@[8[ [_Њ.r=O3 ;s<:;XB)B‹w73@{Bt-l4R* ˋ:95?!S+LA@{__mDk =47 5j˄Okĩl$#C#TQ>P[ܱ<\q/1mҙC+ës?%@Y>?J}C4Ǧ)Ft*F=k5nFCxF`F3C˿-FuFt <%}^LaM`e]Vv(e eNۖμ^䓿 0r 08`|NXxVxjdVX E`> ~ped#R0 Ks!Pe\e}TySF'{ub]N*h$hI$X`b|xaoaUэۭ)6x$UL@HU6C53oo:Ff=M0sD8D˛O$11TcFgr8 н uZ[Td栦h{C6b6-\VQnekEXNe5+dTR<5X\9#:\xkkh\cUnDFXFn;=())!mWr:jVp`1h ]?k> ?ibWo p` ְdfnB&b(_~[Oa֭`(5!sMOMDIUhplNUWbSiaWN55eg`ʏկ=gmpGDIU Y.hs6lK>fӶ` IJSr-Eyr$k gP!pmݣ!uL MsgJ}b[ E@G8_V t0sv s݆^roft~vumv<'Kh"n.4k1mmgzi|D8D5bOsAxrnZrׂ( !חTq"x{[@e~N|}nzkYxDyi.Y5󶴩>yvq,vvdqdncornxPFhAAiw!*+-1my# :Pħy$q[/ nwk _q _oww#[KWyF,W˼r}w! {RSe?@^XEZ+zR?$?XfxZ|]bq`qf6]%OjA`xD& CaF%EbxK4br= aH ÖHarn)L%P9$"i!sI˧zE&KSifM9 %HgpΦ5e*0g\^/@ n߃E䀜@h3 E rx{4S)Ta2|kFUCdYe/BCѧh~x=nu ɁR*Џ(ִ#/) Xas?j0T&[DTAūTE9'/E1Z 12Bz@&䠆jfbf#HAr ,ñ,[Dz.4g0a['2`6x'-=M~n+st# 0 @>1RL+TULUJѲ#+xV5\")fU})ZW1vFĀbY aWV,V?lXlEf)<{1VVHqr\zz_рn\no^̴LcT&099Q-`6P  <6hѮ8CfZAH!ջr&Z#PUj֛v՝Aut@vyVh1 O BaPd6DbQ8V1b #ѨG F`4 (䰩πihr=&MӁ@H2֗nwQgD(tm|HDV*M# ^3]UzUC՟D_^{h ŚO9 p Zʯ=(+-*bB45 Ðʜ@$CDRM'D$|U-2-FqqH K(iA%<r(ha&)C봂0`K ñ,[Kޟ7@4ST=ψ+!h6Ok67 LeGMm6`$#QeLH?ksF"P]rA#Kuua؍ԊYPmc/rpz_"Vض=IhYi G-u[eoWs_}9wmEo1 SF$aKDHq'䈆 lَE ֞jƅ^NH ןYHQ5E}{+$b#=pTwƆl5ƫnZե"Fy/ m]Y4v-!uR1cafER30xlͣ$}dD5yCu]<woIgtĵ"(;5@0 |QfӺj6u<wq1v5gـpc@}ΑX7Uڂ2ԁK?c'`DkQ`0XlPQ9] xDWRbahQL @(@cDn^24%#ʓg֕P A@DZB!lmHI~f -"`q-v_|ҍ8Syhэ(!k(0`d_b4q]Ȉ.SwZAfبIez4.;G!8`k^i Uw(.VKٙqsMQȹ1W-+XǏ=ɼ&knH#یkU4* ʡOaؤcg-yݗ@4 A1djQ:fb N/Tl!cpZ,as#R )t#/M^Wu5iy*u 4Ml[ZΔ$!dlTQ ={,j뇯8 cdJ5\V *ǚfxh=F c7gTM[`, E'cih罚^e,q VH}iav?e Q;[_L]g VaͿqI b̶\1~jy-Һ7+TD9W[ȧ\d2FVpiHha<# >_xuî"F@9Ex@]E94+n՞sjYnďy.4ګ\ЦezG\_;/\ w%z%W~6]X&(μrEع`57y>w=]~#Nx9\./ zzoZEPUO* /2nT- }m.Pێh\O$dXVRN^IpϐVP.lFʈ 'H.졜fh-r!pkXXP"/ T# `y'N:6S ,ej i̶ p,w0H2Oa-QJ@PRfsSoZDN^0t!ti  m K # ! oa ΏhmJYL H+1G˗q<(+-9q1QdjWq>Q".n>nɐ-qj"a(vJ1[|R wBfkp$ B3`{@ @"^%DT^R0]#rq%e) |R /e+ϓj,Q qI(fH$j,#&lQr)&2)5s]9q m9[:-w*vSO( )ftD[q!}K6"Bo+"UEF͙,@R$.&.||^60A$34 G=W>7%\\vvH"9/$ttP8h/cFP:;B1tNS^x(DtzQO!-Vu GpCPί:p],q;,Udr7YNߒ8X )r4l ) x4Ѝt>4O1YR Z.QF(P%?"l_@ ZsmR^SGN^^ʭO@OoFu/QKFVAP/@NCPSO5;O" tI4NND X ?гJK`[YrYJRgA5[I<;` P*h`h4Kz|I)RACp+935a5Q`vm 3fu8KgD]]gCʨ5m%O jup6k( !ڨA7)[6 @ ` v]fe6it6@6qKR ΁AhHh$rs'c:fOM!ABBrcsQp#xeE7P9lJp`n4NPO/zϡT{,n)5|cR"GgH a`|0VTX ?f6uG@OY1yg harʩssO("cj I^ta@px{U6!xu=ml_])},e{rz7ۆ?7aUINXcxpWp""F/VAd鸛_JWUyWՁ% +%UtZ2xvDҎ/(&Ub6Uoc#pV:nX {ףPT28ya!a']&8CkXoΉXNuA/(':s H#4 V;m $xθyrDl@A(U+=']oqy|S))oATRC 5qɔᛕ:?yG}PS7pKCzIABoSZxS/!H M-è8՞mF ڐ 3á0X+!$8v\gfPeYP[Ȼ'ӝ]x9VŰۥ_R`sCp$xRխp cC*"323yҺ {খ̡^9aI5^ǀS}qP2\C1 ׽c[?cΩ͗V-|8O/ϻ6ON=/*bԓ X I苝IM$a`͡=92$rh "i4:{3`bkU/]q;^!cazMQ4{$ܨ~uV;y"z ȏaY~Ev~3zF!ӴޒkbG$KAcNR  APSD(No@X1L4Hnd=5"I=U>cR_pB  $. al dZѩ ǣԦ+ DeK")~G5O0y,УL䔘\uDO%RI4l@u8^'aҩ5Z}h[u]m2W.֦{~bY/t꽲9b\GW^y8zػy0sݡkpE"@A(ao7 `r]OunZ. pUtWzHt]SxKav#\@A`* 1*1¸' =?Q$KDDH*LP KS0 p1Z qʿEЄ" 12).C%1*KR:0(1IasD5Mn sr98gԘQe M(E`6,Pe*PUS`lZn~RSUJ UZ;6 `S[<W]?F9cAETE 0+ec`}Ae23S!ű'$1v7,p^WU]\E%^r-J_p"-'X+Q~cRpǘ awEoc}ϙNjN'x`al8s=Λ:\lLAk{%j 2 `MZ{{n`2L LRRuXX C"\̭$WF>޸ՓBA|Y /sۏkWlfh+ L<ԍ] v\ܻ}v.%>E3#ERG3S_ x|: a`qp0?6h&zTb4YY3Cq7?U$qVrn  (@ 6 h`Q0p 2]81@WnWKaOQzu6 =1*(:LW!ρ2%DZ #Z.%飓eq0lcE%t|o/hrt;a%# rlmHYHBܳ[.L~Pa0Mq(qŅs )>& izO&pxtxNDaꮚ{;fbNa"יNX>#4Ke6SŧGSI$=(v/ثHm,liR_ x=Pr\h`Gl^.]֎}]흭v֔nIh7.Gaq qFTEtU-5A>ܸLֶm^y t 3=`1x)<Y!f;r`|@!@#C@ʀe{!g6dII^h덳S-w~G@n}͋7)S.4po5\ЙF{:77P۝C(4|4\-um@񚯜x1UՌ9fG>(%!4A#Y?@d_sSF(>!{bhŐ> k0ΡCbw3reo|В^81sxc.9<3<~Ǥ^Yo{ĽmYӉϨ.|>t+Mѽq:ӫz?j>C{-+(w&h@0Cp"H@5^+˻؎e@,(g??)!ٲt'l w:CU4K88`'zv1:v;IKҚ>#>22>(?3.Z5S49jC4'/C>.9#C)6c0D7k>勒BBTAL*!nn?˹ pĸ4AoD*QD0w*1DP*t4Us(s1`e`Lc/y\ x0ɀ=W=3H)zEM/;.L/EH\[3B90G2;CS_ävCeNj4<6G+L0q”'4pCGTC"Ā<j@@y Smk<0#%l@1C$;,H&*@dJ 6Pp%9aPRgSaȧ X8ɦsCH36‹IjGzЮd4BCKt|oBC =K@Hr< tBC>̓? ɹ ȴn˙OH ;XIɔ A &iŁ6n@PeõlNy,ʶ:lN]Q  S 14C_NдoGÌO=L$\LcL¬LT.B.$G3dĿ\tCD5B|I5;i/0g poHB @K9Դ NRu'dkQlI (yR9r r./!k 4R` FX5;<4\u><HRP 9PEUМT LD8HQ#/XN+ ffBDk0DJ!WIi*D$PVF\<3]jMK ^"qV?(֥k̕(*zWXUwD:`uO;V? -n%`kC8k }GPbMD=QP U"kO9ϝ 8={=4h^+eU8;ybڠ={pUF^! l 0FVeg‹pц}V=\HFgD;O7zP%)#GiMJP"A)=ݓAHEZxFdiٖa-,om^َ݈ F!!_6kf*6D.E?O :hfUha{FhV"ŞVnoWVċ[~PhWI9P[sN%km>=uKB38bMj]ơ |.)m4 QνZ '՛֜i|RD^ȦRVG&XZp?-U>@^dY4jeN,LV ^MUV^M~"&(afG ~S\@R|q/+rSx'6-5aV O q=j7eo_N!uibƨo,Ob>4fqZ6aKftQp!06W%?3ϫ ܫ` q@/S98ui:|l=.u<ǿ0q ^gt#j=uzٖf6ja_Ke?jG"P.ff#n&U2o[-~v7JvېX6syj6wp&*^fww%o\ImD!:zi~bYrf^ퟁ/eeJf>5qO*SVϔh_υv-aTƭ4ul/6c]${%xflQDo{aǞP%!zF0"im?onǚۑt.Ztzv$!p*_BVtx׫K pc|K.Yw{B 59& V'}/+:d8xaDڮNNdhos^z6}E{MkGeȶpd{Dr8eцy{x~jwWegNo&| οo`TZe;0Rg~A`xD& CaF%d0lD_p K@ɀ!3fIu;OgRU5` 6:Aipjmj2%^WATJRaZZ ӥ:aZVkJMVapyk[cqש]݌_o3f^7<!c}FU[s~um -5m:su@N@˞pDf;p;y|xuT A(d3A0%`?'D S| OJ3q /BB+&C,QL\2M|m7q|mSy -sC G$TIQa$FvDMDܟ#4rHDB1M lLNSK3,26M@u'54 Hn;n:T9N> "8AHU,i@UpS=w<0DdET=dӒl!Xmm"3i+_l2g6>q<|mU!(}O)ݶ=qOW,ka]_YumW`yaLjf(a xlh8VQY^Y/o Y@Ag@ lVU hx=Mϧ^v׷&jCR^.O `\îz)e^ظZܽfjZsɩW5'[ӱ =SnRp6~qp7riޜ*p垀ԀWX]+iIҠQ}I^pZ* j {@b;鲧:7~ u/-t8?=tx>"_CoA~9$ ǁ9Ė k @lACP& p|5GDM5un0 G%u5 Tkl>(?Ҫ0FA@@ MQep'k1v+ atnSϲ6?7Ĵt >BX"'FY~n m2Bɔ;NA+$/vFfN(PÐeXm 8Bi50$5p #$3ٖf|~Ɯ1 vI؞\j彞Ubk0~dlKsB7`O046B :%A1v=#93drdS9Q a)oƢp*qԚ!F1k_XlV;%gZmV]E*&Pux0 {?q@N4k5 p^6w=htZ=&MX+ A( Zg}vs3g79ec]xjz{]wATr{GV|e}>mVH>n7Ic9JD]ݕZvQhQ-q.vҶg}f5n!vHumB2yr$2Yz?h/ ,ٶM@&qk\; <PtQЖ ޫcvi6տUmg4}YkGguz{̳/PT]N]}ޮUo s1\VR{P.@PjaPLpP x@Vfj FV|Zӫk۽uꔔ/QQW9X2?"T1܏v3C|)fe_R#E'4[1$=d`y֟A Cլ9v+Ůz(7q` zriVg}5`/U$a^= qU _۸Gb/~v]( N-0)^-n olmդZY$p"G73:`8[<RO\%1Zr#ķLlثs8ͬTp܎NPˉ}nv%W% ;rk{UKT J N^(g[* ^;x2D]|}ym^Jǹ?| e6ndCz|%Bd=hꑃ͌`X(t(7߀w݄yV4bEH  Rk.oӎPn5n`4eGU=yF떈ջn%Lo}gٗk_4,CrPfˢ$εՈNEgM+FSbg!~|'hLA9$GÝ8y(mj)7j 9_]V6NoٸeM~y1D۵4m\8ΙgCf<\tH5/#/?W]$  9Sg ڻ$CQޓ҉}C]&5ua>RSw,qnSʐGF_K)szc{tv pyo1ÇaL ΙA yػ'Yu|7ȵ}/N]q[bMy{76ߌ^_⣓0^/Kr2 p 4hFG@tOb*sf"j tO}J+HnOv |nj4.ZPxQϸLH$PNtNìXŶOP*%4AL" fp.= 𦷀N!. tld!K#)jf bJ_` MoЍ8.竦mr,6Pnߏ1>ڐm-*xL;P_ e} p˴+yPotm OQ:NPB=PƱO^Yb.14\mPP h]NlP`b-@::pʟ"s/Qr_ЂΫѴSzV$=qw {# ّ0MRh/ ~!s%2U[$mu?O+˯vls'ҕ'j"/ "0Aҁ"+ ܧBV+ `d@ "O8) \`R)q@c&-*TqQ2qϩe52Z.B%\A.3L95Gi{ +' 6l( 20-4Qg9S 3jՑm%o"~,=G*:7E#2N.Y1pf-|#!.s8 RQ8bюY%'rk*9(Q}@j6Sh=B\1E)AKCA7SyEmp72`,7.i":ӱ)9+O;:/}2O׵m,` D ZX` t8D`($xф; `! |M+G2\!Hr$ ҹS(m;(IR.,;6W-#1Ls$$+TK3d+[S.c/,(RzKr;O- CPB0LEP#@+ RRIĝDtP2E9a&Kl8m ANk[#a:g^m2O?c}'v|ͲZSj/;VVcf W|\]t <_ ߨex EE5jF@!FE3mY> )KLm5Ak\T5gWV3Mog%g &e=uE9M~jz'.vQy-ffj9/Znm{dWF1f[Q=;+繤ӳ߰.맶|KgYI9})X'J}/e2d'4| Sa#n@W}HLr8_}1p#fcc(Sx<_ BKc3q )ҷaBxklOAc{pm(<fw4kL64y# KON?H%߼ajI0$iqWJ'0YA9+_jj UM:XZb=AuXLf FiT+f_M Ԣ)2)웄]2/3x猾rY6p9 Θ%䨜4J)䊓T+d݌ FY _sBi JTROHࡊurÉuGf!.Mi.j)6R0`OEZd ,0yDvUQ+cЖ-[tbuOW(kO;hΌhc׭XxZߣUs[jA mK裆4yNZ.)~BȊb(:+CNaD(P̦yոUȃqTOU&x 0IIGf%? dZ]gdzv~~O>JFk ,ՐmeΥdtsN[ѴL= ՆÄpy Yfg]BOI$ejIgy-w5l$[\ rρ\ȇK15("ZyLS&LB 2#yio{l}gzf|ntaX`d9 ;f*W>>?AοC4^?@A$(co",c+)C[^1[M3B21[d=<8k(DS<5-@|J=:ۋDJ6CESD|PBʾ#:Sk3EB9-9\7BE`Z;K=|< b!5DVt@Ed<4g+s<㷿bEesǛ Tn<)BH.4FCCD BSӽWǤHLoDE@d4:dM+ȼ?XG4jNJKKF603gH،T9Z,>㣳DW3^ܚ1BS2Iq:r#\J|,F~L5B{Gj9۵t^$KHH.F;ΪwkB\sHJEKd9$ \H̠ʌcHb ̌< ,KȔAƜ Dʴ$p˂=L$L EKJRq̪[ *,KǤEåKu Ǖ tK >Ge t{EmMD/՟T1? (TQ޵OĄFɛ_P LM#vm"CONP-6O+9-,LL L<8l bSQ4u.0SM ?,9#S D-SsTMR]GS)SEJӵTQTԜ]*U<=ʔFORFM^ vMC@PD NlDžNS\ƽcɤ#P85gU9Y=u$8KSVdW]k ZGmuRE44$]ɕ>LK"M;ȥ=kDEFzWOMO5%LȭO6]dmeτ[I}"?]43ŭ#t|%XeMA$E؅nXVP}s W O,TLQ|eO=嘸 MU}KݒD Um`,Z5}rլY<5GͭCBeSW5VK@JݑYjU F֬luL%5G¼DNw[TnC }MWl]YdJ]=MJpmEJ;X"ZK;۵R5GCWZWdfݓM\%$ܝ\lէ3LT^,Pӟճ"w_|_R-^dCY\ۍM_CUyˤXE˱%֝C- DR}oe_` -U<}ÍU7`#m!e^|U^ZbaLTWDb=`2EЅ _16=cJYc̦/-cTM]IS^}MO$J<ڴa?a7=`(cf&#$-WٝH\[Cũ<-%b MLUM]XQfOt[Rh UN> vfvanpFcݥCs>j6 ZUf"nWR߸P3c =gq^l`;#O#gl;ރSӦЮ\UxuFS>vIx`[Z;EԦ,Z7V^P[.<-eNVFaUڮΞ5x6]\ڝދSe`ƏFcaERj疠I%2fg~e^Y|phVk[6fN[+brBǾ;hS>(v"K&Pl'Zf뽰5k^VX~x_mζkVvjfX&քќmklh~hlMau/gmܢTln~IN]lU1f~m׆Nk>g붛mv^蓔\Af\^bNhȄb#U4c]vF EjFVN>RFprs^g GnFpΌq>]MzfLgc.2=S% noF U ޖq]ZBqj`_w*'LO~q7`4F[i&wWg=!wErO%h e=S&>f.K&޾2.tVq _ uЭ{cUl!'D]GrP]w gNL l^E﫢A_llvOh]Y/2UtNjop[nyoc@_at6'j^4gkENz@xԁc?b0rPtgoo^Նi~obp_Rxφ7mwn 4tjuh/zFݏyG?ΪpV\gy&!oypwZ#l&EF Pxd'Uww(CeS|+Q{R5u9d+tLxwop]?DvaOzN{W4H N:7dfs s}|Gxl :{z^yxO|"?~dNu mS8]Ny6onskkE~ xD& @నF%E@ƢQtn# B$7*!X<-3fSIY9e46k+A),ģ>@Ujzf?ڜLԣ3k-ZW˄kZSMRcz%gfa+; qcqG%d/X<g7s߳_O44U vul]@qx|w#as9v-BipS5#}xi34_9Rzկv.J}=b;9/$!(K֊O|n 캰>C-3( ?ΌP&pEQqZ0 R9 1aNQ3JÈQ*d"KRޗmpDEm[#(˓|8Cp%³ԕ 1t@T:>eQP\=\AU@PzRt9N#:0SLw̳UW8 IU$M1 )4^SFv]f0_gF}QۖwHOurq>I5cSPE62Es^׽TR}EPewnWdM#^W!T- V6`nɊ9`׆XY$6ܖEffY^V5T_Pɘx]e^bj[FM7;uqY.mϹT׳C]O@7 sLZJ3%  :Դ3"O 6?$G{jBP@rA K1 E] H,#pTC1L7&FQ:ʒtC%I3yI AU+L c&LE* ,+ ˳;έI.ӏ1ͯ.? ='<2r4ď0s0]3 OҝFK$R+OI 6MUeY֒4eHU=)RÔtNT:PuZxÐEe1TtjQ|J MZQ bV qnω ^͔m=r'=CvTWEY% c[]M5FOJ:8$+~#M<#{cv[K@5Y(普m}M/nqeYh7͂^7]݇(X UW1aX9oԕzwfz^o;lqdӾÝ>雥9Z-fz{^ϳoO;jUQ5Zn<\?Ep$Kx7չQYyUٷ6-jQ87u#Gx63Rz'鼿YGn\unE]9l=n^sUvV,){?ښy(f۾rTkyVB Sf%rÈyFbaeЬ1Wt]dUe]v\3,?a,N-TTt; Jdz Q-DbVoًDwѢj ;]eǽh)a +{@A!ё@iH7$L,N' @JYI)\>V;+RY:$2C_ " kMPWc VDxlL3sL)4nI:?g3i+#@N;l F?w!5e 0=D`}o𾓿!fQnyY=L n Ne+ 4 2$jM3ɟ6`^F2i=9qE8vS+u DP@u0Q1Udmz 1DK=hЙAsCcK[@tWT&+-(@$>l@ V.js*@X6L{,m/gDTrχ)}[ CxAVE$RU+EPZ L* 4B8|/ǣ G$ɤLW,"ї#CshNc3BQ',4S}JTժ< ¨1d޿HX)ʍeZ"V8-\ݮSH߀ lbLHqvJ:WL;A  e*񋍻Sfw+|-Ŵ"WwK֭e9+}ɶ]tyDzY@v S[5r &ɖGjQv )k'o&{ L[YBW^t_N 3eT7(;E*:aȆEy!ڸVHFB\x>KbZHb0e`Y%+.ui )NQ'B"pJ0!Ek%Xlz*7o֩]nz)$rgڢV#Ċ$¶+9Ak(jIj^l[)X]mZܵm+K f8d.͵a.8WK>p+ĆKGxu0l*,ın4tோH8clgoøt%.Cz(4׸W  |}Wݫz-̻03&x `GT _>i9n @BVIs~^M9@]-9Krb &q߄09gsɂ9hVZ`[mp<8@3px% ya@:5]$GW&AXPU-z) #wCL*X2wހ/H [GD0p/1!N  xrH*U P`' H^bsAT]oJ\Ә2ڻ)6cq%rtѳ=U yɾSe15'cKEa( j?8 YQC"d!C>GQARS Rc(ʇJjgȣC mԩ16pV؅;|(R:smnuBcYORƬLY6sžE7I @Uʹ:Z*Uڦj K Ո_j3$99AS=Ml`Ƚb- pi:"rՀk9$'H8]d݀c{YNS'|*Ѯux f9Ng:.ރ"a;ܢ!\v PbĬ$ɯdֵ{(\(5Z]}73"py=80c@^uo7ȋ;&!\hV&E#2G1%Px@Q;n䍡 F"0 orThOtkΫrbܺ.6aqRB欝5ĽYzf[Sߋ/R,{o xUmY}+m=@M@ j@5=+;ѫ _k@1 Ƃql˔ !4G}'K a[m@6%R-g: u@(𽘼:EJ0o#r B4u8, > 󞂭7lNɋV=_p~+g,ќZC S4K!ˏfIdx(b\'8l V&%o,̅-<̱f%l"<= tÂpWP0"p.PYawDǍb׮0)`o x鄮^gx˽ߏP xDDBψ'Řf~ ~K ^|9,rlyOI3U; wC>c-2]=u-$|Sd}vOӕ[̳>Yؒb2Lי܅(TUBEյ]C $aB*`<xͭEA KbiQlFBL2 X ź]J)#eau!%\n޲%I*i\߅c9NaeMˣ"ɉTc=ݣ64OvCfُLE*"! :G"5p $p B~&Uºa];$M R#7D 'dF|^BP E`ESyrTaې- eC4a5ZD5!Na !Z1Z"]|]w#ߍ#-0%a9!d)VeAbb]eUɀ &m8]Z${dp#x]e,c$f7$J` JI^nMA a&v  =Lb4Bu tKځǀfN:ފZ8%e\eq'f F J ?#bYnbf3# &M7yN)ƪc*5\4&6a-(v>bdZh맚;l96~Y/#λ*l6 ʭ+H]i, |H">%h:@FkE^Cvp][DF8d 7m^녳:ϬMh@5$*=~5#dk\' Ԗmi^q+_?V(Z&02jvy"EE{ ,jX Eض7 չX.ƯcIQb<V%k'8qD]N\GQ3thpMp,p+l+!B&}iٲ)n[f1z#1Npf#,[l2og$)  D 7 W0O $W"mB8JE%ڗ# ND$S1m#TH@:h.in1  mpjpfݮW/sz}19"%1_;H{17s>..f8<,2HZ 3=s4N=4#2ϰЪ2rZFDCLGJ(24]L;@ " W$rBaW>k޺'ިgR?7읠ung=7XY3W,?u3?Es\@@3,٤t]FUsfp>{tTdv|T(ڝ&W:s'vl5q:6ҟVQk5b|nWVG:o?DŽxִkHw_Ow$] &5kg1t(b@ۢ2-1. + ޖf7}{663cBÈ{w{&q6l3hqMv5KgƼ<题̀ȠԤh:2Vx jEXx'P/t8,]6,]7ג!"(B;t]o10NyrnNѽ+N5ޖZ7Ak9˲^<>/>4ZxΊ8;z;BHPW:[T]+ cج\(]wv\~'Z3fTzo9~%1*r~:]OȚs(kU(4_υc>wо:`UӁ`" !|B1hb3 XjCIIS5~c``om/8E 03x; :c:B0y68=;o_` ` @2jh, 6a(NjWnJ!XXF}MdHrcȖYb5^FZbBta-C7"7WغNe)NTei^Xem-cZjd@lr\\Ft7T4uݖ1@i1hZM+i`9YVhdb1 :*`5@i( .+ +gZ޸kfD,)&+F"+PJdōF;"GCx`Ó$nkn Z/@js.iy']GY,Gv LM`kZ||5& @W(\*rqZ@Ƴi,,+NtmHtg4Վb{~-Z55WKKv-dvmy÷i}AP1ˊ2j<tK 1`TZ*8ji.Oqgnv`@J !_vϴ{n߸4RB4N{SZ|oe$LEQv? [(pyKSJl&;0 i~DZ_?e+;@VS  jd Mipςf`0qA?! "kHO !L*mfa(,)|:'`q`2KjDh2|@'i.UZe&@Om%nrtNՃx|z3#>0SeA 2!DF$ՅRJi/&$&2rN?( C`ki=[SoTEwiGԲp )d E({hGSH4p 0@eD$fq0MiC6&ڛsq(ɹ8' o"\:%BrK*8q‡$FdA% RD D=_`l v'%kPna/AnX`h[.7Ϻ0&H0HHĨ=@eM;ux5`7h@ױ\R+L쯞H@y`xL.H˄.P&y9z#Y>Cj;h>*x4n21SLЩW"\~r"O&1 :e9p\b5 4A-y6Srij 03EFs}!ci\mW>d MҰ``O6 Y,K!>Yدgu nmb~O3_C:?_f~?O BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/&@tX}?PhT:%GRiTe6OTjU:NkUv_XlV;$ZdMsye\nW;w^oW/X<& blv{J:o91Y. set -eu -o pipefail QTOX_DIR="$1" cd "$2" BUNDLE_PATH="$(pwd)" cd - cd "$BUNDLE_PATH/../" BUILD_DIR="$(pwd)" cd - DMG_DIR="$BUILD_DIR/dmg.tmp" APP_VER=$(defaults read "$BUNDLE_PATH/Contents/Info.plist" CFBundleVersion) if [ ! -d "$BUNDLE_PATH" ]; then echo "Please pass path to qTox.app as an argument!" exit 1 fi rm -rf "$DMG_DIR" rm -f "$BUILD_DIR/qTox.dmg" mkdir "$DMG_DIR" cp -r "$BUNDLE_PATH" "$DMG_DIR/" cd "$DMG_DIR" ln -s /Applications "./Install to Applications" mkdir .background cp -f $QTOX_DIR/osx/background-DMG/qTox-DMG-bak.tiff .background/backgroundImage.tiff cp -f $QTOX_DIR/osx/DS_Store-DMG ./.DS_Store cp -f $QTOX_DIR/LICENSE ./LICENSE cp -f $QTOX_DIR/README.md ./README.md cd - hdiutil create -volname "qTox $APP_VER" -srcfolder "$DMG_DIR/" -format UDZO "$BUILD_DIR/qTox.dmg" qTox/osx/gplv3.rtf000066400000000000000000001211271415623743500144100ustar00rootroot00000000000000{\rtf1\ansi\deff0\adeflang1025 {\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset0 Times New Roman;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\fnil\fprq2\fcharset0 Arial Unicode MS;}{\f4\fnil\fprq2\fcharset0 MS Mincho;}{\f5\fnil\fprq2\fcharset0 Tahoma;}{\f6\fnil\fprq0\fcharset0 Tahoma;}} {\colortbl;\red0\green0\blue0;\red128\green128\blue128;} {\stylesheet{\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\snext1 Normal;} {\s2\sb240\sa120\keepn\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\afs28\lang255\ltrch\dbch\af4\langfe255\hich\f2\fs28\lang1035\loch\f2\fs28\lang1035\sbasedon1\snext3 Heading;} {\s3\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon1\snext3 Body Text;} {\s4\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon3\snext4 List;} {\s5\sb120\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ai\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\i\loch\f0\fs24\lang1035\i\sbasedon1\snext5 caption;} {\s6\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon1\snext6 Index;} } {\info{\author Kimmo Varis}{\creatim\yr2010\mo1\dy17\hr1\min15}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}{\comment StarWriter}{\vern3100}}\deftab709 {\*\pgdsctbl {\pgdsc0\pgdscuse195\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Standard;}} \paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 GNU GENERAL PUBLIC LICENSE\line Version 3, 29 June 2007\line \line Copyright (C) 2007 Free Software Foundation, Inc. \line Everyone is permitted to copy and distribute verbatim copies\line of this license document, but changing it is not allowed.\line \line Preamble\line \line The GNU General Public License is a free, copyleft license for\line software and other kinds of works.\line \line The licenses for most software and other practical works are designed\line to take away yo ur freedom to share and change the works. By contrast,\line the GNU General Public License is intended to guarantee your freedom to\line share and change all versions of a program--to make sure it remains free\line software for all its users. We, the Free Software Foun dation, use the\line GNU General Public License for most of our software; it applies also to\line any other work released this way by its authors. You can apply it to\line your programs, too.\line \line When we speak of free software, we are referring to freedom, not\line price. Ou r General Public Licenses are designed to make sure that you\line have the freedom to distribute copies of free software (and charge for\line them if you wish), that you receive source code or can get it if you\line want it, that you can change the software or use pieces of it in new\line free programs, and that you know you can do these things.\line \line To protect your rights, we need to prevent others from denying you\line these rights or asking you to surrender the rights. Therefore, you have\line certain responsibilities if you distribut e copies of the software, or if\line you modify it: responsibilities to respect the freedom of others.\line \line For example, if you distribute copies of such a program, whether\line gratis or for a fee, you must pass on to the recipients the same\line freedoms that you receive d. You must make sure that they, too, receive\line or can get the source code. And you must show them these terms so they\line know their rights.\line \line Developers that use the GNU GPL protect your rights with two steps:\line (1) assert copyright on the software, and (2) o ffer you this License\line giving you legal permission to copy, distribute and/or modify it.\line \line For the developers' and authors' protection, the GPL clearly explains\line that there is no warranty for this free software. For both users' and\line authors' sake, the GPL r equires that modified versions be marked as\line changed, so that their problems will not be attributed erroneously to\line authors of previous versions.\line \line Some devices are designed to deny users access to install or run\line modified versions of the software inside the m, although the manufacturer\line can do so. This is fundamentally incompatible with the aim of\line protecting users' freedom to change the software. The systematic\line pattern of such abuse occurs in the area of products for individuals to\line use, which is precisely wh ere it is most unacceptable. Therefore, we\line have designed this version of the GPL to prohibit the practice for those\line products. If such problems arise substantially in other domains, we\line stand ready to extend this provision to those domains in future versio ns\line of the GPL, as needed to protect the freedom of users.\line \line Finally, every program is threatened constantly by software patents.\line States should not allow patents to restrict development and use of\line software on general-purpose computers, but in those that do , we wish to\line avoid the special danger that patents applied to a free program could\line make it effectively proprietary. To prevent this, the GPL assures that\line patents cannot be used to render the program non-free.\line \line The precise terms and conditions for copyin g, distribution and\line modification follow.\line \line TERMS AND CONDITIONS\line \line 0. Definitions.\line \line "This License" refers to version 3 of the GNU General Public License.\line \line "Copyright" also means copyright-like laws that apply to other kinds of\line wor ks, such as semiconductor masks.\line \line "The Program" refers to any copyrightable work licensed under this\line License. Each licensee is addressed as "you". "Licensees" and\line "recipients" may be individuals or organizations.\line \line To "modify" a work means to copy fro m or adapt all or part of the work\line in a fashion requiring copyright permission, other than the making of an\line exact copy. The resulting work is called a "modified version" of the\line earlier work or a work "based on" the earlier work.\line \line A "covered work" means either the unmodified Program or a work based\line on the Program.\line \line To "propagate" a work means to do anything with it that, without\line permission, would make you directly or secondarily liable for\line infringement under applicable copyright law, except executing it on a\line computer or modifying a private copy. Propagation includes copying,\line distribution (with or without modification), making available to the\line public, and in some countries other activities as well.\line \line To "convey" a work means any kind of propagation that enables other\line parties to make or receive copies. Mere interaction with a user through\line a computer network, with no transfer of a copy, is not conveying.\line \line An interactive user interface displays "Appropriate Legal Notices"\line to the extent that it includes a convenient and prominently visible\line feature that (1) displays an appropriate copyright notice, and (2)\line tells the user that there is no warranty for the work (except to the\line extent that warranties are provided), that licensees may convey the\line work under this License, and how to view a copy of this License. If\line the interface presents a list of user commands or options, such as a\line menu, a prominent item in the list meets this criterion.\line \line 1. Source Code.\line \line The "source code" for a work means the preferred form o f the work\line for making modifications to it. "Object code" means any non-source\line form of a work.\line \line A "Standard Interface" means an interface that either is an official\line standard defined by a recognized standards body, or, in the case of\line interfaces specified for a particular programming language, one that\line is widely used among developers working in that language.\line \line The "System Libraries" of an executable work include anything, other\line than the work as a whole, that (a) is included in the normal form of\line packaging a Major Component, but which is not part of that Major\line Component, and (b) serves only to enable use of the work with that\line Major Component, or to implement a Standard Interface for which an\line implementation is available to the public in source code form. A\line "Major Component", in this context, means a major essential component\line (kernel, window system, and so on) of the specific operating system\line (if any) on which the executable work runs, or a compiler used to\line produce the work, or an object code interpreter used to run it.\line \line The "Corresponding Source" for a work in object code form means all\line the source code needed to generate, install, and (for an executable\line work) run the object code and to modify the work, including scripts to\line control those activities. However , it does not include the work's\line System Libraries, or general-purpose tools or generally available free\line programs which are used unmodified in performing those activities but\line which are not part of the work. For example, Corresponding Source\line includes interf ace definition files associated with source files for\line the work, and the source code for shared libraries and dynamically\line linked subprograms that the work is specifically designed to require,\line such as by intimate data communication or control flow between th ose\line subprograms and other parts of the work.\line \line The Corresponding Source need not include anything that users\line can regenerate automatically from other parts of the Corresponding\line Source.\line \line The Corresponding Source for a work in source code form is that\line same work.\line \line 2. Basic Permissions.\line \line All rights granted under this License are granted for the term of\line copyright on the Program, and are irrevocable provided the stated\line conditions are met. This License explicitly affirms your unlimited\line permission to run the unmodified Program. The output from running a\line covered work is covered by this License only if the output, given its\line content, constitutes a covered work. This License acknowledges your\line rights of fair use or other equivalent, as provided by copyright law. \line \line You may make, run and propagate covered works that you do not\line convey, without conditions so long as your license otherwise remains\line in force. You may convey covered works to others for the sole purpose\line of having them make modifications exclusively for you, or provide you\line with facilities for running those works, provided that you comply with\line the terms of this License in conveying all material for which you do\line not control copyright. Those thus making or running the covered works\line for you must do so exclus ively on your behalf, under your direction\line and control, on terms that prohibit them from making any copies of\line your copyrighted material outside their relationship with you.\line \line Conveying under any other circumstances is permitted solely under\line the conditions stated below. Sublicensing is not allowed; section 10\line makes it unnecessary.\line \line 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\line \line No covered work shall be deemed part of an effective technological\line measure under any applicable law fulfillin g obligations under article\line 11 of the WIPO copyright treaty adopted on 20 December 1996, or\line similar laws prohibiting or restricting circumvention of such\line measures.\line \line When you convey a covered work, you waive any legal power to forbid\line circumvention of tech nological measures to the extent such circumvention\line is effected by exercising rights under this License with respect to\line the covered work, and you disclaim any intention to limit operation or\line modification of the work as a means of enforcing, against the wor k's\line users, your or third parties' legal rights to forbid circumvention of\line technological measures.\line \line 4. Conveying Verbatim Copies.\line \line You may convey verbatim copies of the Program's} \par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 source code as you\line receive it, in any medium, provided that you conspicuously and\line appropriately publish on each copy an appropriate copyright notice;\line keep intact all notices stating that this License and any\line non-permissive terms added in accord with secti on 7 apply to the code;\line keep intact all notices of the absence of any warranty; and give all\line recipients a copy of this License along with the Program.\line \line You may charge any price or no price for each copy that you convey,\line and you may offer support or warra nty protection for a fee.\line \line 5. Conveying Modified Source Versions.\line \line You may convey a work based on the Program, or the modifications to\line produce it from the Program, in the form of source code under the\line terms of section 4, provided that you also meet all of these conditions:\line \line a) The work must carry prominent notices stating that you modified\line it, and giving a relevant date.\line \line b) The work must carry prominent notices stating that it is\line released under this License and any conditions added unde r section\line 7. This requirement modifies the requirement in section 4 to\line "keep intact all notices".\line \line c) You must license the entire work, as a whole, under this\line License to anyone who comes into possession of a copy. This\line License will th erefore apply, along with any applicable section 7\line additional terms, to the whole of the work, and all its parts,\line regardless of how they are packaged. This License gives no\line permission to license the work in any other way, but it does not\line i nvalidate such permission if you have separately received it.\line \line d) If the work has interactive user interfaces, each must display\line Appropriate Legal Notices; however, if the Program has interactive\line interfaces that do not display Appropriate Legal Notices, your\line work need not make them do so.\line \line A compilation of a covered work with other separate and independent\line works, which are not by their nature extensions of the covered work,\line and which are not combined with it such as to form a larger progra m,\line in or on a volume of a storage or distribution medium, is called an\line "aggregate" if the compilation and its resulting copyright are not\line used to limit the access or legal rights of the compilation's users\line beyond what the individual works permit. Inclusio n of a covered work\line in an aggregate does not cause this License to apply to the other\line parts of the aggregate.\line \line 6. Conveying Non-Source Forms.\line \line You may convey a covered work in object code form under the terms\line of sections 4 and 5, provided that you also convey the\line machine-readable Corresponding Source under the terms of this License,\line in one of these ways:\line \line a) Convey the object code in, or embodied in, a physical product\line (including a physical distribution medium), accompanied by the\line Correspond ing Source fixed on a durable physical medium\line customarily used for software interchange.\line \line b) Convey the object code in, or embodied in, a physical product\line (including a physical distribution medium), accompanied by a\line written offer, valid for at least three years and valid for as\line long as you offer spare parts or customer support for that product\line model, to give anyone who possesses the object code either (1) a\line copy of the Corresponding Source for all the software in the\line product that is covered by this License, on a durable physical\line medium customarily used for software interchange, for a price no\line more than your reasonable cost of physically performing this\line conveying of source, or (2) access to copy the\line Correspondin g Source from a network server at no charge.\line \line c) Convey individual copies of the object code with a copy of the\line written offer to provide the Corresponding Source. This\line alternative is allowed only occasionally and noncommercially, and\line only if you received the object code with such an offer, in accord\line with subsection 6b.\line \line d) Convey the object code by offering access from a designated\line place (gratis or for a charge), and offer equivalent access to the\line Corresponding Source in the same way through the same place at no\line further charge. You need not require recipients to copy the\line Corresponding Source along with the object code. If the place to\line copy the object code is a network server, the Corresponding Source\line may be on a different server (operated by you or a third party)\line that supports equivalent copying facilities, provided you maintain\line clear directions next to the object code saying where to find the\line Corresponding Source. Regardless of what server hosts the\line Corresponding Source, you remain obligated to ensure that it is\line available for as long as needed to satisfy these requirements.\line \line e) Convey the object code using peer-to-peer transmission, provided\line you inform other peers where the object code and Corresponding\line Source of the work are being offered to the general public at no\line charge under subsection 6d.\line \line A separable portion of the object code, whose source code is excluded\line from the Corresponding Source as a System Library, need no t be\line included in conveying the object code work.\line \line A "User Product" is either (1) a "consumer product", which means any\line tangible personal property which is normally used for personal, family,\line or household purposes, or (2) anything designed or sold for inc orporation\line into a dwelling. In determining whether a product is a consumer product,\line doubtful cases shall be resolved in favor of coverage. For a particular\line product received by a particular user, "normally used" refers to a\line typical or common use of that c lass of product, regardless of the status\line of the particular user or of the way in which the particular user\line actually uses, or expects or is expected to use, the product. A product\line is a consumer product regardless of whether the product has substantial\line com mercial, industrial or non-consumer uses, unless such uses represent\line the only significant mode of use of the product.\line \line "Installation Information" for a User Product means any methods,\line procedures, authorization keys, or other information required to insta ll\line and execute modified versions of a covered work in that User Product from\line a modified version of its Corresponding Source. The information must\line suffice to ensure that the continued functioning of the modified object\line code is in no case prevented or inter fered with solely because\line modification has been made.\line \line If you convey an object code work under this section in, or with, or\line specifically for use in, a User Product, and the conveying occurs as\line part of a transaction in which the right of possession and us e of the\line User Product is transferred to the recipient in perpetuity or for a\line fixed term (regardless of how the transaction is characterized), the\line Corresponding Source conveyed under this section must be accompanied\line by the Installation Information. But thi s requirement does not apply\line if neither you nor any third party retains the ability to install\line modified object code on the User Product (for example, the work has\line been installed in ROM).\line \line The requirement to provide Installation Information does not inclu de a\line requirement to continue to provide support service, warranty, or updates\line for a work that has been modified or installed by the recipient, or for\line the User Product in which it has been modified or installed. Access to a\line network may be denied when the m odification itself materially and\line adversely affects the operation of the network or violates the rules and\line protocols for communication across the network.\line \line Corresponding Source conveyed, and Installation Information provided,\line in accord with this section must be in a format that is publicly\line documented (and with an implementation available to the public in\line source code form), and must require no special password or key for\line unpacking, reading or copying.\line \line 7. Additional Terms.\line \line "Additional permissions" are terms that supplement the terms of this\line License by making exceptions from one or more of its conditions.\line Additional permissions that are applicable to the entire Program shall\line be treated as though they were included in this License, to the extent\line that the y are valid under applicable law. If additional permissions\line apply only to part of the Program, that part may be used separately\line under those permissions, but the entire Program remains governed by\line this License without regard to the additional permissions.\line \line When you convey a copy of a covered work, you may at your option\line remove any additional permissions from that copy, or from any part of\line it. (Additional permissions may be written to require their own\line removal in certain cases when you modify the work.) You may place\line additional permissions on material, added by you to a covered work,\line for which you have or can give appropriate copyright permission.\line \line Notwithstanding any other provision of this License, for material you\line add to a covered work, you may (if a uthorized by the copyright holders of\line that material) supplement the terms of this License with terms:\line \line a) Disclaiming warranty or limiting liability differently from the\line terms of sections 15 and 16 of this License; or\line \line b) Requiring preservation of specified reasonable legal notices or\line author attributions in that material or in the Appropriate Legal\line Notices displayed by works containing it; or\line \line c) Prohibiting misrepresentation of the origin of that material, or\line requiring that modi fied versions of such material be marked in\line reasonable ways as different from the original version; or\line \line d) Limiting the use for publicity purposes of names of licensors or\line authors of the material; or\line \line e) Declining to grant rights under trad emark law for use of some\line trade names, trademarks, or service marks; or\line \line f) Requiring indemnification of licensors and authors of that\line material by anyone who conveys} \par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 the material (or modified versions of\line it) with contractual assumptions of liability to the recipient, for\line any liability that these contractual assumptions directly impose on\line those licensors and authors.\line \line All other non-permissive additional t erms are considered "further\line restrictions" within the meaning of section 10. If the Program as you\line received it, or any part of it, contains a notice stating that it is\line governed by this License along with a term that is a further\line restriction, you may remov e that term. If a license document contains\line a further restriction but permits relicensing or conveying under this\line License, you may add to a covered work material governed by the terms\line of that license document, provided that the further restriction does\line no t survive such relicensing or conveying.\line \line If you add terms to a covered work in accord with this section, you\line must place, in the relevant source files, a statement of the\line additional terms that apply to those files, or a notice indicating\line where to find th e applicable terms.\line \line Additional terms, permissive or non-permissive, may be stated in the\line form of a separately written license, or stated as exceptions;\line the above requirements apply either way.\line \line 8. Termination.\line \line You may not propagate or modify a cove red work except as expressly\line provided under this License. Any attempt otherwise to propagate or\line modify it is void, and will automatically terminate your rights under\line this License (including any patent licenses granted under the third\line paragraph of section 11).\line \line However, if you cease all violation of this License, then your\line license from a particular copyright holder is reinstated (a)\line provisionally, unless and until the copyright holder explicitly and\line finally terminates your license, and (b) permanently, if the copyright\line holder fails to notify you of the violation by some reasonable means\line prior to 60 days after the cessation.\line \line Moreover, your license from a particular copyright holder is\line reinstated permanently if the copyright holder notifies you of the\line vio lation by some reasonable means, this is the first time you have\line received notice of violation of this License (for any work) from that\line copyright holder, and you cure the violation prior to 30 days after\line your receipt of the notice.\line \line Termination of your ri ghts under this section does not terminate the\line licenses of parties who have received copies or rights from you under\line this License. If your rights have been terminated and not permanently\line reinstated, you do not qualify to receive new licenses for the same\line material under section 10.\line \line 9. Acceptance Not Required for Having Copies.\line \line You are not required to accept this License in order to receive or\line run a copy of the Program. Ancillary propagation of a covered work\line occurring solely as a consequence of using peer-to-peer transmission\line to receive a copy likewise does not require acceptance. However,\line nothing other than this License grants you permission to propagate or\line modify any covered work. These actions infringe copyright if you do\line not accept this License. Therefore, by modifying or propagating a\line covered work, you indicate your acceptance of this License to do so.\line \line 10. Automatic Licensing of Downstream Recipients.\line \line Each time you convey a covered work, the recipient automatically\line receives a license from the original licensors, to run, modify and\line propagate that work, subject to this License. You are not responsible\line for enforcing compliance by third parties with this License.\line \line An "entity transaction" is a transaction transferring control of an\line organizat ion, or substantially all assets of one, or subdividing an\line organization, or merging organizations. If propagation of a covered\line work results from an entity transaction, each party to that\line transaction who receives a copy of the work also receives whatever\line l icenses to the work the party's predecessor in interest had or could\line give under the previous paragraph, plus a right to possession of the\line Corresponding Source of the work from the predecessor in interest, if\line the predecessor has it or can get it with reason able efforts.\line \line You may not impose any further restrictions on the exercise of the\line rights granted or affirmed under this License. For example, you may\line not impose a license fee, royalty, or other charge for exercise of\line rights granted under this License, a nd you may not initiate litigation\line (including a cross-claim or counterclaim in a lawsuit) alleging that\line any patent claim is infringed by making, using, selling, offering for\line sale, or importing the Program or any portion of it.\line \line 11. Patents.\line \line A "contrib utor" is a copyright holder who authorizes use under this\line License of the Program or a work on which the Program is based. The\line work thus licensed is called the contributor's "contributor version".\line \line A contributor's "essential patent claims" are all patent claims\line owned or controlled by the contributor, whether already acquired or\line hereafter acquired, that would be infringed by some manner, permitted\line by this License, of making, using, or selling its contributor version,\line but do not include claims that would be infringed only as a\line consequence of further modification of the contributor version. For\line purposes of this definition, "control" includes the right to grant\line patent sublicenses in a manner consistent with the requirements of\line this License.\line \line Each contributo r grants you a non-exclusive, worldwide, royalty-free\line patent license under the contributor's essential patent claims, to\line make, use, sell, offer for sale, import and otherwise run, modify and\line propagate the contents of its contributor version.\line \line In the foll owing three paragraphs, a "patent license" is any express\line agreement or commitment, however denominated, not to enforce a patent\line (such as an express permission to practice a patent or covenant not to\line sue for patent infringement). To "grant" such a patent l icense to a\line party means to make such an agreement or commitment not to enforce a\line patent against the party.\line \line If you convey a covered work, knowingly relying on a patent license,\line and the Corresponding Source of the work is not available for anyone\line to copy, free of charge and under the terms of this License, through a\line publicly available network server or other readily accessible means,\line then you must either (1) cause the Corresponding Source to be so\line available, or (2) arrange to deprive yourself of the benefi t of the\line patent license for this particular work, or (3) arrange, in a manner\line consistent with the requirements of this License, to extend the patent\line license to downstream recipients. "Knowingly relying" means you have\line actual knowledge that, but for the pa tent license, your conveying the\line covered work in a country, or your recipient's use of the covered work\line in a country, would infringe one or more identifiable patents in that\line country that you have reason to believe are valid.\line \line If, pursuant to or in connec tion with a single transaction or\line arrangement, you convey, or propagate by procuring conveyance of, a\line covered work, and grant a patent license to some of the parties\line receiving the covered work authorizing them to use, propagate, modify\line or convey a specific copy of the covered work, then the patent license\line you grant is automatically extended to all recipients of the covered\line work and works based on it.\line \line A patent license is "discriminatory" if it does not include within\line the scope of its coverage, prohibits t he exercise of, or is\line conditioned on the non-exercise of one or more of the rights that are\line specifically granted under this License. You may not convey a covered\line work if you are a party to an arrangement with a third party that is\line in the business of distr ibuting software, under which you make payment\line to the third party based on the extent of your activity of conveying\line the work, and under which the third party grants, to any of the\line parties who would receive the covered work from you, a discriminatory\line patent license (a) in connection with copies of the covered work\line conveyed by you (or copies made from those copies), or (b) primarily\line for and in connection with specific products or compilations that\line contain the covered work, unless you entered into that arrange ment,\line or that patent license was granted, prior to 28 March 2007.\line \line Nothing in this License shall be construed as excluding or limiting\line any implied license or other defenses to infringement that may\line otherwise be available to you under applicable patent la w.\line \line 12. No Surrender of Others' Freedom.\line \line If conditions are imposed on you (whether by court order, agreement or\line otherwise) that contradict the conditions of this License, they do not\line excuse you from the conditions of this License. If you cannot conve y a\line covered work so as to satisfy simultaneously your obligations under this\line License and any other pertinent obligations, then as a consequence you may\line not convey it at all. For example, if you agree to terms that obligate you\line to collect a royalty for fur ther conveying from those to whom you convey\line the Program, the only way you could satisfy both those terms and this\line License would be to refrain entirely from conveying the Program.\line \line 13. Use with the GNU Affero General Public License.\line \line Notwithstanding an y other provision of this License, you have\line permission to link or combine any covered work with a work licensed\line under version 3 of the GNU Affero General Public License into a single\line combined work, and to convey the resulting work. The terms of this\line Licen se will continue to apply to the part which is the covered work,\line but the special requirements of the GNU Affero General Public License,\line section 13, concerning interaction through a network will apply to the\line combination as such.\line \line 14. Revised Versions of t his License.\line \line The Free Software Foundation may publish revised and/or new versions of\line the GNU General Public License from time to time. Such new versions will\line be similar} \par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 in spirit to the present version, but may differ in detail to\line address new problems or concerns.\line \line Each version is given a distinguishing version number. If the\line Program specifies that a certain numbered version of the GNU General\line Public License "or any l ater version" applies to it, you have the\line option of following the terms and conditions either of that numbered\line version or of any later version published by the Free Software\line Foundation. If the Program does not specify a version number of the\line GNU General P ublic License, you may choose any version ever published\line by the Free Software Foundation.\line \line If the Program specifies that a proxy can decide which future\line versions of the GNU General Public License can be used, that proxy's\line public statement of acceptance o f a version permanently authorizes you\line to choose that version for the Program.\line \line Later license versions may give you additional or different\line permissions. However, no additional obligations are imposed on any\line author or copyright holder as a result of your choosing to follow a\line later version.\line \line 15. Disclaimer of Warranty.\line \line THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\line APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\line HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY\line OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\line THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\line PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\line IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\line ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\line \line 16. Limitation of Liability.\line \line IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\line WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PAR TY WHO MODIFIES AND/OR CONVEYS\line THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\line GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\line USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\line DA TA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\line PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\line EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\line SUCH DAMAGES.\line \line 17. Interpretation of Sections 15 and 16.\line \line If the disclaimer of warranty and limitation of liability provided\line above cannot be given local legal effect according to their terms,\line reviewing courts shall apply local law that most closely approximates\line an absolute waiver of all civil liability in connection with the\line Program, unless a warranty or assumption of liability accompanies a\line copy of the Program in return for a fee.\line \line END OF TERMS AND CONDITIONS\line \line How to Apply These Terms to Your New Programs \line \line If you develop a new program, and you want it to be of the greatest\line possible use to the public, the best way to achieve this is to make it\line free software which everyone can redistribute and change under these terms.\line \line To do so, attach the following not ices to the program. It is safest\line to attach them to the start of each source file to most effectively\line state the exclusion of warranty; and each file should have at least\line the "copyright" line and a pointer to where the full notice is found.\line \line \line Copyright (C) \line \line This program is free software: you can redistribute it and/or modify\line it under the terms of the GNU General Public License as published by\line the Free Software Foundation, either version 3 of the License, or\line (at your option) any later version.\line \line This program is distributed in the hope that it will be useful,\line but WITHOUT ANY WARRANTY; without even the implied warranty of\line MERCHANTAB ILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\line GNU General Public License for more details.\line \line You should have received a copy of the GNU General Public License\line along with this program. If not, see .\line \line Also add information on how to contact you by electronic and paper mail.\line \line If the program does terminal interaction, make it output a short\line notice like this when it starts in an interactive mode:\line \line Copyright (C) \line This prog ram comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\line This is free software, and you are welcome to redistribute it\line under certain conditions; type `show c' for details.\line \line The hypothetical commands `show w' and `show c' should show the ap propriate\line parts of the General Public License. Of course, your program's commands\line might be different; for a GUI interface, you would use an "about box".\line \line You should also get your employer (if you work as a programmer) or school,\line if any, to sign a "copyr ight disclaimer" for the program, if necessary.\line For more information on this, and how to apply and follow the GNU GPL, see\line .\line \line The GNU General Public License does not permit incorporating your program\line into proprietary program s. If your program is a subroutine library, you\line may consider it more useful to permit linking proprietary applications with\line the library. If this is what you want to do, use the GNU Lesser General\line Public License instead of this License. But first, please read\line .\line } \par } qTox/osx/info.plist000066400000000000000000000056751415623743500146610ustar00rootroot00000000000000 CFBundleDisplayName qTox CFBundleDocumentTypes CFBundleTypeExtensions tox CFBundleTypeIconFile qtox_profile CFBundleTypeMIMETypes application/x-tox.profile CFBundleTypeName Tox profile CFBundleTypeRole Editor LSHandlerRank Owner LSItemContentTypes public.tox CFBundleExecutable qtox CFBundleIconFile qtox.icns CFBundleIdentifier chat.tox.qtox CFBundleLocalizations en_US bg_BG cs de_DE el es_MX fi_FI fr_FR hr_HR hu_HU it_IT lt_LT nl_NL nb_NO pl_PL pt_BR ru_RU sl sv tr_TR uk_UA zh_CH CFBundleName qTox CFBundlePackageType APPL CFBundleShortVersionString 1.17.4 CFBundleSignature toxq CFBundleURLTypes CFBundleTypeRole Viewer CFBundleURLIconFile qtox_profile CFBundleURLName Tox URL CFBundleURLSchemes tox CFBundleVersion 1.17.4 NSPrincipalClass NSApplication NSCameraUsageDescription $(PRODUCT_NAME) needs access to the camera for video calls. NSMicrophoneUsageDescription $(PRODUCT_NAME) needs access to the microphone for audio calls. UTImportedTypeDeclarations UTTypeConformsTo public.data UTTypeIdentifier public.tox UTTypeTagSpecification com.apple.ostype TOX public.filename-extension tox public.mime-type tox/x-profile qTox/osx/macfixrpath000077500000000000000000000020601415623743500150660ustar00rootroot00000000000000#!/usr/bin/env perl # This script used in `CMakeList.txt`. It's not required to run manually use strict; use File::Find; use Data::Dumper; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 1; my $BUNDLE_PATH = @ARGV[0] or die "Usage: macfixrpath \n"; my $prefix = `brew --prefix`; chomp $prefix; $prefix .= "/Cellar/"; my @libs = <$BUNDLE_PATH/Contents/Frameworks/*.framework/Versions/5/Qt*>; find sub { /\.dylib$/ or return; push @libs, $File::Find::name; }, $BUNDLE_PATH; my %deps; for (@libs) { my @deps = grep { @$_ } map { [m!^\t($prefix.+)/([^/]+) \(.+\)$!] } grep { /^\t/ } split /\n/, `otool -L $_`; my @resolved = map { my $dep = $_; [@$dep, grep { m!/$dep->[1]$! } @libs] } @deps; $_->[2] =~ s|\Q$BUNDLE_PATH\E/Contents/|\@executable_path/../| for @resolved; $deps{$_} = \@resolved if @deps; } for my $lib (sort keys %deps) { my $count = @{$deps{$lib}}; for my $dep (@{$deps{$lib}}) { my ($path, $name, $dest) = @$dep; system "install_name_tool", "-change", "$path/$name", $dest, $lib; } } qTox/osx/makedist.sh000066400000000000000000000033101415623743500147660ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see PWD=`pwd` echo " qTox qtox.pkg " > distribution.xml qTox/osx/qTox-Mac-Deployer-ULTIMATE.sh000077500000000000000000000236711415623743500176400ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2015 by RowenStipe # Copyright © 2016-2019 by The qTox Project Contributors # # This program is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # This uses the same process as doing it manually but with a few varients # Use: ./qTox-Mac-Deployer-ULTIMATE.sh -h set -e # Your home DIR really (Most of this happens in it) {DONT USE: ~ } SUBGIT="" #Change this to define a 'sub' git folder e.g. "-Patch" #Applies to $QTOX_DIR, $BUILD_DIR, and $DEPLOY_DIR folders for organization puropses if [[ $TRAVIS = true ]] then MAIN_DIR="${TRAVIS_BUILD_DIR}" QTOX_DIR="${MAIN_DIR}" else # the directory which qTox is cloned in, wherever that is MAIN_DIR="$(dirname $(readlink -f $0))/../.." QTOX_DIR="${MAIN_DIR}/qTox${SUBGIT}" fi QT_DIR="/usr/local/Cellar/qt5" # Folder name of QT install # Figure out latest version QT_VER=($(ls ${QT_DIR} | sed -n -e 's/^\([0-9]*\.([0-9]*\.([0-9]*\).*/\1/' -e '1p;$p')) QT_DIR_VER="${QT_DIR}/${QT_VER[1]}" TOXCORE_DIR="${MAIN_DIR}/toxcore" # Change to Git location LIB_INSTALL_PREFIX="${QTOX_DIR}/libs" [[ ! -e "${LIB_INSTALL_PREFIX}" ]] \ && mkdir -p "${LIB_INSTALL_PREFIX}" BUILD_DIR="${MAIN_DIR}/qTox-Mac_Build${SUBGIT}" DEPLOY_DIR="${MAIN_DIR}/qTox-Mac_Deployed${SUBGIT}" # helper function to "pretty-print" fcho() { local msg="$1"; shift printf "\n$msg\n" "$@" } build_toxcore() { echo "Starting Toxcore build and install" cd $TOXCORE_DIR echo "Now working in: ${PWD}" local LS_DIR="/usr/local/Cellar/libsodium/" #Figure out latest version local LS_VER=($(ls ${LS_DIR} | sed -n -e 's/^\([0-9]*\.([0-9]*\.([0-9]*\).*/\1/' -e '1p;$p')) local LS_DIR_VER="${LS_DIR}/${LS_VER[1]}" [[ $TRAVIS != true ]] \ && sleep 3 mkdir -p _build && cd _build fcho "Starting cmake ..." #Make sure the correct version of libsodium is used cmake -DBOOTSTRAP_DAEMON=OFF -DLIBSODIUM_CFLAGS="-I${LS_DIR_VER}/include/" -DLIBSODIUM_LDFLAGS="L${LS_DIR_VER}/lib/" -DCMAKE_INSTALL_PREFIX="${LIB_INSTALL_PREFIX}" .. make clean &> /dev/null fcho "Compiling toxcore." make > /dev/null || exit 1 fcho "Installing toxcore." make install > /dev/null || exit 1 } install() { fcho "==============================" fcho "This script will install the necessary applications and libraries needed to compile qTox properly." fcho "Note that this is not a 100 percent automated install it just helps simplify the process for less experienced or lazy users." if [[ $TRAVIS = true ]] then echo "Oh... It's just Travis...." else read -n1 -rsp $'Press any key to continue or Ctrl+C to exit...\n' fi # osx 10.13 High Sierra doesn't come with a /usr/local/sbin, yet it is needed by some brew packages NEEDED_DEP_DIR="/usr/local/sbin" if [[ $TRAVIS = true ]] then sudo mkdir -p $NEEDED_DEP_DIR sudo chown -R $(whoami) $NEEDED_DEP_DIR elif [[ ! -d $NEEDED_DEP_DIR ]] then fcho "The direcory $NEEDED_DEP_DIR must exist for some development packages." read -r -p "Would you like to create it now, and set the owner to $(whoami)? [y/N] " response if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]] then sudo mkdir $NEEDED_DEP_DIR sudo chown -R $(whoami) $NEEDED_DEP_DIR else fcho "Cannot proceed without $NEEDED_DEP_DIR. Exiting." exit 0 fi fi #fcho "Installing x-code Command line tools ..." #xcode-select --install if [[ -e /usr/local/bin/brew ]] then fcho "Homebrew already installed!" else fcho "Installing homebrew ..." ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" fi if [[ $TRAVIS != true ]] then fcho "Updating brew formulas ..." brew update fi fcho "Getting home brew formulas ..." if [[ $TRAVIS != true ]] then sleep 3 brew install git wget libtool cmake pkgconfig fi brew install check libvpx opus libsodium fcho "Starting git repo checks ..." #cd $MAIN_DIR # just in case # Toxcore if [[ -e $TOXCORE_DIR/.git/index ]] then fcho "Toxcore git repo already in place !" cd $TOXCORE_DIR git pull else fcho "Cloning Toxcore git ... " git clone --branch v0.2.13 --depth=1 https://github.com/toktok/c-toxcore "$TOXCORE_DIR" fi # qTox if [[ $TRAVIS = true ]] then fcho "Travis... You already have qTox..." else if [[ -e $QTOX_DIR/.git/index ]] then fcho "qTox git repo already in place !" cd $QTOX_DIR git pull else fcho "Cloning qTox git ... " git clone https://github.com/qTox/qTox.git fi fi # toxcore build if [[ $TRAVIS = true ]] then build_toxcore else fcho "If all went well you should now have all the tools needed to compile qTox!" read -r -p "Would you like to install toxcore now? [y/N] " response if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]] then build_toxcore else fcho "You can simply use the -u command and say [Yes/n] when prompted" fi fi if [[ $TRAVIS = true ]] then fcho "Updating brew formulas ..." brew update > /dev/null else brew install cmake fi # needed for kf5-sonnet # brew tap kde-mac/kde brew install ffmpeg libexif qrencode qt5 sqlcipher openal-soft #kf5-sonnet QT_VER=($(ls ${QT_DIR} | sed -n -e 's/^\([0-9]*\.([0-9]*\.([0-9]*\).*/\1/' -e '1p;$p')) QT_DIR_VER="${QT_DIR}/${QT_VER[1]}" # put required by qTox libs/headers in `libs/` cd "${QTOX_DIR}" sudo ./bootstrap-osx.sh } update() { fcho "------------------------------" fcho "Starting update process ..." #First update Toxcore from git cd $TOXCORE_DIR fcho "Now in ${PWD}" fcho "Pulling ..." # make sure that pull can be applied, i.e. clean up files from any # changes that could have been applied to them git checkout -f git pull read -r -p "Did Toxcore update from git? [y/N] " response if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]] then build_toxcore else fcho "Moving on!" fi #Now let's update qTox! cd $QTOX_DIR fcho "Now in ${PWD}" fcho "Pulling ..." # make sure that pull can be applied, i.e. clean up files from any # changes that could have been applied to them git checkout -f git pull read -r -p "Did qTox update from git? [y/N] " response if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]] then fcho "Starting OSX bootstrap ..." sudo ./bootstrap-osx.sh else fcho "Moving on!" fi } build() { fcho "------------------------------" fcho "Starting build process ..." rm -rf $BUILD_DIR rm -rf $DEPLOY_DIR mkdir $BUILD_DIR cd $BUILD_DIR fcho "Now working in ${PWD}" fcho "Starting cmake ..." export CMAKE_PREFIX_PATH=$(brew --prefix qt5) if [[ $TRAVIS = true ]] then STRICT_OPTIONS="ON" else STRICT_OPTIONS="OFF" fi cmake -H$QTOX_DIR -B. -DUPDATE_CHECK=ON -DSPELL_CHECK=OFF -DSTRICT_OPTIONS="${STRICT_OPTIONS}" make -j$(sysctl -n hw.ncpu) } deploy() { fcho "------------------------------" fcho "starting deployment process ..." cd $BUILD_DIR if [ ! -d $BUILD_DIR ] then fcho "Error: Build directory not detected, please run -ubd, or -b before deploying" exit 0 fi mkdir $DEPLOY_DIR make install cp -r $BUILD_DIR/qTox.app $DEPLOY_DIR/qTox.app } bootstrap() { fcho "------------------------------" fcho "starting bootstrap process ..." #Toxcore build_toxcore #Boot Strap fcho "Running: sudo ${QTOX_DIR}/bootstrap-osx.sh" cd $QTOX_DIR sudo ./bootstrap-osx.sh } dmgmake() { fcho "------------------------------" fcho "Starting DMG creation" cp $BUILD_DIR/qTox.dmg $QTOX_DIR/ } helpme() { echo "This script was created to help ease the process of compiling and creating a distributable qTox package for OSX systems." echo "The available commands are:" echo "-h | --help -- This help text." echo "-i | --install -- A slightly automated process for getting an OSX machine ready to build Toxcore and qTox." echo "-u | --update -- Check for updates and build Toxcore from git & update qTox from git." echo "-b | --build -- Builds qTox in: ${BUILD_DIR}" echo "-d | --deploy -- Makes a distributable qTox.app file in: ${DEPLOY_DIR}" echo "-bs | --bootstrap -- Performs bootstrap steps." fcho "Issues with Toxcore or qTox should be reported to their respective repos: https://github.com/toktok/c-toxcore | https://github.com/qTox/qTox" exit 0 } case "$1" in -h | --help) helpme exit ;; -i | --install) install exit ;; -u | --update) update exit ;; -b | --build) build exit ;; -d | --deploy) deploy exit ;; -bs | --bootstrap) bootstrap exit ;; -dmg) dmgmake exit ;; *) ;; esac fcho "Oh dear! You seemed to of started this script improperly! Use -h to get available commands and information!" echo " " say -v Kathy -r 255 "Oh dear! You seemed to of started this script improperly! Use -h to get available commands and information!" exit 0 qTox/osx/update-plist-version.sh000077500000000000000000000031701415623743500172720ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2019 The qTox Project Contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # script to change qTox version in `info.plist` file to the supplied one # # NOTE: it checkouts the files before appending a version to them! # # requires: # * correctly formatted `info.plist file in working dir # * GNU sed # usage: # # ./$script $version # # $version has to be composed of at least one number/dot set -eu -o pipefail # update version in `info.plist` file to supplied one after the right lines update_version() { local vars=( ' CFBundleShortVersionString' ' CFBundleVersion' ) for v in "${vars[@]}" do sed -i -r "\\R$v\$R,+1 s,()[0-9\\.]+()$,\\1$@\\2," \ "./info.plist" done } # exit if supplied arg is not a version is_version() { if [[ ! $@ =~ [0-9\\.]+ ]] then echo "Not a version: $@" exit 1 fi } main() { is_version "$@" update_version "$@" } main "$@" qTox/osx/updater/000077500000000000000000000000001415623743500143005ustar00rootroot00000000000000qTox/osx/updater/README.md000066400000000000000000000011571415623743500155630ustar00rootroot00000000000000The qTox OS X updater is a mix of objective C and Go compiled as static binaries used do effortless updates in the background. It uses Objective C to access Apples own security framework and call some long dead APIs in order to give the statically linked go updater permissions to install the latest build without prompting the user for every file. * Release commits: ``https://github.com/Tox/qTox_updater`` * Development commits: ``https://github.mit.edu/sean-2/updater`` Compiling: * ```clang qtox_sudo.m -framework corefoundation -framework security -framework cocoa -Os -o qtox_sudo``` * ```go build updater.go``` qTox/osx/updater/qtox_sudo.m000066400000000000000000000150771415623743500165150ustar00rootroot00000000000000// Modifications listed here GPLv3: https://gist.githubusercontent.com/stqism/2e82352026915f8f6ab3/raw/fca6f6f16fb8d61a64b6053dacf50c3433c2e0af/gistfile1.txt // // Copyright 2009 Performant Design, LLC. All rights reserved. // Copyright (C) 2014 Tox Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . // #import #include #include #include #include char *addFileToPath(const char *path, const char *filename) { char *outbuf; char *lc; lc = (char *)path + strlen(path) - 1; if (lc < path || *lc != '/') { lc = NULL; } while (*filename == '/') { filename++; } outbuf = malloc(strlen(path) + strlen(filename) + 1 + (lc == NULL ? 1 : 0)); sprintf(outbuf, "%s%s%s", path, (lc == NULL) ? "/" : "", filename); return outbuf; } int isExecFile(const char *name) { struct stat s; return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode)); } char *which(const char *filename) { char *path, *p, *n; path = getenv("PATH"); if (!path) { return NULL; } p = path = strdup(path); while (p) { n = strchr(p, ':'); if (n) { *n++ = '\0'; } if (*p != '\0') { p = addFileToPath(p, filename); if (isExecFile(p)) { free(path); return p; } free(p); } p = n; } free(path); return NULL; } int cocoaSudo(char *executable, char *commandArgs[], char *icon, char *prompt) { int retVal = 1; OSStatus status; AuthorizationRef authRef; AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0}; AuthorizationRights rightSet = {1, &right}; AuthorizationEnvironment myAuthorizationEnvironment; AuthorizationItem kAuthEnv[2]; myAuthorizationEnvironment.items = kAuthEnv; AuthorizationFlags flags = kAuthorizationFlagDefaults; if (prompt && icon) { kAuthEnv[0].name = kAuthorizationEnvironmentPrompt; kAuthEnv[0].valueLength = strlen(prompt); kAuthEnv[0].value = prompt; kAuthEnv[0].flags = 0; kAuthEnv[1].name = kAuthorizationEnvironmentIcon; kAuthEnv[1].valueLength = strlen(icon); kAuthEnv[1].value = icon; kAuthEnv[1].flags = 0; myAuthorizationEnvironment.count = 2; } else if (prompt) { kAuthEnv[0].name = kAuthorizationEnvironmentPrompt; kAuthEnv[0].valueLength = strlen(prompt); kAuthEnv[0].value = prompt; kAuthEnv[0].flags = 0; myAuthorizationEnvironment.count = 1; } else if (icon) { kAuthEnv[0].name = kAuthorizationEnvironmentIcon; kAuthEnv[0].valueLength = strlen(icon); kAuthEnv[0].value = icon; kAuthEnv[0].flags = 0; myAuthorizationEnvironment.count = 1; } else { myAuthorizationEnvironment.count = 0; } status = AuthorizationCreate(NULL, &myAuthorizationEnvironment, flags, &authRef); if (status != errAuthorizationSuccess) { NSLog(@"Could not create authorization reference object."); status = errAuthorizationBadAddress; } else { flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; status = AuthorizationCopyRights(authRef, &rightSet, &myAuthorizationEnvironment, flags, NULL); } if (status == errAuthorizationSuccess) { FILE *ioPipe; char buffer[1024]; int bytesRead; flags = kAuthorizationFlagDefaults; status = AuthorizationExecuteWithPrivileges(authRef, executable, flags, commandArgs, &ioPipe); /* Just pipe processes' stdout to our stdout for now; hopefully can add stdin pipe later as well */ for (;;) { bytesRead = fread(buffer, sizeof(char), 1024, ioPipe); if (bytesRead < 1) { break; } write(STDOUT_FILENO, buffer, bytesRead * sizeof(char)); } pid_t pid; int pidStatus; do { pid = wait(&pidStatus); } while (pid != -1); if (status == errAuthorizationSuccess) { retVal = 0; } } else { AuthorizationFree(authRef, kAuthorizationFlagDestroyRights); authRef = NULL; if (status != errAuthorizationCanceled) { // pre-auth failed NSLog(@"Pre-auth failed"); } } return retVal; } void usage(char *appNameFull) { char *appName = strrchr(appNameFull, '/'); if (appName == NULL) { appName = appNameFull; } else { appName++; } fprintf(stderr, "usage: %s command\n", appName); } int main(int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int retVal = 1; int programArgsStartAt = 1; char *icon = NULL; char *prompt = NULL; if (programArgsStartAt >= argc) { usage(argv[0]); } else { char *executable; if (strchr(argv[programArgsStartAt], '/')) { executable = isExecFile(argv[programArgsStartAt]) ? strdup(argv[programArgsStartAt]) : NULL; } else { executable = which(argv[programArgsStartAt]); } if (executable) { char **commandArgs = malloc((argc - programArgsStartAt) * sizeof(char**)); memcpy(commandArgs, argv + programArgsStartAt + 1, (argc - programArgsStartAt - 1) * sizeof(char**)); commandArgs[argc - programArgsStartAt - 1] = NULL; retVal = cocoaSudo(executable, commandArgs, icon, prompt); free(commandArgs); free(executable); } else { fprintf(stderr, "Unable to find %s\n", argv[programArgsStartAt]); usage(argv[0]); } } if (prompt) { free(prompt); } [pool release]; return retVal; } qTox/osx/updater/updater.go000066400000000000000000000056111415623743500162760ustar00rootroot00000000000000package main import ( "fmt" "io/ioutil" "log" "os" "os/exec" "os/user" "syscall" "bitbucket.org/kardianos/osext" ) var custom_user string func fs_type(path string) int { //name := "FileOrDir" f, err := os.Open(path) if err != nil { fmt.Println(err) return -1 } defer f.Close() fi, err := f.Stat() if err != nil { fmt.Println(err) return -1 } switch mode := fi.Mode(); { case mode.IsDir(): return 0 case mode.IsRegular(): return 1 } return -1 } func install(path string, pathlen int) int { files, _ := ioutil.ReadDir(path) for _, file := range files { if fs_type(path+file.Name()) == 1 { addpath := "" if len(path) != pathlen { addpath = path[pathlen:len(path)] } fmt.Print("Installing: ") fmt.Println("/Applications/qtox.app/Contents/" + addpath + file.Name()) if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name()); os.IsNotExist(err) { newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+addpath+file.Name()) newfile.Run() } cat := exec.Command("/bin/cat", path+file.Name()) auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+addpath+file.Name()) auth.Stdin, _ = cat.StdoutPipe() auth.Stdout = os.Stdout auth.Stderr = os.Stderr _ = auth.Start() _ = cat.Run() _ = auth.Wait() } else { install(path+file.Name()+"/", pathlen) } } return 0 } func main() { syscall.Setuid(0) usr, e := user.Current() if e != nil { log.Fatal(e) } CHECK: if usr.Name != "System Administrator" { fmt.Println("Not running as root, relaunching") appdir, _ := osext.Executable() appdir_len := len(appdir) sudo_path := appdir[0:(appdir_len-7)] + "qtox_sudo" //qtox_sudo is a fork of cocoasudo with all of its flags and other features stripped out if _, err := os.Stat(sudo_path); os.IsNotExist(err) { fmt.Println("Error: No qtox_sudo binary installed, falling back") custom_user = usr.Name usr.Name = "System Administrator" goto CHECK } relaunch := exec.Command(sudo_path, appdir, usr.Name) relaunch.Stdout = os.Stdout relaunch.Stderr = os.Stderr relaunch.Run() return } else { if len(os.Args) > 1 || custom_user != "" { if custom_user == "" { custom_user = os.Args[1] } update_dir := "/Users/" + custom_user + "/Library/Preferences/tox/update/" if _, err := os.Stat(update_dir); os.IsNotExist(err) { fmt.Println("Error: No update folder, is check for updates enabled?") return } fmt.Println("qTox Updater") killqtox := exec.Command("/usr/bin/killall", "qtox") _ = killqtox.Run() install(update_dir, len(update_dir)) os.RemoveAll(update_dir) fmt.Println("Update metadata wiped, launching qTox") launchqtox := exec.Command("/usr/bin/open", "-b", "chat.tox.qtox") launchqtox.Run() } else { fmt.Println("Error: no user passed") } } } qTox/osx/welcome.txt000066400000000000000000000010631415623743500150300ustar00rootroot00000000000000Welcome to the qTox for OS X internal nightly installer! Please report all bugs to https://github.com/qTox/qTox ######################################################## ## WARNING: Install me to your user ONLY ## ## Failure to do so WILL break automatic updating ## and you WILL be left with and old and ## potentially broken qTox install! ## ######################################################## qTox/res.qrc000066400000000000000000000242461415623743500133330ustar00rootroot00000000000000 res/nodes.json res/font/DejaVuSans.ttf audio/notification.s16le.pcm audio/ToxIncomingCall.s16le.pcm audio/ToxOutgoingCall.s16le.pcm audio/ToxEndCall.s16le.pcm img/add.svg img/avatar_mask.svg img/contact.svg img/contact_dark.svg img/group.svg img/group_dark.svg img/icons/qtox.svg img/settings.svg img/settings/av.png img/settings/general.png img/settings/identity.png img/settings/privacy.png img/status/away.svg img/status/away_notification.svg img/status/busy.svg img/status/busy_notification.svg img/status/offline.svg img/status/offline_notification.svg img/status/online.svg img/status/online_notification.svg img/taskbar/dark/taskbar_online.svg img/taskbar/dark/taskbar_online_event.svg img/taskbar/dark/taskbar_away.svg img/taskbar/dark/taskbar_away_event.svg img/taskbar/dark/taskbar_busy.svg img/taskbar/dark/taskbar_busy_event.svg img/taskbar/dark/taskbar_offline.svg img/taskbar/dark/taskbar_offline_event.svg img/taskbar/light/taskbar_online.svg img/taskbar/light/taskbar_online_event.svg img/taskbar/light/taskbar_away.svg img/taskbar/light/taskbar_away_event.svg img/taskbar/light/taskbar_busy.svg img/taskbar/light/taskbar_busy_event.svg img/taskbar/light/taskbar_offline.svg img/taskbar/light/taskbar_offline_event.svg img/transfer.svg themes/dark/palette.ini themes/dark/fileTransferWidget/fileDone.svg themes/dark/chatArea/chatArea.css themes/dark/chatArea/chatHead.css themes/dark/chatArea/innerStyle.css themes/dark/chatArea/scrollBarUpArrow.svg themes/dark/chatArea/scrollBarDownArrow.svg themes/dark/chatArea/scrollBarLeftArrow.svg themes/dark/chatArea/scrollBarRightArrow.svg themes/dark/chatForm/buttons.css themes/dark/chatForm/fullScreenButtons.css themes/dark/chatForm/callButton.svg themes/dark/chatForm/labels.css themes/dark/chatForm/micButton.svg themes/dark/chatForm/micButtonRed.svg themes/dark/chatForm/videoButton.svg themes/dark/chatForm/videoButtonRed.svg themes/dark/chatForm/volButton.svg themes/dark/chatForm/volButtonRed.svg themes/dark/chatForm/videoPreview.svg themes/dark/chatForm/videoPreviewRed.svg themes/dark/chatForm/emoteButton.svg themes/dark/chatForm/fileButton.svg themes/dark/chatForm/screenshotButton.svg themes/dark/chatForm/searchCalendarButton.svg themes/dark/chatForm/searchDownButton.svg themes/dark/chatForm/searchHideButton.svg themes/dark/chatForm/searchSettingsButton.svg themes/dark/chatForm/searchUpButton.svg themes/dark/chatForm/sendButton.svg themes/dark/chatForm/exitFullScreenButton.svg themes/dark/emoticonWidget/dot_page.svg themes/dark/emoticonWidget/dot_page_current.svg themes/dark/emoticonWidget/dot_page_hover.svg themes/dark/emoticonWidget/emoticonWidget.css themes/dark/friendList/friendList.css themes/dark/msgEdit/msgEdit.css themes/dark/settings/mainHead.css themes/dark/settings/checkboxChecked.svg themes/dark/settings/checkboxCheckedDisabled.svg themes/dark/statusButton/statusButton.css themes/dark/statusButton/menu_indicator.svg themes/dark/window/general.css themes/dark/window/profile.css themes/dark/window/statusPanel.css themes/dark/window/window.css themes/dark/chatArea/info.svg themes/dark/chatArea/spinner.svg themes/dark/chatArea/typing.svg themes/dark/chatArea/error.svg themes/dark/fileTransferInstance/no.svg themes/dark/fileTransferInstance/pause.svg themes/dark/fileTransferInstance/yes.svg themes/dark/fileTransferInstance/dir.svg themes/dark/fileTransferInstance/arrow_white.svg themes/dark/fileTransferInstance/browse.svg themes/dark/fileTransferInstance/filetransferWidget.css themes/dark/genericChatForm/genericChatForm.css themes/dark/acceptCall/acceptCall.svg themes/dark/addFriendForm/toxId.css themes/dark/rejectCall/rejectCall.svg themes/dark/notificationEdge/notificationEdge.css themes/dark/loginScreen/loginScreen.css themes/dark/contentDialog/contentDialog.css themes/dark/tooliconsZone/tooliconsZone.css themes/default/palette.ini themes/default/fileTransferWidget/fileDone.svg themes/default/chatArea/chatArea.css themes/default/chatArea/chatHead.css themes/default/chatArea/innerStyle.css themes/default/chatArea/scrollBarUpArrow.svg themes/default/chatArea/scrollBarDownArrow.svg themes/default/chatArea/scrollBarLeftArrow.svg themes/default/chatArea/scrollBarRightArrow.svg themes/default/chatForm/buttons.css themes/default/chatForm/fullScreenButtons.css themes/default/chatForm/callButton.svg themes/default/chatForm/labels.css themes/default/chatForm/micButton.svg themes/default/chatForm/micButtonRed.svg themes/default/chatForm/videoButton.svg themes/default/chatForm/videoButtonRed.svg themes/default/chatForm/volButton.svg themes/default/chatForm/volButtonRed.svg themes/default/chatForm/videoPreview.svg themes/default/chatForm/videoPreviewRed.svg themes/default/chatForm/emoteButton.svg themes/default/chatForm/fileButton.svg themes/default/chatForm/screenshotButton.svg themes/default/chatForm/searchCalendarButton.svg themes/default/chatForm/searchDownButton.svg themes/default/chatForm/searchHideButton.svg themes/default/chatForm/searchSettingsButton.svg themes/default/chatForm/searchUpButton.svg themes/default/chatForm/sendButton.svg themes/default/chatForm/exitFullScreenButton.svg themes/default/emoticonWidget/dot_page.svg themes/default/emoticonWidget/dot_page_current.svg themes/default/emoticonWidget/dot_page_hover.svg themes/default/emoticonWidget/emoticonWidget.css themes/default/friendList/friendList.css themes/default/msgEdit/msgEdit.css themes/default/settings/mainHead.css themes/default/statusButton/statusButton.css themes/default/statusButton/menu_indicator.svg themes/default/window/general.css themes/default/window/profile.css themes/default/window/statusPanel.css themes/default/window/window.css themes/default/chatArea/info.svg themes/default/chatArea/spinner.svg themes/default/chatArea/typing.svg themes/default/chatArea/error.svg themes/default/fileTransferInstance/no.svg themes/default/fileTransferInstance/pause.svg themes/default/fileTransferInstance/yes.svg themes/default/fileTransferInstance/dir.svg themes/default/fileTransferInstance/arrow_white.svg themes/default/fileTransferInstance/browse.svg themes/default/fileTransferInstance/filetransferWidget.css themes/default/genericChatForm/genericChatForm.css themes/default/acceptCall/acceptCall.svg themes/default/addFriendForm/toxId.css themes/default/rejectCall/rejectCall.svg img/login_logo.svg themes/default/notificationEdge/notificationEdge.css themes/default/loginScreen/loginScreen.css img/others/logout-icon.svg img/caps_lock.svg themes/default/contentDialog/contentDialog.css themes/default/tooliconsZone/tooliconsZone.css qTox/res/000077500000000000000000000000001415623743500126145ustar00rootroot00000000000000qTox/res/font/000077500000000000000000000000001415623743500135625ustar00rootroot00000000000000qTox/res/font/DejaVuSans.ttf000066400000000000000000027065241415623743500163240ustar00rootroot00000000000000@FFTMsLGDEFώhGPOSV5GSUB@YMATH28}h>OS/2Y-v-VcmapI&cvt i9fpgmq4vjgasp glyfa {head  T6hhea  T$hmtx%߭ Uakern ; ?loca`( amaxpq X` nameoM X=postHȖT dprep; h=))(0Y         !%&&'KLLMOPTUst?@@ABCJKQRWX    ) * , -            ghhiijjkmnnoyz!",-l \DFLTzarabarmnbraicanschercyrlgeorgrekhanihebrkana*lao 6latnFmathnko ogamrunrtfngthaiKUR SND URD MKD SRB 4ISM 4KSM 4LSM 4MOL 4NSM 4ROM 4SKS 4SSM 4 kern8kern>markFmarkTmark\markdmkmkjmkmkrmkmkx    "*2:BLT\dlt|fF  f!<":#89;=4CCG Xr s^ 0&:  vy~vy~ *06<BHNTZ`flrx~:::::r: 4 4 `Ltuwxz{|}Ltuwxz{|}RX^djpv|$ lJGH N>X  &,lwlwlwfn@CDEFIRW &,28l`l~l~l`l~l`Z& #HNTZ`flrx~tt;888  !"    ! "(.4:@FB :v| $*06<BHNTZ`flrx~hhh=DhhhVDhh=DDnnnnhh   !# )""0$$1&,29  %",,78 $*06<BHNTZ`flrx~ &,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ $6HZl~ cj cj cj cj c c cj cj&vy~>DJPV\bhntz< w &,28>DJPV\bhntz "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntz     " ( . 4 : @ F L R X ^ d j p v |     $ * 0 6 < B H N T Z ` f l r x ~      & , 2 8 > D J P V \ b h n t z     " ( . 4 : @ F L R X ^ d j p v |     $ * 0 6 < B H N T Z ` f l r x ~  &,28>DJPV\bhntz "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntz "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~U,!8|Q'n ppjjj,v,,vjj  XXXXD[j[j, 8 8>>j pjjj^jj,,,,,,,j^ppjjjj         XXXXX[j 8pp 8v 8 j j j 8 8 v j j j p D j>>>D>>> j,    ppjI^`k/#eYYY++++++jj++jj++jj++ 8 8jj 8 8jj,,X X ,,XX,,X X ,,X X jjjjjj     j j,j,j j j,j,j D Dvv j j,j,j>>++ jRj 8 8,j,j        ++++pp++,,,, ,,,,,,,,,,2  pp++pp++jjjj++jj++,,XX,,XX,,XXjjjj    XXjjXXjjXX&j&jXX&j&j[j[jSjSj[j[jSjSjXX 8 8jjjj 8 8,j,j>>SS&j&j>++jjj  pp++j++ 8jjjj++^++j++,XX,XX,XX,X X   >SSp++ jIII^^^```kkk///###eeeYYYYYYYYY^ppjj     XppXXX p pX&j&j[jSjSjXppvjjjj jjj j,j,j 8,j,j v j,j,j p j,j,j>SS>SS+I++c++++ 'DPsBDVk3=?PltvvD"&vy~BHNTZ`flrx~ 06<BHNTZ`flrx~c00008000q=i000010f00j=0P'-/355 78 :;=A0 $6HZl~ cr cr cr cr cr cr cr cr Ltuwxz{|}Z`flrx~``L \ &,28>DJPV\bhntz "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntz     " ( . 4 : @ F L R X ^ d j p v |     $ * 0 6 < B H N T Z ` f l r x ~      & , 2 8 > D J P V \ b h n t z     " ( . 4 : @ F L R X ^ d j p v |     $ * 0 6 < B H N T Z ` f l r x ~  &,28>DJPV\bhntz "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntz "(.4:@FLRX^djpv|R``S`4rrLRLX X X X [r[r~x,LLRLLRL@@xX X X X X [rrLLLLLLLLxxx99xxxxxx44f_RLFRI^`n#YYY++++++LL++LL++++LL@LL@XXXXXXXX@@xxxxxxxxxxll_e++RLR L 99FFX++++XV++,,,:,,,,:,:,,,,,:,:LrrX+F+Frr++L&LRR++LL++XXXXX~X~X X X X RRX X & & X X &&[r[rSrSr[r[rSrSr~~x~x~LLFLRFSrSrR&R&R++R&RL XVX++++LLRL++R++++XXXxXxXxXxX~X~4S4S4++&RIII^^^```nnn###YYYYYYYYY@X RRX X RRX & & X XX[rSrSrxrxLLLFLLRFR@xxxxxx9xxxff4S4S4fSfSfSrSr++++++((22;;Ps')xVk3=?PltvvD Ltuwxz{|}z "(`{{{{{{{{` <BHNTZ`flrx~]xx@[")@>E"~~x2x::#=bcGHJ"> @FLRX^djpv|]kxyyyxyz[f"w)h>yEy`P["~[~t`zxy2{`uxJJ::#=QQ``bc @CDEFIRSTUVW 28>DJPV\bhnttbbbbt`~~`~` Z R   !"# $*06<BHNTZ 28>DJPV\bhntz "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntz%$=D]4568:();AA=HH>QR?YYAaaBy{CFGHI**J77KTTLMN  OY\^_a cg'r)-|25<HLPSShj bNj $*06<Jms}P-   {{ Y &,28>DJPV\bhntz "(.4:@FLRX^djpv|L-./++'fs.}////'s////s}\'/'R///RJsR//'s{y5D;+./}RY$&'(+,01268=DFGHKLPQRVX]-HQRYayz{*7T , - $%BCFGHOS L rx~ &,28>DJPV\bhntz "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~ &,28>DJPV\bhntz "(.4:@FLRX^djpv|     $ * 0 6 < B H N T Z ` f l r x ~      & , 2 8 > D J P V \ b h n t z     " ( . 4 : @ F L R X ^ d j p v |     $ * 0 6 < B H N T Z ` f l r x ~      & , 2 8 > D J P V \ b h n t z L\/.Rs''}srJf;RRsRR%}^Gb`R////'}sJf////Rss};\f'7R/'z`RR///.RR'}r`RTTRTcRRJ@@RjRjRbRb}RRRRRRRRsR555RRaRt;Q'RRRRRRR}}^G^dRRR::R'aHRR_R:RGR R.R~RJ}'/'}'}^TTT@X}Tg^GX^Rffftftf//LBRRf,4$R'_zRf4L}`ReT'sR^G^5sRR/nRRwRRJV1vvvR;nRR'RR RRR\R}%fOL5s/<\R&Rx9\wR}R`$= D]$>BCDIJKMOPXZ  [\]()^--`AAaEEbHHcNNdQReTTgYYhaailljvvky{loqu xJLgikmmGGJJMM##4477}~hikps{   , -      ! " ( ) * , / 0 5 9:;=OR@@TU W []-i25y;P}wSTkkuu   !"# $*06<BHNTZl l "(.4:@FLRX^djpv|  &,2:@FLRX^djpx~ $*06<BHNTZ`flrx~ (.4:@FLTZ`flrx~     " ( . 4 : @ F L R X ^ d j p v |     $ * 0 6 < B J P V \ b h n t z     " ( . 4 : @ F L R X ^ d j p v |     $ * 0 6 < B H N T Z ` f l r x ~      & , 2 8 > D J P V \ b h n t z  "(.4:@FLRX^djpv|  &,28>DJPV^djpv| $*06<BHNTZ`flrx~ &.4:@FLRX^djpv| "(.4:@FLRX^djpv| $*06<BHNTZ`flrx~L\/.*s''}srJ{#{{j{//{{s{ {o{{'{}{^{G{b{`{{'{{}s{J{#Q{{{{s}{\LX';\//''{ssr`{{{'{{{./'}{r`{{T{{{{c{R{R{J|@{@{{{jj{{b{b{}{/{{{{{{{{s{{{3{33{^{a{p{{;{Q{'{{{}{}{^{G{^d{{{{{::'a{H{{/{{j:G{ .J{~^{}J|E{}{{{{E}{p{{t{}{j{{{b{{^{~~{}{t{^{{{{/'{{{H/rtOs}s'LsoqGGYNsT{a{E{{{{@{{t{{{{}{{{{T{`{kb{{K{{{{{{{{t{{'{///{{4{^{c{s{"{O{,s{O{%'}{{{O{t{t{e{sK{{{{'}{E{b{{t{{{^{{{T{{TT{{@{{{{{}{{{{{{{{{T{g{b{^{G{{{{^{{{LBRf,4${' _zf4DL}1{`{e**}T{{'s^{G{^{{{3s{{/ {0{{{{'l{n{T{T{n{wJ{{{1{{v{vqv{*\;{n{{{'o{{{{\j{{}{'{fO{L5s/<\&Rx9{\{w{{{{{}{y$= D]$>?ABCDFGIJ  KL()M55OABPEERHISNNUPVVXY][]___baacffdijelpgt{ltu yJLgikmmF^efjjllnnppxx  6LT c#$i+,k/7mAAvNNwYYx__yccz}~{}~hps|               &&::@@   -//&15':P,wCVWXRTbkkeuufyyg|}hjk  "#( .",5@A$*06>FLRX`flrx~ &,28>FNTZ`flrx~{{{{{{{{{{{{{{{{{{orr{r{{{{{{{{{{{{{{A{{{{{{{{{{{{{{ {{{{{{{{{{{{{{>D >DJPV\bhntzX(l>tV(t>\X(RDtR<D$(,28<=DHLRX\]HYayz{*7TS&!0# 5PKr9KD &&K9a}au9aauaau/&DaDDkkDDDDkDD)ak}/DDa9}D}&&9}k}k}&D aDY}aaauNaaau}}k}ka aakkAk&k}}DHVaD)kkDN9a}au9aau/9a}au9aau/9a}au9aau/&kD&9a}au9aau/9a}a9aa/D?}DVD aDKr9KD &&Kk}k&/<&R$$%%&&''))**++-- .. // 22 33 445566778899::;;<<==HHIINNQQRRUUYYZZ[[ \\!mm"}}#$%&%'( )*+!!,,-((. /  0  ""&&100::?? 2 3  4 $$%%&&''))** ++-- ./22 3344 5566 778899::;;<<==DDFFGGHHIIJKLLOOPPQQRRTTUUVV WW!XX"YY#ZZ$[[%\\&mm'}}()* ++,,-../"/&&010101234352678888393:;;  3<3<=<;    !! "" ## $$>%%5&&''!((?++@--@//@0011"33@55@66A77B88C99D::??4EFEF G43H4IJ ~ ~A  K L B A B C D M N  Oa$%&')*+-./23456789:;<=HINQRUYZ[\m}  "&0:? `$XRjRVX\^b dh JDFLTzarabarmnbraicanschercyrlgeorgrek$hani4hebr@kanaRlao ^latnjmathnko ogamrunrtfng(thai4 KUR SND (URD (   MKD SRB F CAT ZESP ZGAL ZISM bKSM bLSM bMOL vNSM bROM vSKS bSSM b     RQDaaltaaltaaltcaseccmpccmpccmpccmpdligdligdligfinafinahlig hliginitinitliga$liga,locl2locl8medi>mediDrligJrligTsalt\saltbsalth     (RZbjr $,4<DLT\dlt|R `  DLf      >    d F z     \ HXPTXvHPX`hp EFGcX,,,H!!  !! &   B9&&99 D9LM *_ + j$=EEGGIIKKLMNOWW      ""$$&&((**,,..0022446688:;==??AAHHRRTTVVyz  **__ + +   &!!!&   $$4F""$$4F##$$4F$$$$4F%%$$4F&&(0RU'.6VVXZ'.6[\^_'.6`bdd'(0eh' 0DF Bt|~39?BEH K" &tl$  6(!$B',*4-00n369<8:?B>NHFJLTqNvPhjs#%BRR5;ADGJM 8 * #&D).,6/22p58;>=<AD@PVsP TTVVX\ahjprs)OBRQ4:@CFI L  7 )"%C(-+5.11o47:=<;@C?OUrO TTVVX\ahjprs)O2  zz yvvyz~&8Jlzz }z }z xwut xwtuwxz} > $~|~|6 "(IOILOLIIRl$*06<x{vztyrxpwovmuktS~Q}P| &,y{wzuysxqwltR}T}Uiqs3'B 8  WVWA(:FPZfr ,"  + *" $; V 0 (/ F X R")567DF  hgiefkj$:@GMU[hgiefkj$:@GMU[kuR,-DO *"&kuR,-DO\ 9 %#&$)*"+',(  y!ST|}z{ LM *_ + jjjjj P< ```h`UvZZZZZZZZrZZ8<(:0 &2>JVbfjnr~&2>JVbfjnr~PpN KI P KJ P KK P@ L  M  N  O  P  Q  R  S  T  KKU P LKV P MKW P NKX P oY pZ q[ r\ s] t^ u_ v` wa xb yc zd {e |f }g ~h i j k Dl E0F\r Q  R  S  /W  1X  [T  ]U  _V  ( u ((  (  ( u ((  (  ( u ((  ( q ( f (( ~ ( q ( r ((  (( ((  ( _(_( ( r ((  (( ((  (  ( ( ( o (( ( l (  ( q ( ~ ( q ( u (  ( f ( ~ ( (B((B((q(Vq(Vq(Vq(V >>@@^`   K N o Bq !    / 1 [ ] _33f n $`) PfEd@ m`, , ~OSXZbw%V_  :UWZpt?5JR>PjGv#.[jx{}EMWY[]} d q ! !I!K!N!!###!#(#,#u#z#}#######$#$i&&&&'' '''K'M'R'V'^''''''()) )A))))***/*k***++$+T,w,-%-e-o...%..MGMQWn+AKSWg&A6<>ADO#t QWZ\pz1Ya  !@WZ`ty? 7LT@RtFn&0]w{} HPY[]_ j t !! !K!N!P!!###$#+#s#z#}#######$"$`%&&&''' ')'M'O'V'X'a'''''')) )@))))** */*j*}**+++S,`,y--0-o..."..MЦDLPTb"0FNVd(8>@CFR pva_WRA@?72/.)(&! ,)'`^hgedcWUTM84953*n~e`\VT sofT |{@:80ğmlllllyl^lXlVkkkkkkkkkkkkkQ$Q#konnmiWWG < ~bOQSWXZZ\bpwz#$%81VY_a%'BG  I  KLMN!:O@UiWWZZ`ptty??"#%&()*.589:<ILQRXbd 57JLRTo>@PRjt!.>?FG@nvBKh#}&.0[]jwx{{}}   E HM <PW BYY J[[ K]] L_} M l      d j q H t P k x !! ! !I !K!K !N!N !P! !! !# ## ##! #$#( #+#, #s#u #z#z #}#} ## ## ## ## ## ## ## $"$# $`$i %& &&u&&&&'''' ' ''')'K'M'M'O'R'V'V'X'^'a'''''5''C''E''F'(L))\) ) ^)@)A`))b))d))l))m**o* *r*/*/*j*k*}*****++++$+S+T,`,w,y,--%-0-e!-o-oW..X..Y.".%Z....^MM_DGLMPQTWbn"+0AFK NS&VW,dg.26<>IQk&o(A68<>>@ACDFOR=AKMSW #gptkvp #V 89w;>y@D}FFJPRkՠ!" $$ '')24799;;abdd!gj"lr&tw-y|1~~506-.1155#%+-@ CC    !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a rdei xpk vj s gw U l|>cn T m} b : '   y  qz5fqu-J3T99NR7s`s3VV9s3D{o{RoHT3fs +b-{T#\q#H99`#fy```{w``b{{Rffw;{J/}oo5jo{-{T7fD)fs, %Id@QX Y!-,%Id@QX Y!-,  P y PXY%%# P y PXY%-,KPX EDY!-,%E`D-,KSX%%EDY!!-,ED-,%%I%%I` ch #:e:-ff@ /10!%!!fsr)5 5@ K TX8Y<2991/0 P ]%3#3#5qeB@KTKT[X8Y1<20@0 @ P ` p ]#!#o$++`@1      91/<<<<<<<2220@   ]!! !3!!!!#!#!5!!5!T%Dh$ig8R>hggh`TifaabbNm!(/@U" '&( /)/))/B" ) *!#*- ) " & 0K TX8YK TKT[KT[X@8Y<<<1/299990KSX99Y"#.'5.546753.'>54&dijfod]SS\dtzq{---@A$*.U# jXV`OnZXhq) #'3@6$%&%&'$'B .$ &($4'!%   ! + 1 4K TK T[K T[KT[KT[K T[X18Y9912<0KSXY""32654&'2#"&546"32654&%3#2#"&546WccWUccUVcbWWcd1Zܻۻa ۻۼ 0@      !         B  (('+'$ .  .'.'!!199999991/9990KSX99999999Y"2]@ " ) **&:4D ^YZ UZZY0g{ "-  ' (   2'') #**(/2; 49?2J LKFO2VZ Y UY\_2j i`2uy z 2229]]3267 >73#'#"5467.54632.#"[UԠ_I{;B h]hΆ02޸SUWDi;#QX?@Yr~YW׀c?}<$$/1oX3go7@ KTKT[X8Y10@ @P`p]#o+{ 7@  KTX 8YKTX @8Y29910#&547{>;o @ <99103#654<:=JN@,       <2<2991<22990 %#'-73%g:r:g:PrPbybcy #@   <<1/<<0!!#!5!-Ө-Ӫ--@ 1073#ӤR@d10!!d1/073#B-@B/9910KSXY"3#m #@  10"32'2#"  P3343ssyzZ @@B  KTX@8Y1/20KSXY"]7!5%3!!JeJsHHժJ@'B   KTKT[KT[X8Y91/20KSX9Y"@2UVVzzvtvust]]%!!567>54&#"5>32Ls3aM_xzXE[w:mIwBC12\ps(p@.    #)&  )KTKT[X 8Y99190@ daa d!]!"&'532654&+532654&#"5>32?^jTmǹSrsY %Đ%%12wps{$& Ѳ|d @   B    K TK T[X 8Y<291/<290KSXY"@* *HYiw+&+6NO O Vfuz ]] !33##!55^%3`d^@#    KTKT[X8YKTX@8Y190!!>32!"&'532654&#",X,$^hZkʭQTժ 10$& $X@$  "% " !%190@]]"32654&.#">32# !2 LL;kPL;y$&W]ybhc@B991/0KSXY"KTX@878Y@X9Hg]]!#!3V+ #/C@% '-'0 $*$ !0991990"32654&%.54$32#"$54632654&#"HŚV г "Əُattt$X@# %!"" %190@]]7532#"543 !"&2654&#"LK:lL>$& V\s[#@<21/073#3### %@  <2103#3#ӤR#٬@^M@*B$#29190KSXY" 5Ѧ`@ #<210!!!!^O@+B$#<9190KSXY"55//m$e@+$     &%K TX8Y99991/9990y z z ]%3##546?>54&#"5>32ſ8ZZ93lOa^gHZX/'eVY5^1YnFC98ŸLVV/5<4q L@2  L4307$7CM34( (+(I+*(I,=M<9912990K TK T[KT[KT[KT[XMMM@878Y@ NN/N?N]32654&#"#"&5463253>54&'&$#"3267#"$'&5476$32|{zy!orqp ˘s'6@   0210].# !267# !2'ffjzSb_^^_HHghG.@   2 99991/0`]3 !%! )5BhPa/w.,~ .@   21/0 ]!!!!!!9>ժF# )@ 21/0 ]!!!!#ZpPժH7s9@ 43 1990%!5!# !2.# !26uu^opkSUmnHF_`%; ,@ 8  221/<20P ]3!3#!#"d+9.KTX@8Y1/0@ 0@P`]3#+f B@  9 KTX@8Y991990@ 0 @ P ` ]3+53265M?nj @(B  291/<290KSXY"]@ ((764GFCUgvw    (+*66650 A@E@@@ b`hgwp  ,]q]q3! !#3wH1j%@ :1/0@ 0P]3!!_ժ @4  B    >  91/<290KSXY"p]@V   && & 45 i|{y   #,'( 4<VY ej vy ]]! !###-}-+3 y@B6 991/<2990KSXY" ]@068HGif FIWXeiy ]]!3!#j+s #@  310"32' ! ':xyLHH[[bb:@   ? 291/0@ ?_]32654&#%!2+#8/ϒs R@*  B     39991990KSX9Y""32#'# ! '? !#y;:xLHHab[T@5  B    ?  299991/<9990KSX9Y"@]@Bz%%%&'&&& 66FFhuuw]]#.+#! 32654&#A{>ٿJx~hb؍O'~@<    B %( "-"(9999190KSX99Y")])/)O)].#"!"&'532654&/.54$32Hs_wzj{r{i76vce+ٶ0/EF~n|-&J@@@1/20K TX@878Y@  @ p ]!!#!ժ+)@@   8AKTX8Y1299990]332653! ˮ®u\*$h@'B91/290KSXY"P]@b*GGZ} *&&))% 833<<7HEEIIGYVfiizvvyyu)]]!3 3J+D {@I      B     91/<2290KSXY"]@  ($ >>4 0 LMB @ Yjkg ` {|      !   # $ %  <:5306 9 ? 0FFJ@E@BBB@@ D M @@XVY Pfgab```d d d wv{xwtyywpx   []]3 3 3# #D:9:9+=; f@  1 ]@ /<20KBPX@   @    Y3 3 # #su \Y+3{@(B@@ 91/290KSXY" ]@<5000F@@@QQQe &)78@ ghxp ]]3 3#f9\ @BB K TK T[X8Y991/0KSXY"@@ )&8HGH    / 59? GJO UYfio wx ]]!!!5!sP=g՚oX;@CK TX@8YKTKT[X8Y210!#3!XB-@B/9910KSXY"#mo0@CKTKT[X@8Y<10!53#5oXޏ@ 91290 # #HHu-10!5f1@ D10K TKT[X@878Y #ofv{-{ %@'   #   E&22991/9990@n0000 0!0"?'@@@@ @!@"PPPP P!P"P'p' !"'''000 0!@@@ @!PPP P!``` `!ppp p! !]]"326=7#5#"&5463!54&#"5>32߬o?`TeZ3f{bsٴ)Lfa..'' 8@  G F221/0`]4&#"326>32#"&'#3姒:{{:/Rdaadq{?@  HE210@ ].#"3267#"!2NPƳPNM]-U5++++$$>:#qZ8@G E221/0`]3#5#"3232654&#":||ǧ^daDDaq{p@$   KE9190@)?p?????,// , ooooo ]q]!3267# 32.#" ͷjbck)^Z44*,8 Cė/Y@     LK TX @8YKTX 8Y<<991/22990@P]#"!!##535463cM/ѹPhc/яNqVZ{ (J@#  &#' & G E)221/990`***]4&#"326!"&'5326=#"3253aQQR9||9=,*[cb::bcd4@  N  F21/<90`]#4&#"#3>32d||Bu\edy+@F<21/0@  @ P ` p ]3#3#`Vy D@   O  F<2991990@ @P`p]3+532653#F1iL`a( @)B F 291/<90KSXY" ]@_ ')+Vfgsw    ('(++@ h` ]q]33 ##%kǹi#y"F1/0@ @P`p]3#{"Z@&   PPF#291/<<<290@0$P$p$$$$$$$ ]>32#4&#"#4&#"#3>32)Erurw?yz|v\`gb|d{6@  N  F21/<90`]#4&#"#3>32d||Bu\`edqu{ J@  QE10@#?{{   {  {]"32654&'2#"s98V{>@ GF2210@ `]%#3>32#"&4&#"326s:{{8 daaqVZ{ >@   GE2210@ `]32654&#"#"3253#/s:||:/daDDadJ{0@    F21/90P].#"#3>32JI,:.˾`fco{'@<  S  SB %( R"E(9999190KSX99Y"']@m   . , , , ; ; ; ; $( ( *//*(() )!$'      '/)?)_))))))]]q.#"#"&'532654&/.54632NZb?ĥZlfae@f?((TT@I!*##55YQKP%$78@  F<<2991/<2990]!!;#"&5#53w{KsբN`>X{;@    NF921/290o]332653#5#"&||Cua{fc=`@'BK TX@8YKTKT[X8Y91/290KSXY"@Hj{  &&)) 55::0FFIIFH@VVYYPffiigh`ut{{uz>]]3 3#=^^\`TV5` @IU U U U   B     K TKT[KT[KT[K T[X@8YK TK T[KT[X8Y91/<2290KSXY"@" 5 IIF @ [[U P nnf yy          %%#'!%""%' $ ! # 9669 0FHF@B@@@D D D @@VVVPQRRPS T U cdejejjjn a g ouuy}x}zzxy  { v } @/   y]]333# #V`jjj;y` C@F      B   K TKT[KT[KT[X@8YKTX8Y91/<290KSXY"@   & =1 UWX f vzvt        )&% * :9746 9 0 IFE J @ YVYYWVYVV Y P o x  /]] # # 3 dkr))`HJq=V`@C        B     K TKT[X @8YKTX 8Y9129990KSX2Y"@     # 5 I O N Z Z j        '$$  )( % $ $ ' ** 755008 6 6 8 990A@@@@@@@@B E G II@TQQUPPVUVW W U U YYPffh ii`{xx   e]]+5326?3 3N|lLT3!;^^hzHTNlX` @B K TK T[X8YKTX@8Y2991/0KSXY"@B&GI  + 690 @@E@@CWY_ ``f``b ]]!!!5!qjL}e`ۓ%$w@4 %   !  % $  C %K TX@8Y<<29999999199999990&]#"&=4&+5326=46;#"3>l==k>DV[noZVtsݓXX10#$@6%   #%#C %K TX8YKTX@8Y<2<9999999199999990&]326=467.=4&+532;#"+FUZooZUF?l>>l?VWstݔ1#@  1990#"'&'&'&#"5>32326ian ^Xbian ^V1OD;>MSOE<>L5` e@  <29910K TX @ 878YKTKT[KT[X  @878Y P ]#53#3b+e#!Q@+     "  "<<<221<9990%.'>7#&73JDFHAMf fIX⸹)**'# 32!b`@!    <<1/2<2990K TX@878Y66].#"!!!!53#535632NL=ty-=))׏/я^R#/@I -'! - -'!0 *$0* $ $(st*(s099999999919999999907'#"&''7.5467'7>324&#"326{r%$&(r;t=:x=q%%&&s7t@?s9q(&%%s>v:@t8s'%$|pprR@F  B     fe f e<2299991/2<2<290KSXY"K TX@878Y@(' ' ')((79  ]]!#!5!5'!5!3 3!!!c`Tþ{yT9{3{JD{3@ <210##  \= >@54&.#"#"&'532654/.5467.54632{?>?>S8alӃ\]>9̭IXW:fqր][;;ȦI.Z.L-[.K''PGZsweZ54m@''TLf{xf[1,pEF)@dd1<20K TK T[X@878YK TK T[KT[KT[X@878YKTKT[X@878Y@````pppp]3#%3#^y/IC@&=>:A$104G$ 7aD=0^* D^ J21/02#"$'&5476$"3267>54&'..#"3267#"&54632mmllmmmmllmm^^``^^⃄^]]^\^BB@zBCFInmmmmnnmmmmng^^^傁^^__^]⃅]^^! "s;)_@3(%%  * "(kl"k *22999199990!!#5#"&546;54&#"5>32"326=P,]uu>DIE~bRhP{@p?Dq[[""CO@Mr%# @I    B   o o n<2991<2990KSXY" 5 5%-+#-+#RR^@ 10!#!^d10!!d/8L`@6EBC?2H09JC 9 $HE301B54&'.'2#"$'&5476$#32654&'2#'.+#^^``^^⃄^]]^\^ㄘmmllmmmmllmm}{{nWXfi`C.;I6Bf^^^傁^^__^]⃅]^^gnmmmmnnmmmmnb>KL?gwyVpMI`3Db+/10K TKT[X@878Y!!Vu=  @  Z[Z10"32654&'2#"&546PnnPPnoO@v+..ooPOmmOOp1.-rB .@     <2<21/<<0!!#!5!!!-Ө-}}^J@$}}B ~9190KSX2Y"!!56754&#"5>32 "?XhU4zHM98rn81^BQ##{l0b(H@'    #)~&~ )999190#"&'532654&+532654&#"5>32 \e9}F4wCmxolV^^ad_(fQI7Z`mR|yOFJLl?<:=svcE`sRf1@ D10K TKT[X@878Y3#fV` M@%  !   NF!2912<990"`""]3326533267#"&'#"&'#% )I#ER2bf*V H<9 NPOONN;9 %@]] 91290!###.54$yfNݸHF103#F#u@  ' 1/90!#"&'532654&'T76xv.W+"J/;<+->i0Y[ 0.W= ,@   |]|| 12035733! c)t'+n`d.@  klk 9910!!2#"&546"32654&PXγгi~hi}|P{ݿܾsH# @I  B   o op<<991<2990KSXY"5 %5 +-+-#^R^  ^R^  &{' Pd '5?&{'td '5b&u' Pd '5n` $@/  !# #%" " "!& %999919990KTKT[KT[X%%%@878Y@ ttttv]33267#"&546?>7>5#537ZZ:3mN`^gIYX0&ϜeWX5^1YnFC98ŸLVV/5<6hk&$%uhk&$#uhm&$&u  +@ ]1h^&$$u #+@ @O# /#]1hN&$"u  +@ 0?  ]1hm !@T   !!  ! !!!B     !  VV!"2299999991/<9990KSXY" #]@  s P#f iu {yyv v!# ]]4&#"326!.54632#!#TY?@WX??Y!X=>sr?<҈_Z?YWA?XXN)sIsrFv)H@9  B     <291/<0KSXY"]@gww  ]!!!!!!#!59=qժF՞su'&&z-k&(%uk&(#um&(&u@@ ]1N&("u @@ @]1;k&,%/uk&,#/u`m&,&/u +1XN&,"/u +1  g@    2  y<291/220@(   ]]! )#53!!3 !iP`P5~.,3^&1$u"+@ 0?""]1sk&2%'usk&2#'usm&2&'u+@]1s^&2$'u!0 +@ 0!?0 !/0!0]1sN&2"'u +@ @O]1? @M    B   <291<290KSXY"  ' 7 7w55v8vL57y5yy5f +@< +,  )&  *&& &,+,* # )#3,99999999199999990@*WZWU!je!{vu! FYVjddj(|svz( ]] 324&'.#"&5!27!"&''3>_'y=_''NOy;WfNPƀ[gX@CHp@CpDfbMKYg[KKX)k&8%u)k&8#u)m&8&u +@ / ]1)N&8"u +@P_@O /]1k&<#su =@   ? 2291/0@ ?_]332+#32654&#'ђ/@0-'!  **.  !' $'$-F099991/990@@'(     ! "&  : :!MM I!I"jj  ]]4632#"&'532654&/.5467.#"#:A9`@IPAtx;e\`Wqqs`/Q*%jd_[?T>7;[gp{-f&DCR @?&/&&]1{-f&DvR @?&/&&]1{-f&DR (,+1{-7&DR.< +@ ./<.<]1{-&DjR -( +@(o(P-_(@-O(0-?(-( ]1{-&DR%@&,,& 2882 ++1@ ?5?/5/]0{o{3>@C'-%= 4%:.-*1 %?47&%7& =&-7"E?<9999912<<29990@0+0,0-0.0/00@+@,@-@.@/@0P+P,P-P.P/P0+0@@@@@@@@@??? ??0,0-0.0/@,@-@.@/P,P-P.P/ooo oo`,`-`.`/p,p-p.p/,-./]q].#">32!3267#"&'#"&5463!54&#"5>32"326=DJԄ ̷hddjMI؏`TeZ߬o0Z^Z55*,ywxx..''`f{bsٴ)qu{&Fzqf&HCqf&Hvqf&H"+1q&Hj@@ ]1f'Cof'v\f& +1F&j +1qu('@^%{&%#${##{#({'(#&'('%$%(('"#" ! B('&%"! ## #)&' ! (%#" QE)999999919990KSXY"?*]@v%+("/#/$)%-&-'*(6%F%X X!` `!f"u u!u"%#%$&&&''(6$6%F$E%Z Z!b b!z{     {zzv v!x"**']].#"32654&#"432''%'3%F2X)6 ~r4*!M!ü޼z&77kc\̑oabd7&Qquf&RCsquf&Rvsquf&Rs+1qu7&Rs .+@ /. .]1qu&Rjs +@ @O0?]1o )@ r <<103#3#!!oAH +@<+,&  )&  *&& &,+,* # #Q)E,22999999199999990@p(?-YVUV jf!{    { z{ {!"#$%{&%--&YVUZ(ifej(ztvz($$]] 32654&'.#".5327#"&'')gA\*g>}66]C_56`?`!*(Ou))Hn.Mw834OMx43NXf&XC{Xf&Xv{Xf&X{ +1X&Xj{ +@ @O0?]1=Vf&\v^V>@ GF2210@ `]%#3>32#"&4&#"326s:{{8daa=V&\j^+@ 0? /]1h1'q;$ +@@O]1{-&qJD+@o]1h'J$+1@oo]0{-&OD"+1u&${u{&Ds'k&&#-uqf&Fvs'm'&Lu& <=/1qf&Fs'P'*Lu&q'Fs'm&&'-u@]1qf&Fm''u'q&G! @_?]1 q$J@$ "    GE%<<1/<20`&&&]!5!533##5#"3232654&#"F:||ǧN}}daDDa3&(q=q'qH@p]1m')u(@@]1qH'H@p]1P'*u(q'Hu&(qu{&Hxg&('o@@ ]1qa&H!+@!]1sm'&\u* <=/1qVZf&hJ  <=/1sm&*)uqVZH&JsP'*\u*@?]0qVZ'jJs'^*qVZ4' J;m'&u+ +@ / ]1dm'&uK*+1KQX88Y@ @@]:@    8 22221/<2222203!533##!##53!5qʨ"ʨ9Qx>@!   N  2221/<2290#4&#"##5353!!>32||}}`Bu\zzedx^'$.u, +1g7'+1Y1'q.;,+1H'q+1gm').u,+1VH'+1u%'d,u 'JLP&,*/u<<1??]0y{,@ F91/0@4D@P`p]3#\`{f'-\,@1V'M8L@F1f_m'&.u-+1V\f'+1j' .' N` @(B F 291/<290KSXY" ]@_ ')+Vfgsw    ('(++@ h` ]q]33 ##%kǹ`!jl'#nv/Jl'#ZvO<1KQX@8Y@O]0j' /' O@@]1j'!/'!9O @]1j'y1w/'ysOK QKSKQZ[X@8Y1u ?@   : y<<991/900P]3%!!'79Pw^Mo;jnH ^@  z z <<991/90KTX @ 878Y@ @ P ` sz p ]37#'7Ǹ}Lɸ{JZjXj3l'#v1@O]1dm&vBQ @?O]13' 1d{' Q3_&1'g +@ /  ]1df&Q +@]1'QU~V;@  AKTX8Y21@ /0!"#367632+53265PͳNijQRW1fOCCoa`ZVd{;@  NF 21/90`!!]+5327654&#"#367632dRQi&&||BYZuccH``01`e22wxs1'q';2 +@]1qu&qsR+1sm')'u2+@]1quH&sR#+1sk'+'u2quf'Rs ;@   299991/220!!!!! !# !39OAg@AժF|pm|q{'3@1 . ("%4"1 K1 Q+E499912<2290@%?5_5p55555????? ooooo ]q].#"!3267#"&'#"32>32%"32654& H ̷jbdjQGьBN5Z44*,nmnm98olkp݇Tl'#v5m&vBUT' 5J{' UT_&5'}g@_]0Zf&U +@]1l'#v6om&vBVm'&u6  ))Ic:1of&%V  ))Ic:1u&6zou{&Vzm&6'u + ""Ic:1of&V' + ""Ic:1u&zP77u&zW_&7'sg +1@_]07&W!7p@]1F@   @ @ <<1/2<20@@p ]!!!!#!5!!  ժA@7C@  F<<2<<2991/<<<20]!!!!;#"'&=#535#53w{{%&sQQ''PO>)^'$u8 '+@ ]1X7'X&+1)1'q;8 +@ / ]1X'qX+1)m')u8+@]1XH'X+1)o&8iX&X| @@@!]1)k'+u8^f'Xu)&8u{&X'Dt'&|:+1V5m'EZ+1t'&r|< +1=Vm&^\+1N&<"su +1\l'#v=Xm&vB]\P'*u=X']@ O _ ]1\m&='uXf&] +@ ]1/#@  L<1/0!##53546;#"c'&яN()g ,D@% ")%,$'".EG* ,(%#'F-<2221/<204'&#"327667632#"'&'##5353!!STTSSTTS:YX{{XY:E/tssttsstRd0110d}}P)C@#   . *29991/90"]!2654&#!2654&#%!2#!"#546D+ |v݇f>orqp ˘0_i1F&8@# (EGF'221/067632#"'&'#!%4'&#"3276s:YX{{XY:NkrSTTSSTTSd0110dtssttsst 3@  . /21@  / 9/04'&#!!276!2#!#ONDNO|N8DCDCD>@  G /221@  /ij9/0>32#"&'##34&#"326s:{{:"QrdaadDs'0@  0 <10>3 !"&'53 !"shSzjffbGGaaHH_^9'(9^_sZd$D@"! %  %  0%210&&].# !267# !2676;#"'ffjzS` SfM?nb_^^_HHgh$bzq"N@$ ## HE#210@ $$$$$].#"3267#"!2546;#"NPƳPNM]-GFE0iL~++++$$>: a .@   2 99991/0`]3 !%! )"#5465BhPav/w.,~0_i1F.@  .21@   /0)!"!!"$54$3!!@DNN|#+qZ?@G E221/0` ]5!#5#"3232654&#" M:||:ndaDDadqVtc'T@ )E Q E(]99@   (99@%S 910%!"'53254%&'&326&#">kGxfu'~@3cnBOFFu\0%p9 *E +@    21@ /0!5!!5!!5E>9+uD@& 39190!!"56$3 ! 7327upo^   2`_FHg[{(@@$ )) #)* &)190.54$32.#";#"3267# $546؃ YsrSǾmTj^У%!| &${spw21%%ݐf#A@  2991990 ]!!!!+53265ZpPM?nժHVe@#   LK TX@8YKTX8Y<<9912299990@P]#"!!+53265#535463cM/ѮcMPhc뻫Ph*Nsd&I@43! F'1@'$$'990%!5!# !246;#".# !26uu^[DM?npkSUmnꪖ_`%Rv%@ 'P $&]ĵ 91@ %$&222990@ #%$$<<$#$%#@$"! #9927654'&'3#"'&5476736,3,,3,6hC.KddK.Ch B9Iy\\yI9B z^ȮwBAWWABw1G*O@, *&NF+291@ '&&  #/<<9990%27654'&'5+"&54&#"#3>323LTWJ>ymoF||BuLibep_!edg .@  KTX@8Y991/9903;#"&n?M-– R E@   >f3@)B 6  999991/299990KSXY" ]@068HGif FIWXeiy]]!3!+53265jG?n+Vd{Ls 1@ 3221@   0! ! "!&32sy:;x Vb[[z=g&24v'X Rs3@ !  <1/0!4&#! !2!2"327&nzy;pa'Xܯ–bb-LgFqVY{!:@ """# E"9104'&##"3232"327&&&idRصRQ@TVt1098``:6:@   ? 291/0@ ?_]32654&#%!2+#"#5468ʄv/ϒ0_i1FV$O@$#% %G  F%22991990@ `&&&&]%#46;#">32#"&4&#"326siL:{{8(adaaTV@  ?  2299991@  /9990@ @u|]#.+#33 326&#A{>ٿJx~hb؍Oђ r!d@ -" "99991@B!  "90KSX@ Y6 327# '&546?6764'& {璑z<;YZL-|숋_ppٶ+23@@md{'@  !! RE(99991@ '$$(90@S !S BKSX99Y"]@/)?)_))))))]@% '$&((*//*( ( ))$]@.,,,;;;; q>323267#"&546?>54&#"Lf@eaflZ?bZN?$%PKQY55##*!I@TT((8V6@   O 221@   <20;#"&5# 54!23%&'&#"3wMc/R5!n|wj=hP`@o,0A37V?@ F<<291@/<2990!!;+53276="&5#53w{KsF0j&&էN01`>X@ @  991/2990K TX@878Y@@p ]!!##"#546;^vժ+Zi1F7I@  F<<2291@  /<299990]!!;#"&5#53546;#"w{KsբcMcN`NQfT@ @@ 120K TX@878Y@@p ]!!;#"&!n?Nժ=–_&84i' XN:@!3   1@   <2220!! 47!5!3254'5!X ƱXw>*a"Lav-@   /<91@ 0%254'&'5!'&'&33cAnMagn"ʦmWDtz–d@  @ @99/1@  /9990@        BKSXY""#3 632#54&9%NZUUIG9\[ny6P=V{j@  K TKT[X @8YKTX 8Y9991@:        B    9990KSX2Y"@      '$$  )( % $ $ ' 755008 6 6 8 A@@@@@@@@B E G TQQUPPVUVW W U U ffh { F]@%     # 5 I O N Z Z j ]+5326?3 67632#54&#"N|lLT3!;^0XQ99) hzHTN43`rr:T*\@5    B  B K TK T[X 8Y9991/<20KSX<<<323#L:s_%'ST_ijxzX"Jh0@umHLIwKK!!C12\RI`1]5@ F1@  0 4&#!!!%$ $5& )sQ;-%,%hV)$yhL?`3@  F1@ 203 4&#!!!32!"'hi;-ԧc%,&cV)$yJX$!"'&'5327674'&+#5333!plnUQQLITNPc9:V>}ws}#(rAbLrV{@@  F221@ B 0KSXY#36763254'&#"s4QҸMNr98xܭz BR1pqWBAV&@ F10@ @P`p]3#V''V:@    <<2<<219/<2<203!!!!#!5!5!5!s____,Ԫ m'?' f'@'qf'@Gf$'-/V'Me/V'MvOf'-_1V'M>1V'MeQhm&$'u<1{-f&DZ +'+1`m&,'/u  Ic:1^f&  Ic:1sm&2''uquf&Rv <1)m&8'u<1Xf&Xv  Ic:1)3&U08X1'q{;)Z&8X6X"&XX)Z&8]0X"&X])`&8Y0X"&XYq{h3&$U{-1&qR;h3&$W{-&DWH4'q>{o'qs%T@!$"43 &<1@"#%&99ܰ KTX"@8Y<203## !2.# !2675#535!5yyuu^opkC XSUmnHF_`%'XqV{ 4X@"2% G,E5221@ #% ) 2/3 &)/99<20`666]4&#"3263#!"&'532767!5!6=#"3253:aQQRZ9||9=nXF]@,*_EG^[cb::bcsm&*'Ju!<@!T!$!]1qVZc&JJjm''u.m&N'u* +1KQX88Y@ @@]su'42quu{'Rsu1'q';quu&qsm''uyXL/f&TVdf'%  Ic:1 '=' ']'q']Gsl'#v*qVZc&Jv-5@8221@ /203!327653! '&5!#>=B>d`gd"dPNOKZ߀xxv 9V@@  221@ B 0KSXY%#3676324'&#"8WST=<HW5xz7 GF3k'%u1dd&QChs&#\}{s&#}Hl'#\v{oc&vefl'#vHc&vhp&$,z{-d'Dh6&$(>{-H'eDp&(,zqc'H6&((>qH'Hsp&,,Yzc'fw6&,(>>UH'$sp&2,Azqud'Rs6&2(>quH'RTp&5,yzJc'%UT6&5(>^H'-U)p&8,zXd'X)6&8(>XH'X'v6o{',V'S77'WRs. 56$>54&#"57>54.#"5632?4o1\}p_s54&#"57>54.#"5$32Fp>!BlJc(v];?"AW?-1CA#E ptgDZX%KlaF='.`[b[3XpVU 2#PQ̝qpD(4%3254'"632!"'#67&5#"'&76323 76'& %44nI5"C0:XY|ˀ|YX:ST$TTTTT- H:E<$d0110d^jtssttssq% ;W@$3=E (B!8;7B/E<̲ ;]91@$3< ;<,<990" 7654&327654'&'52 '&54767&'&5476!˸jkkjpkk_;̨_`Lm䖋_``aCUtMMMMMN'|OEH-AA+Mdha "ccttttُcc"FYXSJqq 4C@6E B42()+&BE5221@4)".559920" 7654'& '&5467&'&5473327654'qSRRS SSSR:4HRQ;4?+IHIJ,MMMMMNMMJ@b@Y "ccttttُ"#VKYIAAAAAtw>\V@ B  K TK T[X 8Y991@ B  /0KSX@ Y@@ )&8HGH  /59?GJOUYfiowx]]+53276=!5!5!!Hri&&gPP%01oXV`@   K TK T[X 8YKTX @8YĴ@`]99Դ@`]1@ B  /0KSX@ Y@2&GI + 690EIWY_fh]]+53276=!5!5!!۞Hri&&5ejLP%01%hP'*u${-'JDu&(zqu{&Hz{s3&2Ubqu1&qs;s3&2\iqu&R\sP'*'u2qu'sRs3&2Wjqu1&qs;1'qr;<=V&q^\p\%3254'"632!"'#67&73%44nI5"C1- H:EVy` 8@   OF 991990@  @ P ` p ]3+53265F1iL`aq #/A@1E%G +G!E0<<<<1@( . /22220 6& 23632#"'#5#"'&76'&  7/ST$Trrrrˀ]STTSST$Tjtss ^ŨŢtsstjtssqV{ %/D@1E$G+G'E0<<<<1@ *.! 02<220'&  7"'##"'&763253632 6& STTSST$TrrˀrrST$TdtsstjtssRŢŪjtss|3 #!#'#7'7 3!Jafp|҈2F;R/o]jY'FF8O ",'&76!27&'!2767# '#&# rfuSv=:efc.1 tsfjwv9tFXh$xYv+!f //_H$$\/ح ]"+'7&576!27&'32767#"'&#"i`UUQ.-Y_vcPNONMRS]7GGcc^N lOU ^q+$Vqrg j ;@   : <<1/<20@ 0P]33#!!#53ʿ_w1##'!5!7 !4" gZ8f,i> XRBY bo{=4'&/&'&54632.#"3#"'&/&'&'&'53276 23@LLfLNZDE11?PS{W*L'TrGY$alfccaFF'K((%$JK((**T@%$!,KL[@~$=&[#5-,X3`!;#"'&/&+=!qjN\1*LlTrGY=Z^e`1~$=&[? %P6@ 9991@  /0##32654&+"56;2'񍚚EOZ*,FP{7@   991@  /032654'&#"5632##/dLUIVVN}AH+Fnt  (\@ #  . &%)<229991@(% #/99/<20*]!!!2654&#!2654&#%!2#!#53[D+ |迿ɐʇf>orqp ˘p _@ 8AKTX8Y<2<21@   29/<<2299990]3!33#! 5#53!3265˥ߦ®j*$}h1B33#!!!!#7#!#!AX .AA<VF㪾FqB&-1&'&'!3267#"'#&'&3273&#"#So+Jajbck{cPm!)81G\9/Zo Z 6Z44*,!  C "2JcfRY@    9 KTX@8Y<2991<2990@ 0@P`]#+53265#5333RM?nʿwHVS@$   OF<<22991<2990@ @P`p]33#+53265#533#F1iL`(aؤsf$C@$  %" %  %2299199053;#"&5# !232#"nEMMT–\\xEEqV@{$H@"%"%G E%229910`&&&]#"&=#"3253;32654&#"@F:||:Li1戮VּdaDDada= T @  ?  !<299991@!  B  /<229990KSX9Y"@"]@Bz%%%&'&&& "66FFhuuw]]#.+##53! 32654&#A{>ٿJxʿ~hbw؍OJ{=@ F<<<1@  /<20P]###533>32.#":.I,h<ĤfcΡ3!733!#!53!ٗ ٗwјv9 V`+5326?!533!33!+N|lLT3!øLùmhzHT33`{ %@ '  F&22991@ & # &  9990@1??? ?!OOO O!___ _!ooo o! ! !]@%???? ?!?"OOOO O!O"____ _!_"]2654&#"3>32#!3267#"&߬o?`TeZ+f{bsٴ)Lfa..''qZ{8@G E221/0`]53#5#"3232654&#":||ǧdaDDa{ 8@  G F221/0`]4&#"326>32#"&'#3姒:||:/Rdaad` $C@  !G! F%22991/0`&&&]4&#"326>32#"&'#46;#"姒:{{:Z[/Rdaad~Ӝ}}{0@  EH <10>32!"&'532654&#"M]*ULNPƳPN3$$##++++qs{'/O@( ,,H"E02991@.*%00@ 11111].#"67632#"'#47&'&!23254#"NPc'>IjJ?_SPI 9/-U:Me5++rQ,3H=Y}/)9DhQ#3 :#:9KqV@$K@$%"%OG E%221990`]#"&=#"323;32654&#"@F:||:Li1戮VּdaDDad^ؙa=q$=@" %%  GE%2210`]546;#"#5#"3232654&#"iL:||ǧadaDDaq{"r@ KE#91@  #90@)?$p$$$$?????,//,ooooo ]q]47632!"&'532767!7&'&#"qkcbdcjfg ]\RS^,*4cdWWZZq{A@$  KE91905!.#"5>3 #"73267qN ͷjbck 9Z44*,#ė|{ 4w@6.('4 KE5<Ķ&  91@/.'""5 5@  &"90@ 4 &'<<<<<%6'6'32#"'&'&'&5>3 73;#"'&5Nf  R`\Lladbck $˸&&i+@WR֊>8E#Z`vg'#d4*,#)u10`Z|J|*|>i@@603273;#"'&5|PUTZGUU]UTNHtCDFEwGQPabLq_&&i+@WR@\l%88ZX83,-F@.. NBj10`ZȦFq|/;@ 1 &,E01@00)0#90"327654'&+5327654'&'2# 76`cchҗUTNHtCDFEhqr<V`K@   OF<<22991<2990@ @P`p]33#+53265#53F1iL`(aؤqV 0U@)  &#-* *-+& G E122991/990`222]4&#"326!"&'5326=#"32546;#"aQQR9||9iL=,*[cb::bcaqVZ` #C@ # GE$21/990`%%%]!"326!"&'5326=#"43!aQQR9|=ͻ,*[cb:*qO{8@4 E1990%#5!#"!2.#"326Ae{-h]_cƳO|$$>:77>>`Rd`#y@ %  $ĵ 91@  $222  990<<<<< 3#"&54767327654'&'bB_j&;;&j_BC(::(xܱSccS$-EIdccdIE-`d`#y@ %  $ĵ 91@  $222  990<<<<< 3#"&54767327654'&'b)rG,EE,Gr)C'88'bLx>>xLb-!@2FF2@!-VX`9@     NF21290`]332653##"&||Cua{VfcdC@!   N  F2991/<9990`]#4&#"#46;#">32d||iMBu\~aedVd!J@%  " NF"2991/9990`#]+53265#"#46;#"632diLiMHa=~a >@    F<<<2221/<20@ @P`p]33###533#¸`<Ĥn`Nt` '@   221@   /2205!#3!53t褤K#<@ % V V$<<1@#! !//2<903327673#"'#&'&#"#67632= &}33[ &}33[ %$RIJ %$RIJMT5@  <2<1@ /9/<2033##4'# 7632&#"3=5*7M\TK9V_ (@  F 1@   990;#"&5y=x1F|t(L6$@#&#" F%<̲#91@B""  " /9/ 990@$#@  **8;ILT[q ]@$$%$$5$7E$FT$\ ]@    ]2!"'&'5327654'&+5!#3!CicUQ^cdjTmcd\[je8+lh%12KKKJ3Lb&^@PP F'<91@  #''<<<290@0(P(p((((((( ]%#"&5332765332653#5#"'&Cb`ruSSrw=ZXyzVUy=<b`^zbze32>>Vb&a@PP F'<91@  #''<<<290@0(P(p((((((( ]%#"&5332765332653##"'&Cb`ruSSrw=ZXyzVUy=<b`^zbzZe32>>V{0c@PP)%'F1291@ %*!*-(&/<<290@02P2p2222222 ]>32+5327654&#"#4'&#"#3>32)E__RQi&&ru99wSS?yzUV|v{zH``01NM_``gb>>Vk{Q@N O F2991@ /9@   990`]#4&#"+532653>32k||F1iLBu\satedVJ{;@ N  F21@   /  90&54&#"#3>32;#"R||Bu&&i1F``edH10d` y@BNF 991/<2990KSXY" ]@068HGif FIWXeiy ]]!3!##`ylqu{ ,@  Q E2210"!.265!2#"qt蔔98q$`I@  E2ij 991@   /<<@ 9/0!!!!! '&76!#";:E*%xxxx%`ݛlklm>|$2@ &E E%1@ #%<202765 26= "&'"&H`k&InI&k`B"F:.aע ģ0[1[0T\l6puypVj`/@   /2991@  /90%!"/32653#r%832JI,:.˾ fcVJ{:@  F2190P].#";#"&53>32JI,Li:.˾atfc~{%@ 21@  /29903!5346;#"iLAat~{%@ 1@  /29903!534&+532ʴLiAa`@4  B      F299991/<9990KSX9Y"@]@Bz%%%&'&&& 66FFhuuw]]#.+#!232654&#0s2âJ{Qpwu t]:'`iVNM``E@  F299991@  /29990332673#!32654&#Q{Jî2s0jp|Ɓuw`':]t i`MNVoV{0@C  S('  S'('B1 '(!.1' ($R$+E19999190KSX99Y"0].#"#"/;#"&=32654&/.54632NZb?ĥdXLie@f?((TT@I!* ajYQKP%$V4@ O F<22991@  99046;#"+5326cMF1iK»Ph)aV O@ !O F!<<229921@! ! !99<20546;#"3#+53265#53#5cMF1iK`NPh(aؤi7V5e"O 1@ 04&+532;#"&McKi1F(hPaV2@   O 221@  /<20!3## 54!346;#"#"3276w5RcMów|n!o@`Ph3A07^3@   /<<2991@  /<2990]!5!4&+5323#{Ksբ>`N7V=@   F<<2991<2990]!!;#"&5#53w{Liൣa>`C@     NF2221/222220` ]3!33##5#"&=#5!326:CuȮ||h=$#^lfk`8@   91/20@ 3 3#f%.]`8XV`@"B  OK TK T[X8YKTX@8Y2991/0KSXY"@B&GI + 690@@E@@CWY_``f``b]]!!;#"&=!5!qjLLi/F7e`ۧa%X`!@  "KTK T[X8YKTX@8Y299<21@  /<0@ BKSXY"@:&GI #+ #690#@@ECWY_#``fb###]]!367632+#47!5!3254qjL"TA`:&>R~ie8FX`ۢG7W9W`/=3<;4%6]XL/` @ "!̲91@B!  !9/ 990@ @  **8;ILT[q ]@  %$ 5 7E FT \ ]@    ]2!"'&'5327654'&+5!5!`q|/=@1 %,%E01@0 0"0( 90";#"327654'&% !"$5467&'&5476EwEFDCtHNTUhcc`a|p<:!a>>`V.9@ F<<991@   /<203#33## 54!3#"32767Ku_+xG`͋BA0 L` ## 33R9L T#`@ F1/03!!`3qV $C@  #%% "GE%2210@ `&&&&]32654&#"#"32546;#"#/s:||:iM/daDDadaX$L@ & %<<ij#1@  $! /<2KPXY032765&'&#"56763 3###53T?V:9cPONNLQQUmlprLbAr+#}swԤX$M@ &"#E%<<ij "#1@ $!# ##/<2KPXY0535&'&5476!2&'&#";3##plnUQQLNONPc9:V>ws}#+rAbLrq &) 76'& %3!!!+5#"'&7632/ST$TTTTT iL:XY|ˀ|YXjtssttssH^Lۓd0110MqL4@#5#"'&76323!2!"'&'5327654'&+5 76'& Z:XY|ˀ|YX:jejbVQ^cdjTmcd\]:ST$TTTTT3d0110d^L$8*mh%12KKKJjtssttssq 3: 76'& %%!332!##47!#5#"'&763233254#/ST$TTTTTghL<):XY|ˀ|YX:FXjtssttss_ 3<;4d0110d^6[7@7!!3!27654&/.54632.#")"'&5#53w{%&s@FF^@fLNZb?ƣ|LQQ''-,4IM*$((TT@I!,e>PO`>7V&/!05476;#"+53276=#"'&5#53!3wxWQîc&'QRF1i&&QQ3%&sN[V((h)``01PO`>''7p-9D!6!2&'&#"63 #"'47!"'&5#533276'&#"&57!3w{UQQLNONPcccO+eKTIQQ;BS_r(ր%&sz#+qrfr v)2LOAPO`> 'KV ''/Vo5+5327654&#"#!##535476;#"!;67632oRQi&&||ӹWWc'&-BYZuccH``01/яNUV((hce22wxA'3!27654&/.54632.#")FF^@fLNZb?ƣ|LO-,4IM*$((TT@I!,e> @   F<2991@ B /0KSX@  Y@B &GI   + 09 @@@@@C EWY `````b f]]3!!!+iLLۓ6 333# #333# #6ttttU=63@    <2<21@  220!#!#!#!#6kkUXrXJ3@ NF 21@ 0%#"&54&+53232653#׃Li1FęaBþyVv!:@ #NF "21@" ""0%#"&54&+53232653;#"&'׃Li1FPh2FęaBþyfu0@ 32tNN^luu)qJy}wYYk\g88u:KSX@ 32tNN^lugrB0)qJy}wYYk\xkW6Vr88 #@<<1@03+5327653#zt43r,Bttx66XVru@ 1@ /0.#"#3>32.biuu$uT  qksa97H <1 /032653#5#"&'H.bitt$uT  qkJa97Hu' <1@  /<032653;#"&=#"&'H.bit0B,rg$uT  qkJ V6Xlx a97 !+33276?3327654'&+CFCDtk=%%(f{n!!"}K'))'K}N;[--s?5/.6 333# #6tt&+53276?331/.N]D0 {{bp"#WK/itftf&t  @ 10#5Rڬ@u1 ܴ? O ]ܶ ]<1ܲ]90526544u@XX@sPOOP{X@?X{POPPu1 @    ]<1 Բ]90"'&4763"3sPOOPs@XX@PPOP{X?@Xu+@ 91@   032765&'&#"567632#'y7$#?q22110335WDDFk[@*7K$@ ` XFh_@Cu-@ 91@   0#&'&547632&'&#"3kGDEW53301212q>$%6y[AmC@_hFX ` @$K7*@ 2% % g 25-5g'|?f=u912]90K TKT[X@878Y3# #fg|?fLu91<Բ]90K TKT[X@878Y@ 5:5:3]]33|g?f7@ u91290K TKT[X@878Y3#'#f?f7@ u91<90K TKT[X@878Y373x^@1@/0#^+b+qsRf3#ff #ofv^@1@/0%#^++Tq^#onvsR3#lo#E@ j,5!##–,dU 533##5#5Dud&u!5!&>ߖ)9H W@ VV1<0K TX@878YKTKT[KT[X@878Y332673#"&v aWV` v HKKJLDfN@ d10K TK T[X@878Y KTKT[X@878Y3#  @ V xV104&#"3267#"&54632X@AWWA@Xzssss?XW@AWX@sssLu @   '1/90!33267#"&546w-+76 >&Dzs5=X.. W]0iJ7c@$   VwVv99991<<99990K TK T[X@878Y'.#"#>3232673#"&9! &$}f[&@%9! &$}f[&@Z7IR!7IRfB@991<20K TKT[X@878Y3#3#߉fx%3;#"'&5&&i+@WRd10`ZȢf '#7'373\\]]\aa``u # 5473733254/MMz /1/03#zttu/2&'&#"#"'&'532654'&/&'&547632j1549W++](}24NM9>=D@?>=RX o(l00GF@99 a /$*+MW33 k2-*)*IX01 u! #'#37 ͉H+uX@ 1/0!!5!AGЈX'@??//21/]0!!5!3A4X@ 21/0!!5!3AhhX'@pp0021/]0!!5!3A4X@ 1/0%3!5?p+v'qqm 93vJ!_@ Vw V v"99991@   "<<99990K TX@878Y'&'&#"#67632327673#"&9 &}33[&@%9 &}33[&@7 %$RIJ!7 %$RIJ{f6@ D910K TKT[X@878Y # mXfvq{Pf6@ D910K TKT[X@878Y3#fs{?f<@u991290K TKT[X@878Y3#'#?fsH7b/q|  !)1H{Z%@ 910@4D]3#^{)I@ dd 91<20@#4D`````````ppppp]3#%3#^y)7{"@ V@ V /1@@ /0632#546?654&#"7pihX,#w3-.>GZdH3UC=A   (6%""($4f{Cf<@u991<90K TKT[X@878Y373NxsD/1/0#DD'4]fB@991<20K TKT[X@878Y#!#͇fxx)1V'B)1H VV/1 /<0#.#"#> v aWV` v ")KKJLD( @0#3Ӥ?#55#53pp{53#7"op{y3#@uUCqPUv$<#5353#ĠxxxF33##xx2xU?p!5!#Ik{1@V/K TK T[KT[X@8Y21@ /0532654&'3#"&=X.. W]0iw-+76 >&Dzs5V @  V21@ /0"&5463"3VZ||Z(55(}ZY|x5'(5 M3!5353D M#5!##걈ň$ #53533##Ġxxxx 5! zV '+53276=0RQi&&``01wV %3;#"'&5w&&iQR10``Zs3#'SjC( @V xV1@ /04&#"3267#"&54632[6'(55('6y|ZZ||ZZ|&65'(56&Z}}ZY||jT @03#Ӥ#uzLuD/1/0#D`tP#5!#fJc9X#"4533273273" v aWV` v "6KKJL9HS/TB  #"'&'.#"5>32326SKOZq Mg3OINS5dJ t]F ;73 !;?<6 7=xh!5xhh5!Ĥh'`_^NO'ygfFXY @  V21@ /02#52654&#Z||Z(55(B}ZY|x5'(5[3!53[J!!5#>J*>c9X632#&#"#&'"#72;tv gfv ifvtR+ '7'77}`}}`}}`}}`p}`}}`}}`}}` .54675>54'&'C!RI 7!RI 0PQn +0PQn : ' ! !fCqPfvH7FbV+I#5!#!Ֆ֖V,2!5!5!5!>>2xx3#3#@`tt!#!–*>,Jf'73327673#"'&'#7&'&#"#67632Bmk  &}33[& !Bnk  &}33[& g  $%RJI g $%RJI J!%'.#"#4632326=3#"&3#3#9 $(}gV$=09" (}gT";薖Җh! 2-ev 3)dw.CJ"$$c( 7!#'73!'3p~(͛3#557'2d͛~~x&'&4767@*,,*@rNPPNr*,@A++{OPPN`1'+!x050567654'&xrNPPNr@*,,*{NPPO{++A@,*.Do2>&"762"'"&46264&" 5O57O5>||=>||66O5555M75m?|}A@}|6M65O5p "pk "Ppk!!p kpT!!p ଔ* '#'&'&#"#67632327673#"'&O,$e5Fqp[?9ZO,$a9Gqp[?9J7  $0GJI "7  $,KJI pn #w(5!'3#7ws~~d͛q` !#!#!#Sb+e !#####b+tf@103AntVH@10%#AnH3y`V #"'&=3; #V!. {q{'yOF{'y#f-sRf1@ D10K TKT[X@878Y3#fFR&jl@_]@_q0hf''HFyuf't+f'-}f'z/f'5(f'n:f'h>6'.Nh$%j@ 1/03!!)ժh=@ B1/0KSX@Y !3f5:9+(\=;+s!2@"" "#3"10!!"3276'&' ! '&76>b܁܁:xżp[bb,j.h<@ B1/<0KSX@Y3#3#:9&+031b *@    <<1/0!!!!!!29iggqs2;3 F@B   <<1/220KSX@   Y%!!5 5!!>!8ߪp7<s'<@) !%(<<<<1@' %'/<<<<0367654'&'&'&76753#–bbʖbbWssWWssW=;;s.@ <<1/22<20!6'"'&336763#ּՂnʊnhg椌gHN&3@ &("3'1/<2220%!567654'&#"!5!&'&576! cccd?IH1/GGaʦa>”XN'"/u/ +1N'"qu: +1qf&Fnf&PJVdf'Lf&NF*&Zqy *@ ,%E+99@ ?/]q@ ) !/99@<<10@  ]@IIIJN LNIK ]@:9:88? <>]@ + +*))]@  ]@++]'&#"3273;#"'&'#"'&763 N,-=MKLyHc( #) Xn^T).^,ru7 nik%1)0T*XoW)&V!7@E F21@  90%#! !"3 5 4# yYo 0kEdZ&J:@ V`@@ 1@ /<20@ 993#&+532i^;,_1FLdVD~qu-T@(/E( Q!E. ]99@%%.99@S910&#"#"'&4767&5!232654'&'&fu5KxD7VUV[a~@Fu\0%p̥@$OF(Iqrs`g |2=@" 33'(#,34 '0E310&'&547632&'&#";#"32767#"'&546p<@ KQX@8Y1@ 20%#457654'&# !5!ʄOTJPE* :;f,KOxsPWKL,#%5,*3Y'iVd{1@  FN  F21/0@]#4&#"#367632d||BYZuccH`e22wxqu$!O@ """#E QE"2]21@?]0@ w##]!3276'&#"2#"'&76EVSI 6VQ@=񈉉d~uvn` @ F1@ /0;#"'&5c"$lYoRR`+.0`b` I@   F 21@ /<20@    <<33 ##Gb`/ZFB?= F@ 1@ /<0@  # #'&+5z~J/k`ue<2~V`wJ`B@1@ /20@ 99!367676'&'31!xdLjE.*{`T|p5dwY|rNįtkR&@@ (" %'1@ '#"'<90%#457654'&# %$47#5! $ڄOTJPE* :MKOxsPWKL,#%5,*,X$Rݿ qu{RJ`/@  1@ /220!#3267#"&5!##J117,#J%x\c`PH? XV{1@ EQ F]1067632#"&'#44&#"326=;{:+fZ#adqR{$6@ !& HE%1@% %0 !2.#"32#457654'&-ULNPƯPTJPE* >:##++LOxsPWKL,#%5,*q` 1@  QE]1@ 0"32654'&'!##"'&76sRVVOcm񈉉qnsȷzn휝dm`#@  1@ /20%;#"'&5!5!!$lYoRR\ W0`b*`+@ E F@?? ?]1@ /<0327676'&'31'"'&5R27ki;jF-*eb`+@EvfwZ{sxvpVh )=@+E(#E*<<1@ *'*<2<20"27654'&'2##"'&7673=A__UVF6˷džfB:VVMpˑRh]p[nmNssg.;Uda@    <<91@  <<90%KSX@   99  9 9Y#&+53;'$ܕ11FA3N11F~0)~pV`6@   <<1@  <2<<0&'&53367653#EkUJ|CUvܷ%aw~LB,BTxnc#n'`8@E  E1@  /<2<0 433233243! &aƏ˪ޏƛa!)R@O@+}&Nj.*&jZquf&}T*f&"Z'f&^YVj 3! # # wHV1M$ 'G@)E& F(2Բ?]1@ ("((Զ?]990267656#" '&76#327>&iPDyz]6;~oxҤ]Y:PWp=l޺lǧ_ը,嶖ꀰ-ўqu$ 7@ !EE <1@  04'&#" '&4632  1BSxyJ̃Я#/p~ZZ7Ai6deBWQ I@ "!9Ĵ?@]1@ /<99@ o]0#4''&"562%62#"FR**RMw(oUCHk&_*SKHv H# 0r{C @[)/Bf'ngPWQN'"ugpV'A@)   $E(<<<<1@ (  (<<<<02##"'&76327676'&#"DžǷdžǷqMTVMqqLWULc휙owgsugHgusgAm`E@ EE91@ <22205!#%$! 47)323764A,Ma")aM:GϤ*RѧOp[g9&'&47#"54654'&#"563277632327"'532! `7"7$>9[@[`7"7>9[&F]_I I5l|"O z:6hl0'[Ml |"Oz:6hlf$11sXD@!  ܶ0]9ܶ0]1@   <0#&'&76!   76';:{HpҳI椤qVu{ <@!E E ܲ0]9991@   <0"32654'&#&'&7632sVVUVVV9kjstntstu n}{R$.@ & #%1@ %"%0 32#457654'&# '&76)F`{[mzYTJPE* :xe+wTOxsPWKL,#%5,*eNqRQ` 4@ " E!IJ]1@ ! !0")!"32#457654'&g-[oPTJPE* >LOxsPWKL,#%5,*#)@VF'6  (<1@ ( $(0347632&'&#"!!#"'&'53276`1213$)),x:KAb933.1220W@Rd >Qoɏ?s K_7"'&76'&526n 'BQ_'BQ_[~,`*l#FR`*l#FRB@ 91B/0KSX@Y #!3&pM]rV`!#56! #'#64?!"QhRR_@0:IKiXL}/M4!wx#&'#&' #'nd2Fb.-t`4#M!P^sK=W@< 9:?5 +,">99KSX +9> &1>29<90'6767&'&'#"'&46733276=332764''3=D۴vayͤgDd''dey{d;]TCHI}rHGFFtAGCT_8d榈d*0QA^^^Fkmihhimw'AFU'`%S@!'E  E&99KSX"Pe^Ґ8*7D ! ! 12԰.#AL.#^Yq4+& "H4B;;=/?"+VhPOV !! 7654'&#"#676! 3 7llc^#,V)ۄe]6?fضdVj{ # 7654'&#"#67632327\B\\TP%I/yYk}oSKu,2R¤ຐs5%! &'&#"567632 67632'&#" ;!53276n"?E! rK,/ 4'Kr !D<&tEGGH h=" C(FK#C "&E !!6{5%! &'&#"56763267632'&#";!53276[96:@%((%@:6-:IkI:8=3553gs%+$67632! '&76!2767&#"327*W8QU{2Τ|sK^lȺhiieb-sJV"1Pһ '$Astxssq[/&67632#"'&76!27674'&#"3276I,)e[xtgO_\SG]EZSTVXXTRS7xJF61𢢜Pһ ''rsstxsst,V4@  <<1@   <220#5!#!#!3`d`du7U3@  <<1@   <220#5####!3_pzpppg3#"54654'&#"563277632327#"'$47(`7"7$>9[@[`7"7>9[@[|"O z:6hl0%[Ml |"Oz:6hl0%?[MV{$:@&E QF% ]1@%" %04767632#"'&')! $'&  7Z6;x[Y: +STTSST$T%Уb^#10dX4tsstjtssq{FVyMsaq{!&'&#"!!32?# '&76!2%%cjf_[_fMJOhk en(' c\\c( +{!56763 !"/532767!5!&'&#"'(ne khOJMf_[_fjc% ؜c\\c Vs'& @  >  91@ B  /<290KSX@  Yp]@ 6II YY @  &)5:EJ ]]! !###-}-!+V` O@ F  1@ B   /290KSX@   Y!!###`{`UV{'4767632#"'&'!!#5#5'&  7Z=;{XY:eSTTSST$TfZ#10dȪpptsstjtsss'Hs'&y3s''yk&%uN&"uBBBB|#I#IabhFaF`C`#BC`CUXC`C85YBB#Ih;5#I@PX@855Yf4@  <1@/20%+532654&#!#!5!!!2L>o||Rh"9+Fjk&#us'N@  2<1@  IIPX@8Y0! ! &! !!! 'zOFӐhgս6,XNf-T/3@   <1@  /<20!565!32#!% 4&+pٕxL@+8/Xڦ5@ 2<21@   /<2<20!!#3!332#4&+326 z6࡟9d݇,@   <1@    /<202#4&#!#!5!!||Rqf9+Fk&#u3k&%u#m')ru; )@   1  /<20)3!3!#++h$.@  . 21@  /04&#!!26!!2)DlN݇@%j@ 1/03!!)ժe4@ <1@  /2220%!!67!3#!#p&axު D+?x4&A((v@   <2991@B   /<<2290KSX@    <<Y@ I:I:I:I:I:I:@  <<<<33 # # # 3DDxM(?@ * %)21@  %&" )02#"$'532654&+532654&#"5>I8z,|йԳƆ\qѲ|!ĐBY+wps{M("3 y@ B  6 991/<2990KSXY" ]@068HGif  FI WX ei y   ]]#!33j+3m&)u# + KT KT[KT[X@ 88Y1 Y@   2991@ B  /<290KSX@    <<Y3! # #_yT:%@   1@  /<035675!#!T>Wxfb/X++0;+s2;@ 1/<0#!#;"++3s'&7#> 1B /20KSX@   Y%+53276?3 3 OM?w.-!suٵ2&]*jklyj =@!   <<<<1@ /<2<203>54&'$%53# W==U+  -=;; )@  <1@ /2<0)3!33#;ʪ+$@  21 /20!!"&533!3_||xdv+ *@    1@ /2<<0%!3!3!3OOʪ+++o2@  <1@   /22<<0)3!3!33#OOʪ++< *@  21/0!!5!!2#4'&#!!276GN6ONDPO+DCDCF&, $@   21/04'&#!!2763!2#!ONDNONDCDCo#N@ <21@   IIPX@8Y0! 7!5!&! 56! ! 'oOzFՎaa0&8@''!&$#(  !%$'2<1/0"3276'&76! ! '&!#3~܂܀s;:ŴL椤kj@@  21@ B  /<0KSX  Y3!!" &$54$)#!:ƒdv'V+w{-{Dp7):@+E'Q! E*21@*$ *9902#"'&5476$%676"32654&}:[;z631-~LӔ{0w)v ,u8w>` /@ " F!21@  /0!2654&#32654&#%!2#!r~~hhVlj9_ZZ^SJJOgyr`F1/03!!`3k`4@  <1@  /2220%!!6765!3#!#}v[(bt:d6(U3Rq{HF`@   <2991@B   /<<2290KSX@    <<Y@ I:I:I:I:I:I:@  <<<<33 ##'# 3?nn`QO6m|(N@ &* )1@ #)) ) KQXY KQXY0#"&'532654&+532654&#"5>32|PZG]twGabLx\l%%pZXkYF@\]y` ?@B  F F 991/<2990KSX@  Y##3y`}`y&# +KTKT[KT[X@ 88Y1` Y@  F 2991@ B  /<290KSX@    <<Y33 ##Tsŷ`OQ5Ls`$@ F  1  /<0356765!#!L8D{X^~ŷoPO` M@B   F F 1/<290KSX@   Y! !### >? ˸ʹ`'P` '@  F F 221/<203!3#!#U`7qu{R`@ FF1/<0#!#`3`V{Sq{F<m` 1/20!!#!<1BB`3=V`\pVg (3B@5E)! '.E4<<<<1@,41$ 4<2<20327&#"#"323>32#"&'4&#"326/{brrb{9SS99SS9{brrb{/Ǩ<9^N5=L^^LN^Ǩ;y`[` (@ F <1 /2<0)3!33#9U`33R`;@ F21/2#I #IRX 8Y0!!"'&533!3Hf\45h)_Vu;;` )@ F  F 1 /2<<0%!3!3!3ڹ"ٹ`3+`2@  F<1@   /22<<0)3!3!33#"ٹڹ`333R>.` ,@ E  21@   /02#!!5!!!2654&q8$~͓7_ZZ^{'">`%@ E  F21 /04&#!!263!2#!z~~@9LZ^_n7q{M@ H<21@   IIPX@8Y073267!5!.#"563 !"'q2 ǚ-VړiVFHL{ :@ E  F2<1@/0"32654&632#"'##3Jq и¾.`At"`<@  21@ B  /<0KSX  Y;#" .5463!##zwwVtS^a\'qk&CZq&jBBBB|#I##Iabh#FaF`C`#BC`CUXC`C85YBB##Ih;#5##I@PX#@8#55Y/V?@N F <221@ /<20#533!!>325654&#"#߰Bvz||яLmedY).ПĞm&vq{N@ HE221@  I IPX @8Y02&#"!!327# ǟ 2ғ-{FViګVH>=o{VyLFVyML`6@!E  <1@ /<0356765!32#!!%2654&+L8DثX^x~~~ŷ7oPv_ZZ^`8@E   F2<21@    /<2<2032#!!#3!2654&+N޹"\~~`7`73_ZZ^/:@N F<221@ /<<20#533!!>32#4&#"#߰Buʸ||яLmed*m&voyk&C]=V&^` )@ F F 1  /<20)3!3!#TfUf`3s48@$%6 )  51@ $-/<2<0"'&46733276=332764''3#"'&':y{d;]TCHI}rHGFFtAGCT_8d{{ђed''deFkmihhimw'AFf^^^^'`^:@  <<<1@    /<20!2#!!5!53!4'&#!!276XNpqONDNOQQfDCDC:@E  <<<1@    /<20$4&#!!2!5!3!!!2##~~EW^͓Lʣ+#3376!2&'&# !!!2767# '&SvwhfstgFtsfjwvú 9$#G_//wƪ//_H$$O{#2&#"!!327# '&'##33676>\" , Ux{ z{FVAW^3VH`3ʀ !#!#!#3 73` !#####3 Ñkk`_ !#!#!#!#3!3  o_<9d7`!#####!#3!3 kÑkk`_s@   9ܴO]9ܶ@@]9991@B  /<<9<20KSX@  Y@]##767!#'&'!ʓdսxQPtՀ`>YY~b҆12z(k{`~@   9ܲ]9ܲ0]9991@B  /<<9<20KSX@Yp]! #4'&'##767E]kKV:VS8V‰Jl&VtO\KtU'4! !#'&'##767!#3!PtՀ`ʓdսUn>qd2z Y~b_49n(.`! !#4'&'##767!#3!7kKV:VS8V‰]w&VtO\Kt`?sVszS#"&#"3276&#"#"'&54763!27654'4327654!"567376767632'&#"ssD#`At bTDt;<}J5?u_hFAXVRuťޠsj#B#' "2ZbrRUgr %',azQ^XRj7&6J- @' WoWdE\`[tO#"&#"32632&#"#"'&53!2654'&'"#5223 54'&#"5673767632&#"vmDPb!',-cX;b12i?,ZnN .rr. >._- > ^ >‘  tӪ ҫ q{&P%327654'&+"&'&'#";67>2# '&5476!36767623 !#"'&'&r-HVV?- ,4, -GVUH- ,4 .xt. 4 .wt. 4 `ta  _tp_   颈   袉   vt&'0'&'s3'cS'&sV'9@  0Դ/?]1@   /0]!# '&76!2&'&# 3!#SvwhfstkSh$#G_//ӂqV{9@  HE1@ /0@ ]! '&576!2&'&#";#UQQLNONPccccɖ#+qr͹rq;'''7'77'77did}}didii}}}d}}}}dBz/!"'&'&'&547676763!476767623 8  8 g    ) M #&#"56763 v][Jw}$)/K'*Ca"53#7 a#55#53g M !2%$'$'&ʇrE2 _fݘL{t\q F` &3@MZg#.#"#> #.#"#> #.#"#> #.#"#> #.#"#> #.#"#> #.#"#> #.#"#> v aWV` v "8v aWV` v "v aWV` v "fv aWV` v "v aWV` v "v aWV` v " v aWV` v "v aWV` v "AKKJLQKKJLKKJLKKJLKKJL)KKJLKKJLKKJLX- #)/'7'7'7%'%53-#%5#53 3#kyo\wyo\zV\Ly[`@¬@_ӤRӤRZy\yW\zn[wyo\ԤRԤR߬@¬@Vm&)uV8&!:@  <<<1@    /<20!2#!#535334'&#!!276N訨ʨONDNOQQfDCDC&E 9@ E <<<1@  /<204'&#!!276!2#!#5333>CB>ytts9L^*..+URRRя>'+#!2'674&+327'7Uj~ rGj#u~{Sqrے-,9/~V{)%'7654'& 32'#"'&'#367632*nOSTTSSTFoWl{XY::YX{ ]ststsjts].01d d01j@ 1/03!3!)2$ F1/03!3!`:33G )@  <<1/<20!!5!!!!!N)#l8U` +@  <<1@  /<20!!5!!!!!?`۪ f3@  <1@/0#!!!2+5327654&#)qmL>87||9ժFwrKK"V `3@  F<1@/0#!!3 +5327654'&#rFRRQn!&&1`GQ``07 )(33 3## # # 3׈)D"AMF`33 3###'# 3?nfz!n`QL6mu&z9u|&z3! 3## #E#A`33 3###Tw8sŷ`OL5373! ###ʭd_dTy%u`37533 ##5#`eBTse``avFOQ5a!33#! # ##53ʨ_ʨye=3!!3 ###53dTsŷ}}z}5OQ5}2 _@   2991@B   /<290KSX@    <<Y!! # #!2_=y+*` _@   2991@B   /<290KSX@    <<Y!3 ##!*8Tsŷ`OQ56@    8 22<1/<20P]3!33##!#"dA9@`1@  F   F2<21/<203!33##!#W`39L -@   8 221/<203!!!#!#)"d9` +@    F221/<203!!!#!#W`3ͪJft8@<1@ /<0#!#!!2+5327654&#;"rqmL>87||9+wrKK"V!`3@!F <1@  /<0#!#!3 +5327654'&FRRQn!&&1:`GQ``07&.sAY%.54>323267#".'#"$&54>73267>54.#"+9lR2*DaSN}aF-?jQ&h;>e3.x=&QUW+Byc[sp8<{R?S0 $0>&1H3!(BT1kBtW22Tp{:SJ#&4t}f|}ާbm:E/fcYC(+G[`_&bnqxz?P4>73267.54>3232>7#"&'#".>54.#"qKц][-2`X'V$?/(PtMBpP-\_#D-)*%-8%7CFIGԑLV"- !(,!(؜XFrXbr> %gx@]sA9hY^    , Tָ&^dc+KiB&HiCsu''z-qu{'z ,@ @ @ <1@  /20%3##!5!!A+<m` (@   <1@ /20%3##!5!!B1BL<=V`o@  K TKT[X @8YKTX 8YI:9120@BKSXY"%#3 3;^^DNl!#!5!53 3!ssf=V` !!#5!5!53 F;^^`XXNl=;%3## # 3 3p\Y/su A{+3;y`%3## # 3 3q!r))kLHJqG5@ @ @ <1@    /2<20%!33#!!5!!+A+B`3@  <1@    /2<20%!33#!!5!!xZ9B1B9L|.@   <221@  /20%3##!"'&533!3_qm||x˪Awr7ٟd`F@ F  <221@  /2#I#IRX8Y0%3##!"'&=33!3f\45h)L _Vu;;#"'&53;333###;qm||֐wr7ٟ9d+`5333###5#"'&=3f\4+ _Vu;0$@  21 /<0!2#4&#!#z||f9dK"*I@#$ $3 +291@ $ (+<2076! !!267# '&'&=3%!&'& ":Cppoż vzKB@bHam `_F$$UgkL>A9||f{%.i@.&&K /2@ p000]91@& &"*"/o]2</]90"'&=33676!2!32767# '&&'&#"XY`09Jt⃄ fgjdcbhcneNRS]\RZF1!&łZdc4,ZZWW-!&'& 76! !!267#$'&'&=3bHa":Cppomw vzKBm|| `_F$c TgkL>A9f{1&'&#""'&=33676!2!32767#&'&RS]\ƐY`09Jt⃄ fgjdcbhcOJ{ZZWWRZF1!&łZdc4,3{,(vm')[uFH'f532+5327654&#!#3!qmL>87||qwrKK"9wV`3 +5327654'&#!#33^HRRQn!&&,%wGQ``07$)`6V!#!567!3#:bCux+8.%5ժV.V+`%3##!56765!s{{v^̳;bVdžf;1@ 82<1@  /20%!#3!3+53276q"L>87h_9dKKV`/@ F F2<1@  /<0!#3!3+53276WRQn!&`3``07V!#!#3!33#;"9dժVV@`!#!#3!33#W{`39V/@ 221@  /20%!"'&533!3##_qm||xɪwr7ٟd+`G@ F221@  /2#I #IRX 8Y0%!"'&=33!3##Hf\45h)p_Vu;;V%3####! !+-}-VV`%3####! !H{˸ʲ>?V'P`yOh'J+1@oo]0{-&O"+1hN&"u  +@ 0?  ]1{-&jR -( +@(o(P-_(@-O(0-?(-( ]1H{o{m')u@@]1qH'@p]1uQq{uN'" umq&jTn(vN'"QuF'jN'"u&j:yXL/`T31'q;y'q3N'"uy'jsN&"'u +@ @O]1qu&js +@ @O0?]1saqu{7sN&}"'uqu&~jso#N'"guq&j#1'qr;=V&q^#N'"ru=V&j^#k'+ru=Vf&^N'"u&j^j #@   <1/03!!3#)ժA` #@  F <1/03!!3#`LFN&"u&jGV9@  <<<1@ /<20!!5!!!!!!+53265N)#iGRiL`na8VU`;@  <<1@ /<<0!!5!!!!!!+53265?`nFRjK۪`na=f*%+532767 # 3 3*SfL>7( ^Y/su bzK5sx+3;Vd` +527>5 # 3 dkkCQO5r))`&9as mHJq=;3 3!!# #!5!suNt\Y+wD{;y` 3 3!!# #!5)) ~q4H &@  21@   /03!!"!"$54$3!fONDNONNCD#CD+fq` %@ F E21  /03!!"!"'&763!5>BC>9sttyLZ+.i.*RRPRUC 09@2&)  1291@"-(1220!"32765#"'&54$3!3327653#"'&NOO_KV! 3j^nN?4pi;?nhf1CDP_m}`61f[JJOZxx9qs` 08@2F&) E1291@" 1-(1220!"32765#"'&54763!3327653#"'&=C>A@j\-1C]^fety>dhd.*^\:9m4l01a`RUaPOORAsxx%7@@9., ,#81@'2-28904'&+5327654'&#"567632327653#"'&'&\]OOQRSrsdeY憆GGRQ?4pi;?nhf0!JK;$& hi|UV!bb[JJOZxx8PaF|5G@7., ,#61@66'2,6 KQXY04'&+5327654'&#"5>32327653#"'&NHtCDFEwGQPabLqr<=ih<>dhpb8f83,-F@.. NO]@AHOHXDEORAsxueV<):@  '+%*1@!'(/90!#4'&+5327654'&#"5676323#s\]OOQRSrsdeY憆GGRQJK;$& hi|UV!baV|)?@ !+) *1@ / KQXY0%3##4'&+5327654'&#"5>32ȻNHtCDFEwGQPabLqr<dhpb{v^̳;b`WORAsxue{-`6@F  F221@  /20327653#"'&=!#3!zgh<>dhpbW`WORAsxue{`3s0@  1@ 0# '&76! &! !2653d-|e'%{9!Ҏ׿qF{0@ E E1@ 076!2&#"3253# '&q кĽbZZb/n||r|r|>禞f/@  @@1@  20327653#"'&5!5!?4oi;?nhin+[JJOZxx}q`2@  1@  2 ]0327653#"'&5!5!x>=ih<>dhpbB1VFEORAsxue{~{R|JTf:/@ 1@ 20356765!+532765!T:WxM?77fb0dKLøLVs`/@ F1@ 20356765!+532765!L3DF1a.&{X^}з0)oPT 35675! 3 # # !T>Wysu \Yfb/X+3{L` # # !56765! k0X^̶8D')`HJoP~ŷt32654&#!##!23 #h /ϒ0*3V{ ##"&'#3>32&  k\{::{T%+ܧ$`tad dakj3&$54$)!!!!!!3!!"d;>v78ȒFwtw{&/!3267# '&'##.5463!632.#"%;#"w ͷjbckVteVgKww^Z44*,'ėS^a\s4qVZ{TD:V5`Z37!#'# #3'jȎ_Ȁy`373#'##35Av擎LsŷK)@wLQ5`PTfs%9@' !&<1@!/<035675!!2+5327654&#!#!T>WxqmL>87||fb/XwrKK"9+LV `'9@)"#(<1@#!/<0356765!3 +5327654'&#!#!L8DFRRQn!&&,{X^~ŷGQa`07$)oPft!?@ #8"2<21@ /<2<203!3!2+5327654&#!#!#qmL>87||"dwrKK"99V`#@@ % !F$2<21@!#/<203!33 +5327654'&#!#!#UFRRQn!&&,`7GQa`07$) !!#!3#q"r+A9` 3##!#`9L3`p#653&'# 33267.''U.,aOYFqlEk*Yb?f)6^&4Z)e\3'4H./)%uxm3-`433! #54.#"!!"$+Z`bZ*J%)9 ym22my=]#3##!".4>3 4.#"3ؔMM،"*Z`iV%,[_r=PŏQym2:d_5Z!4.#"#5! 3#*Za`Z+$"ym22my 9''ί=3!!32>=3! }J*Zb`Z+ym22my 9)%)!!332>54."#54> 3Tmqn,dV=uG0fܜc.L2Rg|]= L+S{ugyC@pVGKyҚXZ %!!3!!Zf33! #54.#"!"$+Z`bZ*%)9 ym22my$)8"#4> 3##".4>;54.2>5#"q6[6[Bt^_tADu[5o%AXhV>"9Z>N2myuݔJJݓ4kn9:sl3zmpBAkP#?]##".4>3!33#!"3265IՋؔMM؋o_[,%ViWW]О\uBnsEA33>32#54."#?vԒK+Z[+ MIHݖK2ym22lyD!!xگ2>53".5!#36Tf`bQ3HҳK`1Kf>>fKIwo55owO+>33>32.#"#".5467#32>54.$+ NBR=3)EvԒK+Z[+?CHݖ&ym22lzF%.>76$73>g? ;w8q`g_ 8/@9L`uPhG/+Xam@E\>&5C4>32>54."#54> .'#".%"3267.;eHxv(A//db,N,T"?W68m86o8n~Gf=l!?1XXVQ^ IrN(`P2s|Fg}F@pVGKyҚXXV?-_34e0Yd$Kq$3 AOH@BPd%3!4.#"#5! ;+ZabY) !ym24mw 9''i$"!".>7>73>33!&-p|CtBnK#QMCYP8PDYMG)#@[9r El~mXBkJ(~`#J#! 332>5!)*YbaY*& ''uxm33mxY7 .5332>54&#!5!2>54& #4>2YML+`qp^*PQwM%ʠJL%@T.DuW2g~GJeArW22Vp>'C\5vqr{_g65f];hS<?[vq32>=3! #5)ZcbY)uxm33nx 9-!y..+532%.#"32>=3#".54>7d8UjGk@*oKie2R䑒XNoԔ Hy54&#"#54> MJ*rIhf2R$WKUEg Vq32#4.gVb4fc8fGl IpX*9ٜWWق\OvO}";".5&>32!!332676&%4.#"32>VxP.O哠MLldNQ_(0eoke.+K;ym'+H5#9GG@k”X^yݼ1L?=d{E9dM&7%@r[-jx#!4.#"#! 3#^*Za`Z+$"ym22myu''ί=)8d)#".=32>533;AvԒK+Z[+HJHݖym22lz =332>54.'.'&>2#.#" .5`RPd86aTCq_F(IÍN'NzVOxO$&KqPՔNRҕPDtT0 DiI:U=( -?XsKZr@=lT,TB)'F_72I5% Binfs=K)3! #54.#""(,\`bX)%)9 ym22myug1@ .5332>54&#!53.54>22>54& gML+`qp^* 7(JL%@T.DuW2QwM%ʠD|g~GJeArW22Vp>2?J)_g65f];hS<?[vg'C\5vst{&UJ0M!!#:Ox'3>4.'.>753#]h88h]ˇXXˇZZv;kęl;T ֘TT՘T2n+".'!!#5#53!22>4."*>n^Kh˾"ٔMM$VҎU%&WЌV%4毚e$ NMZ\22[a67as2n)3 $.=3".546;2%"4.#>db7>53o:Z=z3^SB[9z.Z$9H(@{b?%9H(G{^;fCU5)#&>32#".'332>54.'&umG~f?z`< =[<'OC1& 0B&Je;yD"FnL0R>#+G3 %'<*.cZ3!Z#z`*%#".5332>5332>53#5#"&EW]1:V;GrQ+qxFrP+?y|~t?xqNQqH 1\Rzb2]QziazVd{5#"!!#3>32Bv='V fcqV/{(2>54."##".>32533#/+QtuQ+,QttQ:}fyCCyf}:ʡp<323eBvVH=`fcb3!!3276=3#5#"'&>=}TVCvddZLPO_b0gbxwqV/{-3!#".>325332>54.#"Zs:}fyCCyf}:!IxW<]F0 !KwW<]E0 RdaUٛUadN{M%AUaf0N|M&@V`f )3!!!#z%LVd{!#"!!3>32VBv=} fc\V{,<!4.#"#3>323##".4>!"32>f1HvORg;*cmv=D~mQ.`FH`87Zsf2(+@)QQD/dp=)Ze 5".54>3!33ItQ++QtIQvM&BB@{<5faep<32dBv\=fcVl`3!3rN V*32>53#5#".5#"#3>32HqxFrP+?y=kDKnH$AWh;⣛2]Qzia)e=2\Q'I7"qt-3!#".54>732>54.'#"ԤB7}C=DD>o[(QwPOxP(+G4NxQ)  ҠNׄzΗUUzxƔ^SoAAoSMwcV,EuVX3327653##"&VWCvZ_^{VgbdKj0-B#5#".7>7.54?37>32>=4.'<4H\pCbj7>m^D];"$*.GW(9 !CeDMxR+EpQ=lR0, d-J6Bxi`K 4GS)D59?;-?""EsS-7]{DV5wuh&2uV9{!#"#3>329sBvH=`fcRZ$(>53>32&#"32#5#".'&732>=4.+7k-42Grb*wќZF\tEff568V-Ia8FsS.*buG* `zE*Kh=Lw,J6ArZXuF3[|Ih@mEA~-332>5!##5#"&KwS-ոCva2\Q/gbVr` 3+53265F1iL`aX!32>53#5#"&5#'KwS-Cv2\Q{gbV{{13!!".54>54.#"5>32 Pt*D07.54>737>7$F7!#0D(YȸE  V`*%#".5332>5332>53##"&EW]1:V;GrQ+qxFrP+?y|~t?xqNQqH 1\Rzb2]QzXiaznV|,D3!!".5467>'.54>32%">54.32!!5>54.O|V-?yct?!=V5)?`B"#Gl4ZzFs`afF}gS{q94myPIa8X{XV-332>533!#"&KwS-sCva2\Q/Vgb{'3>32##"#5#"&5332>!OW[,ĸ32dKwS,Bv\=2\Q gboVX{J`%!!3r&"`V'3>32##"##"&5332>!OW[,ĸ9jQ1CdƸ?oS0/3K2\=/ZUVgba/[)V{,%!!#5#533>32#"&4."2>s빑:|fzCCzf|8+QttQ++QtuP+__daV؛Vadp=;2%"4.#2>!gWxV0PZbo;;ob}t{ E}jj}֗RV'IgS]VWrB+OnCHpM'LF&OHBM_/9k~%!!5#"&5332>73X&"CvƸIuT.gbZ/X}MR73#3#Rd%$'d ZZ-,DC <21I:03#3#D-dC'KRX@8<1YC %  <<1@  <73>53`#8!2-A,#8"2.@,eX5AnEQ`Q2,  6AnE RaQ1,XH`6ܴt o]1K QX8Y /0%4.#!5!23!5 &EyWJ{tEKcf:%.UrUX`?! ̴t\]KQK SZX}/Y1  //0#&'#"'532>54.+532EM:!FPP7.3"$,EV8(3e\Z`^pQg6 4[u;X`( 1K QX 8Y/0##!5!ƺ/я`<F Fp j ]K QX8Y1 //0##4&#!5! .T`&`ؙt`!FFK QX8Y1/0#t``Xm` E 1KSKQZX|/jZj Z ]1YK QX8Y /0#"#467#5mPWAM8`y'` 7F Fp ]K QX8Y1 //t]0#4&#!#! ^yƒ/`k[! F ܷtp / ]1K QX!8Y /l nn\ ^^D D4 4 ]02>54.#"5632 r-YttY-0ZCB]rWfT%`t[(([tq}=Qҍ ,DB`#1K QX8Y/0B`LXV`8F a _ ]K QX8Y1 _O?]0#4.#!5!22X]ֆ5hruB=X`5ܷOO??]1K QX8Y /0!2#!5!2>4.#!XXuxP""PxuXdN""Nd`@kk@>uu>X6  Բ]1K QX 8Y/0!!3CWn`4F F1K QX8Y  /o ~ ]0)!2!4&#%w4g`:sƒXp%d'F &Բp]J#:#*##h#Z#Jhs #]K QX'8Y1 //Xl\v]03>3 !5!4&#"#>54XBMLbL3xzaV0*bP]` NFd6û[I3'@Vt`$FFK QX8Y10#t` Xx` 1F  K QX8Y1 / ]0%4&+532!5dj~~ͻX{o.` ]  Fܲ]ܶJ < `]1YTid]K QX8Y /44 && BB Rd]0!  ! 4.-_`'0kIuX$XBH`KF >   ]1K QX8Y ]0g G ]35%3>1X‚$FDvj/h 29Vd`^F F1K QX8Y ܷJ  / ]KPKSZX @8Yj\J]0#4&+327#"&5! dGJ)CRC^ СIE d`j Fܴ_ ? ]ܷ== KK ]1K QX8Y /}ܲ_qpPJ]KPKSZX@8Y0! )5!2654&+327#"&5(V^HJ)CRC`܎ۺIE XVcGܲ]ܷ6DT]1K QX8YIJ7]0>=3#4.'%f:uh=hH9=1c䒙.×o~S&PjI9X`lF  ܰ KQKSZX @8 /Y<;?HlY_Ji|= ]1K QX8Y /ķT8Tt]0%367>53!5d,.&=$S47Z7 J}XM./GV_ R  F ԷO_o]1K QX 8Y/IJ]@ P q0#7 !5!WWV LTюnX`@F 1KTKT[X /1YK QX8Y /0KPX//0Y#4.#!5!2ʺ3P~RO~zM::IviF)5]XS`(* )ܴ]ܷH;+P`p]K PX881/}/Y 0WW]1fgwv]K QX*8Y/ܷ/]CC"H%]0!#3>732>73I0K8( )l_+bkcE8" *=Ki`!#@5]Ey+gE=(Ci䣉W=`I F 1]K QX 8Y /j[]0#4.+#"'53265#5!2 $@pP{5NA&G.]l,^`Jce:%r3C `',,`',40`'0=0`@ D103#`n`@DD1<203#3#`|"%0#4'&'37676537653#"'% '##5 rb{ .q & q-aT !}Bs12j{@E#$]} q!<"ibP-F`)*5"2767#"'&54767&'&5&76 '##5M@V:118UF%/>7P6.N@?^G?D)7-#F}Bs)^ &# \*$@.") n F>]KH*!#TH#bP-F`z %3#%3#3#%3#ƴ>^< %3#%3#%3#3#%3#>>^!#53ӤR@ 327654'&+5336767N5G4pQf$h?FA@6b ! eI(R[2* #53 3#ӤR%@-$%#5754&'./.54632.#"'/XZH߸g^aOl39ZZ8{4<5/VVL89CFnY1^5YVeU"756767&'&54767632&767/SD435gcbnZdF31`9:H:ZU!LOTAKv?=0ps2#ql '~U'}>ry3#&-9&p. &.&/ (f&[- (f$3  !27# '&5767"$JKԖ^`e~h'?6`vc–e4- (&[-?}R%67654'&'3#"'532# b&_-q  ?%#&'$473327676'&/3327653323#"'&'TPxmil_Qb_y^@@$;sR,%@n\Kf% I01_2F,k>GHܳ&%0l}=J"5^.327654'&'&#"&#4763&547632#bzL,5;(.;Dn2KxAZM\MObxX'*9:X DD(NOf7*(?$S-8APH&-? "327654'&'2#"'&5476B!799[]KB{ƶ`Q%T*WE{R,,9.UMAx|KU#JN @ &"34'&!5 767"'&'&547632?,3/V%._]g>v-(tYhYH9!$3/,;̠X*VL_ !"bWg3ZfJ6%#"'$47376767654'&'&'&'4762#&'&'&VfxH?Ba=~T;~BrC:@_` B(EN><}9M I&huqc- !P85J.39sJ%*==!'&"7*S@UYD J&r. $5%5%HHnnnn$&567&'&54763233"/#"'&5332767654&#" %!lE?I(7 /4KU^r8Z #08 " -d$* 9^W4'6O'&n=NV)qaK" %$5%%5%HHnnnnn$5%Hnn$-&'&5476323"'&'#5276767654&#") lE?I(7$# +EȓV " - 8_W4'6O -n=*{nmp" %$5%Hnn8(#"'&54737676533254'3'&!9EO)"a 2=`YG g -SGL(E?4mmb}8T"RY$6îs9It6Y ! 4&#"32>"&462X@AWWA@Xz柟?XW@AWX栠h732767#"'&'gC*6:)kXZZC5"LMD6{S )L}@FOwO $/-#"'&547.#526763"327654'& lE?I(7$# +EȓV " -. 8_W4'6O -n=*{nmp" % 4373ËF3# !#'3%1yI !nR#'337673#" %1BR{6)coajr!nUPymL%#'37676537653#"' %1/(/H/; 'G 44.5WY9!nr|> @2%,*;l>3  *"2767#"'&54767&'&'&76#zf\MOYp0;JcX~VI|eepdkAXH,7p 4C@#90L@rRiUZhsBBsǮuu5aU#'#"'532N%bU`DK*22<!&'3673b~ĚZ00ZĥxU:Ũ ;6I<3#&'#6̴UxĚZ00Z~bI6; :d#"'&'&547632#54'&#"=:i_{\ %Z[,,G\O98<SGU37e{a}UwnWl42@B^!x$%-`+-!d! M fM&L&19 &19 &'.>&0 &0&2 &2 (&[}8 (&[1? (f&[. (f&[1 (&[/ (f&[0 (f&[2}&]L}R&]>}R&]-}'-&]L}&].}R&].+}&]/}x&]0}&]2 /'L>_ r'>_ &&_>X &&_-4H &'_ &'-4H&_-( &_. &_/ &_2 &a'-_ - &a0x &a'/ 0x &c.x &c/~ &e/Ru @&g/,:654'&32! '$&73! 76767#"'&54767632)B,4((7(*Hnق@AZAd#?zKbNLZB`.+M;3*)3P&ڴF=)d \^tL"9;l&NKCW4,E$2Hf6&-k&'-~-k&/x~&0&2xxkH&-~kH&/R~)-%2767654'&54767#"'$473$62 #dGf>5?AhXPA7.EB|=Q#!w*6(  %{{qeVUI&b \^~B".54767!#!"'$4733!2$6=4'%$ `h_ >5 ?Ahm/yYk>ba7# #5&qeV&b \^~B"jj7)&>F&l-F&l/qF&l0X)+&Q)+&>F):&.)+&.X)+&1)4&/&m&m-jx4&m/&m0&vH&'-? -vH#"'$47332767654'3HdnaPm/1]]LGL"fh8D%jdQ45b`ޜH&L%]H&'-? >&]H&/  2?4#">#"'&54733267&'&547632&'5#"32764&__A-D$Iln9e|8-H,-C,QN(Jb41}>XA%v3hO =J6>(E& !BQHJQS'Bg=q?%'i!.C] ('0&[-? @r'>q @'xq @4'q @'/, qJrr&r>J'1rpRLR%'&547632&767#"'#'3X\lTX\D8/0E= %1Bx:=$!"4'Qjr!n8j$(327654'&#"327#"'&5732#"-2!WZWXZV%2-Z(.5__52ZJkV0B7,g`p5oU%mao3/AbM3))I<<d (@  1@  0"32$  h P3343ssyzZ (@  1@  /20%!5!3%=Je+HH=  21 /203!#3ulh=   221 /0)5!!5!3=lȪ=   21/0%!!!3!l =21 /0!#3!=l*=1/0!#!3!=lcr8A'91/0#3ASuNA (  < /<10%!3!#N{ 2@ EEܲ@]91@   /<02>4."#&'.4>329[ZZ_PGr䆇䄄rEMp`77`p_88 1ŧbbŧ1 y@ 1/03#+q!/@ E  EԶ 0 ]1@  0 6&    z>z='+@  2291@ /2903#36Q*=q33# =qCq @ 1/<0)3!39Uq"q @ <1/0!5!!59qKqO!>@#E E"ܲ@]ܲ@]1@  /2<0%!!5!&'.4> 2>4.":RJr 惃sKRQ[ZZ{ 1ũbbŨ1 p`88`p`88 %@    21 /03"3#!5!p9 fq2@ E<21@  /<20!#!##"&6 54'&"3qvCf^]8mr^:<UfɃ]8ƃD '@   <<1@  /0#!!!y5!Փ/= '@   <<1@ /0!5!!5!#55ߒѓ+ %@ <1 /0!!27654'&'2#!3,R4,,=iXXXlι]Oz}I__ҭ$;@   ܲ_]9@   /999@ 10#4'&'5!4B 5McAq_9V= 491@ /̲]촍]0 53#T9+!-@ #"1@  !/203432>324&#"!4&#"!}x5%^ZHZlK--Xh&|ŕnc= &@   <<1  /<<0!5!3!!#KK?=9@  <<<<1@    /<<<<<<0!!5!3!3!!#!KøL=??q!@ 1/0!!9UqqK==1B/0KSX@Y! #tFC00B~+n 4@ <<1@    /<20327654'&+!!2/!!m]%i ;@ED\TqQE=4."XErrJSRJrCEoJ[ZZO{ 2Ʀ1 { 1SV/p_88_p`88} @ 1/0#!#}+B} #@   <1/0#!#3}Om +@   <<1@   /0!%!!5!!z;  TKѓ+qO $=@&E "E%ܲ@]<<ܲ@]1@  "#/<<02>4."%#&'.4767673 [ZZTXErrJSRJrCEoJR"p_88_p`88 2Ʀ1 { 1SV/ qO(#&'.4767675!5!!2>4."XErrJSRJrCEoJRNQ[ZZP 2Ʀ1 { 1SV/ p_88_p`88b/1/0!!VBf#"&/#332?E=9Qct2 %xf" %/x $Dp/1/03#=f7u91290K TKT[X@878Y3#'#f[fE9190@ Ueu@ )9IUe]]!5'3{Bf3326?3#'#"&'Bx% 2tcQ9=Ef$ /% "[fC9190@Ueu&6FZj]]5%3%[{fS/1/03#̭F'/1/<<03#%3#\yu  <1/0#527#53gu  <1/03"3#  gd 1/03#!!Mdd '@  <<1@ /03#3#!!Mޒ1/0'!! ''/33!!3'#67654'&67654&nudruxtddx>DD>xIIv! RTx`aw,0dc1-!:;z{t{*L@$% E+<<<<@!#91@$+<@ (+0%"3254"3254#"54!#"543263 #4#"h??AA??A'+,LW@@@@@@@@pطQQ9/@@1(. #E0<<1@!0%* 00"3254"54$3  !2632&#"# 54-654!"`@@@CvBըiUv˫:knL?o@@@@N;Ejfae:.88U8327&'"254"%47&5476! #4'&# 63 #"'632# i60IKhh*)7!o^RX;*:9u`/'"6OfqAtqLI $\9.ȶmQ!6@   E"1@"  "0463 #"&'7325#'&&7'6met "xCBCquЍ h! ACBB )2@  #&E*<1@  *%/0"32654& 4''&5432#5476$ % U%|{e6Lj` %"%:yx~)RhKK>  65@$- 3 (E7<<<1@ 5/7&7"32654&4763  !27632! 54-654!"#"`$ % 琺By#xJi:OknLIo %"%0yKpjNdfDQcwiC|85ss *;@&%   E+<1@")+&+02654&'&47&7'73%$$!% l݁6ZA| $! $Vm-G4 p?{1@ F1@ <@0%"32544!  #"54$32@@@)@@@@Pvv .<@- " 'E/<1@ $/-)/<20%"32654&672#4#"#"'&#" #"53232l$ % L 7*>(z*M#6&8"$ %"%3|0ۯqiPWu{+?@-$'+ ,<1@ )!,&,<0%"3254"3254 #"5#&767663 #4!" @@@@@@!Ӣ7y-^@@@@@@@@edm%W ,9@. $  )E-<1@ '-+"<0"32654&4323254#4#"%$7"@$ % 쐋'(uj %"%@կ̰Xsgh\_"9@ $ E#1@# #<0254#"53265$54767653!"'#W@@>z]U]iTrs@@@@pegu/ssIs|2@  E<1@  0"325447&763! 3%$5@@@ԶMg@@@@R&Ѩ'LBIs2@  E<1@  0"325447&76! 3%$5@@@ԶMg@@@@<%Ҩ'hBY E"32654&!"32654&&''"&5623253765$7465&'7$ % $ % Kfg饤IJ %"% %"%IKbv4ˋ42@7-]fn9h%A@'$ F&1@&<<@" &0!"'# 432!32533253"3254hfg襤>@@@ JJ=|\@@@@@h} -?@, (,$ E.<1@"&. .<<0"32654&2533253!"'# 47&5432d$ % AfgB %"%4˩/JJ=%܉Mh -?@, (,$ E.<1@"&. .<<0"32654&2533253!"'# 47&5432d$ % AfgB %"%4˩JJ=%܋L@`$@1@  <<03!23! '#"543225O)3Ɯ)`,88{s *;@&%   E+<1@")+&+02654&'&47&7'73%$$!% l݁6ZA| $! $Vm-G4 p& ,7@  '#E-<1@+.%.0"32654&4! ! &# ! ! '&54323 c$ $ 6buUKX $ $8${nE{N%O 0@@2, %&E1<1@%/1!*<0%"32654&&'&'&5! 765! '676%&4% $  ,D )@ ' 1#-E5<<1@ )6/%!60"32654& 4%$54!232#"'&#"! '&5432h$ % ${ajjh@MqKy)LJm_ %"%1EYl0xP^b8Rsu_|]F'"2''&'$!32'&547"32?6AS2;9’hhNU~ +;9jq!Bao'@u@` +@   /991@ /0! &7623$'4'74"Y#!A[VB8?<kP$U.FM?>={{+@  E1@   <0 ##"2#"53254#"n=;C>@{jVR777r&WA@ji  /1  /<20! ! !5 74! %&?%~?>~@i$@  /1 /<220! ! 3!5 76! %&>%~?>wJ~~@ji*@   /1@   /<20! ! !5 74! #5%&?%~?>~N@i.@  /1@  /<220! ! 3!5 76! #5%&>%~?>wJ~~T3"36654'#"5432AA\(DeN[̼o[$N[u%@ /1@ /0"3254"547&54323253r>Juum@s> [yu?{EBXF` '656%"'&76! 4"3YVA!. {x9322674&#"CCjFPH OQ$!%!p'(FnJv-O!3] $ $z{&LL00, ("32654&&3 #"4/&5432N$ % s $ˌeqɘzm %"%82y,v\#"6@ E#@! 1@ ##04$54%&&5! $#"57"3254ix@@@X4|`Pٳ ?@@@@ ""32654&5&'7!$#"47#$ % dt.; %"%Ȉ_p 8>u%t/;4#"#"'&#"$#&532327632! '&57"32654&"3C2z7J,"/IN\=0BWTO3H$ % Xt\DD\t] 5<\UCfwpv  gH %"%V@/1/03#V '@ /1@  /<0'6"%)56574 65+*+UGm++),}݅.p\(>.4"!27676327673!#5654#"'&'&#";&543.%2~*&IHHܝBOg(LBC]i%>e>.`h>3A?~= h\$kb8:;-F_Zkf2)N !@ /<<1@ /<<053533##5N؎؎؎ JP>r@ /1@ /0432#"73254#"ЄLTPPHHH` " 7654&' ! '&476^L:NbX1coqoh`WĒcg&24764'&#"676'&'&5476  pHgc/5pIu upHECle\gUܚsuϨcy\$24"27#&5432# '&5?$5+r%3]f́|pHFPfouTapH/%24'$5432327#"'&#"%$'#"54322533]L/|tkZ1AQf(3Ɯ)DjR:jTh8KOpt$68{cW%24"$'&5?$532&'&32!r|T9lc ~x?LvTamY<KcW-224"7&5&326532&'&32$'&324!B}b$|T9lc ~xr=Ch(筭 ?fXmY<KLvttY4@'&''"&54323253765'$543227#"$#""32654&fg饤u ^|uISL\>$ % ,IKbv4ˋjEaTW8ҋ %"%{ &%"324"324#"54!#"543263 #4#"h??AA??A'+,LWpطQQ%Rpt MU"32654&254"#&76767%4#"#"'&#"$#&3232763276'767$ % nnvp+-"2D2z7J,"0IN\=0J%.3?5xv'Q %"%933hk//3wt\DD\t 5<\UCrTF-2bG;"b,i $5354#" #"524"m~ŶejsX\|9 I"327$"3273653%"'%5254%$7&76%$5#0#&7626A?A? Tcb*@RX6&$Hu Ӣ5r@@@@@@@mo6J,/7'- /ԋu cd LX"327$"3273253!"''&76324%$7&76%$5#0#&76262654&'&A?A?fxԅ$8$+Rb,7Hu Ӣ5r$!% @@@@@@@mӔJce$3- /ԋu cd $! $x! !5!!%64'7;OtJu4SSv;X"]| Kn#"57!3273!q-JLX1o2!6&#"&5&632!~po~ds.xsgd{1n? #"327%%5!5!!I4&#"!5!! > F+ W5y%# !!!)!YF^e.*zMjO<%!3!kѳ/Cx} 4&#"36#&3632ʴtÑl}9^DoUD!#!63 䂍w8 =10<x "3265!%"3!!5!!5LiA-*'=Xx%4&#"#463!!&632ʽtwaf ɪXPdr !#"3 3!  ! 36_J ?89biVGej4T2#"5!5!3>53{9ˉ= RծP%#"547#5!3273ԢV-ؠv湯V4bn%!#"5732653!!Iݼsrǀiy8/quŚl# 533>54'7п _*jiю6&#!!>7)!!! ۜ ٽ3+dr !#"32!527&'! ! aUm}EI=a]Mv8"`_؀d"#327&%#;!###) 7bb6eeabe^tN4 LNhBZp!#!3!3!ѽr4fQJA#4&"#&32˲(۬TAnu%#"533273ҐM=űU5ax~&$=3326=#"533273~ս˶θ$!S98ȗX2%6&#"#3632χ/050y5#$!&# %73gu6wfranXx{ "3265!# !33!Ǜ G$P ;w4&#!!>7#!!#5!#!2p]9P~ҹ9{m{(d # 5332654%$432#6&#"ѿͬ]`yDeȪƋv#"'!!33263ϴ< ! ## #5!zk{TO_<G# 5!#!32653Gó Z^ѫ54&+#3!23X׾7bo!#"&7326554632#&#"}}5:#ÃL}<B%#&=#5!;3Ѿv ZtmfanR#&$3;>53RL[a^}#ǩP 3#"32654&#"3267+!!.546;!5!5>32EHPw=xXcahxZ֔յ~9]~}_h` dYZydDz¼d! 7332&53[]&"z~8<kԤd! %76'&53'!2YK/0>!<ACijAI{2HPZLi$%#"5%674#"'! 4&'326LҫES}*6w1(Wd(a'! 47&#"#&%'67$!26! # !2^NWue+ htzkof P(B Ԟclq/쬱zbd`! 7332#"#'! tX!71n?\od`"! 73!54'#53654#"#'! oI% ݪy=Z\ϿT҅h)! 7&%&326#"6\Z[FxL *qaqEtD'\ad`!263 ##"! #"32dh~eos` U@J(6dd`# #  \D #m8ZV! 73!54+53$54'3mF2( g)y>[Fa[d( j0#5#"#'#"#'#"#&%'67$!263263   C1Cm\`X^+ >fTo'd! !4#"#5! &' !2oIeٷzD&?! 1N//T-, jNY! )!"67 #32RfN0v{t{\n@  ;kd`##"#5#"#!263 /8WaV@~Y$3 d! 7332+53274$53[]Ǯ-mdȨHdi! 73325 '6%5%cWd9)*@]OruGvd###"#5#"#%7263 OާPvĶ[`KBbw+ IH@Q)M! 3!26573fRT|u]Zp %3! 32%#Zܸ:_\2)K[60~  ! 73!2&#"#4+'73263 nG{ RmWPOC0@]g[x(2d!`2:! 63 ! $=46326=4+5326'#"! #"32d]xcd렷PSԟa ]YeRj\wCD4soX7<\"qd! 7332&'"#'!3wU!#g2*>|?\K | Pd)_%67!263 #5#"#'#"#4'&PQ`[n³<5)5A)q+dN! 7332 4733673gQ|zӑkB_v?  ><(! !4#"&=6#53263 .'"325zGN~ZXR86zH9!? ,V5''5X4ʸMk74#"63 ##"#! ZtvNA%tG4SD9S`Z8A ! 53! +532'6&'3t{>@׭ `W(;Ƙnl=Wd ! !63&#"32[]\x4+G.BaX'!26;'"&=4#"63 ! 3 #&9RXZ~NG}+y4X5'(4V`G$>d%3!!! 733245#";#!3  ^[ V//UQK_ K+; ! 363 #"32bV`(/?;L#%#5265&/%77654&+532Q sQfV?LEDOR#\ՀxЖsp2Eš"%-4.-UAd'! 73324'#53256+53274$53aW((qegZa mV=EHqx'! 3324+53256+53274&53[]woqdaH x{mQY>EHqd`27$! 3&'6#"FRᑞߋTs_0sIKSzydd_! 73325#53$=#%Y_'ݵo#B&=ZZyu?d%! 733254+53$#$! 727_Z CM\WA^!)vTwd'! %!! ! 3#3!"3253!54#"Y^ arrr6fwwid9`$!"'!525#"!$!263 #"729Y`^H]Y'zSe"8Z`! %$54#"#'! ! 4'3676 bO4`V69"S+\PBh$43 32/7! 47$3254&#"PŽH=Few1(W~d dca54'&#"'67$% #7#";;_CC B|MKOm0T$,n' 3#3#3#nʺd\(2"4;!"4#"32JA{ntv2c`Lзh=@ B1/0KSX@Y %##.d+hK'Eh*hO'*t@1B/990KSX@Y sNO'*)tN'")u'ew^?1B/990KSX@Y 5](&xyw^O'*1t'56'&56'O'56O&E'E'EO'EO&O'*0'wE&O'wEO&w^O'*?0 3#!38Ygg`nC^^n7]^7nn7]]0d"&533265453zWA@XzCss!AWX@+!U#454&#"#462zX@AWzB+@XWA!s0U!5!2654&#!5!2@XX@s0{X@?X{0U 4&#"32>"&4623X@AWWA@Xz柟C?XW@AWX栠H> %'111 ]]1<203!3CC~K3#K!5!${1V #5#53533zz{{1##5!z$ %{{:'U'"'=wq'h9hK'Eh1hO'*tw^:<1B/0KSX@Y7 5wM40w^O'*)tw^N'")uw^'w^:21B/0KSX@Y%5^xyw^O'*1t'56&9'56&O'56O&'wE&O'wEO&'wE&O'wEO&w^N'"1u<291B0KSX@}}}}Y5`sbbs]103C)8)K'E)*@ 8AKTX8Y1  /<03! #4&#"!!ˮî$*\u)O'*tw^ 2 <1 /07! )5! )w5BhPa.,~w^O'*tw^N'"uw^'y` 2<1 /0%! )! !`aPhB5jiy`O'*"t&''&O'O&'w'(O'wO&('y'(O'yO&('~ ~21@  0# $54$!3#"3nn͙ nn{'|'|w}'dy'F> %@ 21@  /90"32654&"$54$32#Bz_̀#R3IK'E %@  21@  /90"32654&#4$32#&f̲_ȭT#R3{O'*tF> (@  21@  90%2654&#"3#"$54$3Bf̲_ȭ벃F>O'*t FN'"u  (@  21@  90%2654&#"672#"$53z_̀ʃIO'*5t#'F'?'~'|?O&~O&|' F& O'!FO&!?'#~&#|?O'$~O&$|?&~#~  $~ ]21@ 02654&#"632#"&53XP^J\TaaQ_VFTHUGQK})~J8 2654&#"03#"&54632xOaT\J^P_KQGUHTFV}i~F'x'F'x '#F> 1 /0#4$32#4&#"#fK'E< 1 /04&#"#4$32f#O'*t<F> 1 032653#"$5fF>O'*t>FN'"u> 1 03#"$53326f餗O'*5tA':F&:?'<~&<|?O'=~O&=|'>F&>O'?FO&??'A~&A|?O'B~O&B|?&~A~ ] ]1 03#"&53326yaO\T~JPML 32653#"&5T\OaQLMPJ~w:1/0!#!5!)+jK'EVj@ :1/03!!)ժjO'*tVw:1/0!5!_++wO'*tXwN'"uXj/jO'*5t[5&Tw'T&V'VO'WO&W5'Xw&X6O'YwO&Y'[&[O'\O&\&~[]10!!3 nC ~21@  0! $54$)!"3͙ nn{3!5 nw} (@  91@  20"32654&'2#"$547!5__ȘLӦnjFY 'iqFY} )@  91@  20"32654&'!!#"$54$C`^ȋMӑnj '*i<qw "@  91 /20%2654&#"!5!&54$32__ȋfLnjw'*<sw'"<sFY #@  91 /20%2654&#""$54$32!C^`șMgnjFY'*T<vH}'ow}';o3'vFY'yv3'wFY&wyFY'"T<v\ 2654&#""&546 !j>_IEcI_(0MJBSKFXCIn~|Q;n."&5332653ܨabaaJPMMPJ\ 2654&#"0!5!&546 _IcEI_>jm0(MICXFKSBJnn;Q|~w 1 /0%2654&#!5!2#bŘ쥒FY 'FY 1 /0%"$54$3!!"Cꏙƥ᪑FY'*<w  1 /052#!5!2654&᪑w'*<w'"<FY 1 /0"3!!"$54$3CbƙFY'*<H'w&;3'F&13'F&1H'w&;H'w&;3'F&13'F&1\"3!!"&5463RiPYnvDZHCn~}w^ %5-5 ^j22F  ? 1 /0!3#$53TCc Xon2K' E @ ? 1 /053#3  cCT-ncCO'* tF   ? 1 /0%#5%3# c--noXF O'*tFN'"u @  ? 1 /0%!#3#c-gCcnO'*3t'F'''O'O&'F&O'FO&'&O'O&&~  ] ] 1  04&+3#XHǜV+.#"#"&'532654'&/&'&54632Cw7Bh#-8GC>=JGBAm'./G?;=~ÇH)@@V\`RʺªV\`RʺªhZ·%XhZ·Fl632#4&#"#"&3326tҪºR`\VҪºR`\VX%Zh۷ZhFlO'*tF'32654 !"/.#"3"54!2!rz|K٬42 swUҤ'4X˧|`í~pX˧|`J3~F'*<F'763 #52654&#"# '4!"326(24׬'Uvr!24֭٣K|zsp~ȕ`|Xp~8=`|F'*<&F&'F&O'FO&'FU''FU&'FU&'FU&'>72#52654&#"#"&'463"326[*'sobI=J>",BR\*$jt_UV) '2654&"#"'&54632! 33265,B:d:B0<~JIjˮîB,">>",BVU_tjN*$u) '"2654&'632#"&5! #4&#",B:d:B0<~JIj!!ˮîUB,">>",BVU_tj$*\) '"2654&74&#"#! #"&547632(B:d:BB®!!jIJ~<UB,">>",Bu$*Njt_UV)O'*t)O'*tS^$264&"&546; )5! '&Vhf# fw_:@ 91@ B /90KSXY%4$32#4&#"!7g#ʲfhXdfF.=@ 1@ B 90KSXY#"$533265!>ʲf"fw_?@  91@ B 90KSXY '!32653#"$5g"ffd餗 K'; , '< , O'= ,;'>,;O'?, 'A , O'B ,( (2654&""&546323326=3#"&=bFntnPX/Q,CEmaZT:KMMKFHn|ppX;oBGj9$ 3>2654&"!&546323326=3#"&=!"&54632!2654&"bFntnP?+/Q,CEmaʔ/bFntnPZT:KMMKFH;XppX;oBGj9|ppX;T:KMMKFHFY<@   91B /0KSX@ Y!"3"$54$3!7YꏙbXhUFY'*<.w8  91B /0KSX@ Y!26544#!wb gXw'*\<0FY:@  91B /0KSX@ Y'!"$54$3"3!YhbƙXiU𥒥FY'*i<2\'%!"&5463"3!\=.̞RiPYB~}nDZHCw%#535!53!3##q=ԭ-!%#5#53!3!3=~0Ԥ!O'*t6w533#!#5!5#5q=-ЭԤwO'*t8!3#!#!#5353=ԭ0~!O'*Vt: 33#!#!5#53m unfy~n ,@  221@  /990%2654&#"672#"'"#3z_̀ٷ{O{ʃIH+'sZ@  21  /0# !3! !5aPh//+jiN !!!5!;VnVN#5!5!5!53!!75$i2$i*mւVxnVnՆu!s #'#37 ͉sH+'Y &,:s &-< O&-= 7&-> 7O&-? &-A O&-B!!!!#!YX  !!###!YX  !!#####!YX    H!!#######! \YX     !!#########! YX     !3!!  !333!!&  !33333!!e    G!3333333!!     !333333333!!      !3!!#!?r !333!!###!?r   !33333!!#####!?r      Y#!3333333!!#######!?r        +!333333333!!#########!?r         SC !3!!#!YX\\SC!333!!###!XX\\\\SC!33333!!#####!\X\\\\\\S FC#!3333333!!#######!ZX\\\\\\\\S C+!333333333!!#########!YX\\\\\\\\\\!33!!# #!՚rՙr %!3!!#!!2^DD^ Wc !!!5!5!!!wsX #5!! !!'!%'! !7%!77'7!  ww u||||||||||||u  G7+/37;?CGKO!5#535#535#53533533533533#3#3#!!#3%#3%#3#3%#3%#3#3%#3%#3??????𨨨!!!!aOq:#[!' 7#}CrarCrrD:[! !rarC}rbar=` !#!#3!ff`G [`3!!!!!!!! j /t`Ӕ&{o{4=J%#"'&=!.#"5>32>32#!3267#"'&32767%2654'&#"JԄ℄N ̷hddddj||MI؏ii~ST`Te__ZjkSR\]i߬A@o\]Z^Z5*,=>` #% 54)3#4+327#!5#53!2x9||ԙf_ڪrĐq{Fg`32654&#%! )s7F0Ǔ$g` ! )#53!#32654&+7F0ɖzٍ`` !!!!!! /`Ӕ|1#"&'5327654'&+5327654'&#"567632p<54& #.54! ì++f++$$>:#tNPƳPNM]*U3MY + 3267>54&#"'>3 '# 5467'7*(Ou))Hn.Mw834OMx43N)gA\*g>}66]C_56`?`q{&/=5!&'&#"5>3267632#"'&'#"'&732767276'&#"qN ffjbdjQGhi񈉉ijBN℄RR\]VVUVVVZdc44*,nmn67윜78lkpĘZYWWsttstuq/u{ 4&#"#32/8qu/ 32653#"4/8`!264&#%!2#!#N[cc[H^^>2`!.54763!##"#676#";jpkla;;?î545w?@@?w iQP%$q2^66**TS++2`!&'&'3;3!"'&546#"37545â?;;a|lkp w?@@?wS66^2q$%PQicQ++ST**<m``$ 653 &53sXٹ};ML+%!5!2654&#!5!#TZ`fcL||BtN5353!5!2654&#!5!#Z`fcxzʤ||Dv/{&#!5!2654&#!5!27654'&#!5!#|vz{\MN`_`gb>> E__ru99wSS?yzVU=`YV5`ZX`]x`73264&+5%5!2 'Ӏ{n Fo}ɽBdd>Jm7{3!!I{/=`N`#!#`I``Z^`367653#5&'&3U9VˆmmV9S`1Ms,}},uMLs` h !3#'!#ZgVXVq`!!!!!5!#!.AeW"___DXI &327654'&#327654'&#%!2#!g1221g̼^-..-^EOO)(N^h+&&MO%%X@? ]65dL.- rUpz 327654'&#%! )[ZZ[vNONN]eefe !!!!!!R-@___S !5!!5!5!5@-_/__H~$5#5!#"'&547632&'&#"326NJYXe|}}|\SRFFPOWWVVWCj]/rssr'y5UVVUL 3!3#!#΀2Wr3# 3+53265A@1(TFDE`Tli 33 ##-<azBm3!!_ 33###|{9="G 33##|_{EEG ##3G|_{EDEH"327654'&$  '&RQQRQQQQwvvwtww[\\[[\\[\vvvvuvG>@"327654'&327654'&'52#"&54767&'&54763sCDDCstDCCBR65<%j<=0ER^X65`l<=ca==ll*6RI)++LK,++,KL++5##,&)$%LY+8:6iG2278PyAAyP87'21I.* 32764'&#%!2+#Y0110YQQQQ))))]?@@?[ #'&'&+#!232654&#=)&''y.,,LPO)*s\^^\$ )(GTD<32#"&'#3t4554455$pMPPPPMp$uuc@AA@@AA86Z[[Z68^gG3#5#"'&76322764'&"Jtt%78NPQQPN874555555S^8Z[([Z@AA@@AAG#!32767#"'&547632&'&#"@AsC?>>>BADbc^]SSt44Va:: 2j88a WW[ZQRmT3210XGMK SX@ 2KSKQZKT[X888Y1@   /0Y5!.#"5>32#"&73267GsC}>?CŻthVau2koamTebXTb2&'&547632.#";#"32767#"&5476G&%HG{065>=f,K,,+*Ib]W-155_;65-9553+,$$4O,, ^$'U13 `fa<))R`1#"'&'532654'&+5327654'&#"5>32FLHG{065>=23-KX+*Ib]V.156_:65-9j2RQ,+ H4O-+]4$'U 12  `33a<))G 14'&#"327#"'&'53276=#"'&763253J44^]4444]^4PP=7633223r99$88NOPPON88$tm=>>==>>FNO e 45k37XX"XX7_z3#53ztttu 33 ##uuZu2u{"4@ $ #32>32#4&#"tHKYhuu'oMLl+yRowtHJZiw[Wk\sa97EBEB~wZXku4@ zx66X6VYYk\sa8BDG 6@ KSKQZKT[X 88Y1@ /0"32654&'2#"&546]ml^]ll]ǁqqpoWGu 67632#"'&'532764'&#"G0336^_]^:5311213p?>>?p3121 XXYY _ ?@@? G4'&"#46320T6667zWVoBAA@qWWG27653#"'&506667zVWoBAA@qWWu#3>32#"&$4'&"27uu$pMPPPPMpf4554455b_86Z[[Z6@AA@@AA#3#;#"'&5#5350Hww33UUPM,V-,vTPn3327653#5#"&nt''N^67tt+78Jy~{Y,-65\c`9nA!5!27654'&#!5!#Ue22<KLg#"FS10gg%dAl88u{(#"&53327653327653#5#"&Q+<=Rnxu$$IZ54t$$KY45tt(78LMlE!"z[+,64\c[+,66Zcb;F&33#&{{y #! !&'3254554#"t nυ9F}攥^ؙ83a _{3#5&+532{t<,||GXG+&#" '&54767&54!232654'&'&yAJZVWVWW!/bL+"766^]l9=P(r(B4?KWXXWr]$,O'(@?Ajp69G  )"27654'&'2##5"'&5476734 )=;67-!XQVVQs~SVV@h)%661FQ:5}t?3XJOZUUXR=\ ,Ajq@:%'#&+53;'&^sa,(^ra,GX]:DFYzg Oduudnsd&sdyodsdy67632#"&'#44&#"326&_%sNo%ti\[jj[\i92ض78"{qqrG xd%tdV{(!2.#">32#"&'#32654&#"aQQR9||9F,*[cbbc#Lt`5!#3#3!53#53t𰰰त T2V${"+%##533>323##"&!3276!&'&#"s:{ˀf t{7JTTJf>TT>̪daÐꕢafttf>VttVV/%+53276'7#3/F0j&*06G#367632#"'&$4'&"27tt%87NPQQPN78f5455554_s^8Z[[ZA@@AA@@Gu&'&#"32767#"&54632u1122q>??>q22110h;533` @??@ _ GKu+325&#"47&'&54632&'&#"632#"Z%0\R@5`$^4412/412q>??5{3 * &;/Z ` ?@@biG.&'&#"32654'&7#"&54632''7'37 i:;n\[nO$$ZY drP =67Tb1#"'&'5327654'&+532654'&#"5>32N+,QR2658-56:_651.V]aIV-+K-32==l/|GHL ))unn77wU:8P#P,i/0\+53276=#533343r,Brrtn x66XU P#PG ,5#"3276#"'&'53276=#"'&54763J]4444]^44tPP=7633223r99$88NOPPO>==>>=۠NO e 45k37XXXXn3327653##"&nt''N^67tt+87Jy~{Y,-65\cO9I 5333##53#Irtggttt\\jz~ ;#"&5C,rfpUWlwI 5!#3!53IMjjo\\E\\I5!#3#3!535#535IMjjjjooo\\\\\\V`3#"54;33#'#"3276ztteztry "3rKNB ,|ssW?#5$ z~3;#"&5ztC,rfSVXlx[`+53276'7#3`34r,Bttax66XS gq3!!q_u{467632+53265&7454&#"#4'&#"#367632+=32#4'&#"43r,B0t*pJz>?t'(N^66x66X6V~a88BDwY,-56\uU 4'&#"#367632;#"'&5P''N^66uu)89Jy?>0B,r34Y,-56\sa8BDzV6X66xq 33##q-{{~G 2#"'&5476"!&'!3276WVVWUWWU6//1w &6^]6&WWWXXWWWW@9\[8E-AA.G&.#5!#3!535&'&5476767654'&OpFVVFp^nCWWCnt6%66%4#76$\\FWWG\\FWWE[*,ApoA-9*A@+Fa:.#"#"/;#"'&=32654'&/.547632;1j8W*,]({44MN9> 0Br34@?>=RX l)k`GF@rb/$+*MW33 V6X66x"j2-*TIX00476;#"+5326z73zno>43r,B0]Me30U:Jx66X6#3#;+5326=#"'&5#5350Hw43r,B033UUPM,ax66X6V -,vTP^!533!33##5#"&=)3276^ntgtuut+87Jy~''N^61\\`9Y,-6/G&5!327654'&'5!# '&54767GE()78Z[78*,?G$"ZYYZ!"J\{':?KY7667YR8>#{\8?>LRRQRR<=:u2653#"'&53QHuDEEDuHPZs{>??>{}ZPz3+"&53?27654'&'&gH#"YZ,rftA Z87)2:08?>LRRlwpU67YQ8C&# #3{{ s7n !!!5!G'L\^=R^7!!#;#"&=!5!G'LC,rf>\^=R VXlx ^7^n#47#5!5!3632#'3254#|`\'Ln& m,7!!^R^=jR37!2#"'&'5327654'&+5!5!hCQ>63``;??C5~Ex>?::hn\& =;M|CD m**PJ*)]R^G !32767&'&"2#"&76So/6^]6/ +66,ǗWVVWVV*MWXMmGYXFovw^wwwv[f!5!73[f3!Px[f#'!5f[f!!#PU騋fBf 3#'#35fxBf 73#'#˴fxh'${-{'TDP'*u%R'>E&%&E&%&Esu'l'#Lvquf&vCP'*u'qZ'uG''qZ'fG''qZ'fGw&'z[quZ&Gz''qZ'fGZ&([q^'[HZ&(Zq^&HZK&(7qK{&H7v&(qv{&Hum')u&(zquH&H'zK#O'*vt)/'uIs&*2"qVZ&JI;N'*s+dR'>K;'+d'K;P&+j@dN'>Kt;&+ztd&Kz9;&+ 9d&Kv&,Jvg'LYZ&,XtF&Xajl'#v.l'#ZvNj&.&Nj&. &Nvj'/''O jk'*u'/S1'q(; 5j&/J'Oj'&/\'&Ol'#sv0f&PvO'*wt0'P't0{'P3P'*u1d'Q3'1d{'Q3&1d{&Q3'&1d{'&QsZ&2`fqu &R`sV&2^lqu&R'jo$rsZ&2[jqu^&R[sZ&2Zhqu^'ZRl'#v3Vf&Sv2O'*t3V'STN'*s5J'UT'}5J{' UT1'q}; XJ&q YT&5TJ{&UO'*t6o''V'6o{'%Vm'#v'*6of&V&VvW&_6o'-#O'*t `o'' aO'*rt77'uW'q77'W&7b7&W'r&77''&W)'8X{'{Xv)&8vX{&XK)&87KX{&Xu7)Z&`.8X&+v)4&V28X'VXh}&9F=7&Ymh&9=`&Y^Dr'%|:V5k'C ZDr'#|:V5m'vZDN'j>:V5'jEZDN'*s:V5'GZD&:V5`&ZJ=;O'*s;;y'b[=;N&;j>;y&[jfP'*ru<=V'`\\m'&u=Xf&]\&=X`&]1\&=X`&]d&KfN&Wj->V5&ZB=V&\{a&D/'uA!#'7#53546;#"7Jݰd&&KhjN()gti/!##535#53676;#"3# GWd&EFV( D7&#"#4>32"#"'532654.546m@f_@&9dc07CjjCӴmob)F[dd[F)Z@hoϋ\(Ž}_-C-->T\_EFvX5P3) $2BgCquIh'${-{'!Dh&$u{-{&DTh:&$a{'aDh:&$b{-&Dbh[&$h{'hDhu&$c{-'cDhm&{-f&"hZ&$e{-'eDhZ&$f{-'fDh&$g{-5'gDhY&$d{-&Ddh&{-&3&(q{&H&(uq{&H^'$u(q7'H:&(aq'aH:&(bq'bH[&(hq&Hhu&(cq'cHm&qf'& Z&,#uD|& &,.y&Ls&2'qu{&Rss&2'uqu{&R}s:&2alq'aRs:&2bjqu'bRs[&2hjq'hRsu&2cequ'cRsm&'quf's& sgk'#'ubvf&vscgk'%'ubvf&Cscg&b'uv{&c}g^'$'ubv7&scg&b'v&cs)&8X{&X{)&8uX{&X}_k'#uqif&v{r_k'%uqif&C{r_&qui{&r}_^'$uqi7'r_&qi&r{r&<%r|=Vk&\C!'v<=V`'t\&<r|=V&\`^'$ru<=V7&w\ 333!!+ժ 33533#####53มม}}qa&F pqa&F Hqf&F qf&F qf&F qf&F qm&F vqm&F Dha&' #ha&' f'' |f'' Ĉf'' SXf'' om&'1 Qm&'x Na&J Da&J 9f&J f&J %f&J Of&J R-a'+ -a'+ 7f'+ |If'+ Ĉf'+" Sf'+^ oVda&L Vda&L Vdf&L Vdf&L pVdf&L Vdf&L Vdm&L Vdm&L a'- a'- f'- |f'- Ĉnf'-3 Sf'-d om'-t Qm'- Nna&N na&N f&N 'f&N <f&N Qf&N =nm&N nm&N Aa'/ 5a'/ Kf'/ |Kf'/ Ĉf'/4 Sf'/p o"m'/ Q)m'/ Nqua&T xqua&T nquf&T equf&T Tquf&T quf&T a&5# Va&5} Of'5v |Yf'5 Ĉf'56 SPf'5w o*a&Z =*a&Z *f&Z '*f&Z !*f&Z `*f&Z W*m&Z 8*m&Z Ia':b f': Ĉf':6 o3m':L N'a&^ ^'a&^ T'f&^ Y'f&^ ^'f&^ 'f&^ 'm&^ c'm&^ ^a&>N qa'> if'> |uf'> ĈCf'>t Syf'> om'>B QPm'> Nqf&F tqfAf&J TfBVdf&L VdfCnf&N fDquf&T {qufa*f&Z 0*fb'f&^ M'fcqVa& HqVa& HqVf& HqVf& HqVf& HqVf& HqVm& HqVm& HVha&  Vha&  Vf&  FVf&  FVf&  hVXf&  Vm&  Vm&  2Vda& 8Vda& 8Vdf& 8Vdf& 8Vdf& 8Vdf& 8Vdm& 8Vdm& 8Va&  Va&  Vf&  Vf& ! Vnf& " #Vf& # TVm& $ dVm& % V'a& NYV'a& OYV'f& PYV'f& QYV'f& RYV'f& SYV'm& TYV'm& UYVa& V \Vqa& W Vif& X Vuf& Y VCf& Z Vyf& [ Vm& \ PVPm& ] qH&Fzq&FqyqVf& ^HqVy&FHqVf&AHq7&F nqV7& Hhm&')uh1&'q;f&'B RhfVh&' xa VxaH <ܲ?]1 Դ?_]KPXY̲?]90IIPX@@88Y#55#53xgJ7FJm'$jVdf& b8Vd{&L8Vdf&C8Vd7&L Vd7& 8f'+b Ruf f'-n Rf!V;&- f'   f' . BJm'$ nH&N$n&Nqn&N .&x7&N .zm&N 0gm&/).uY1&/q.;f'/q R}f"~f'  f'  _Jm'$ *H&Z'*&Zq$*&Z *EVa&V Va&V *7&Z '*m&Z m&:)vu1&:q;f': Rf$5a'7 F)&j lFRfCV'f& jYV'`&^YV'f&cY'7&^ OV'7& Yf'5; Rf#f'>D Rf%NV&> sRfvxaH ܲ?]<1 Դ?_]KPXY̲?]90IIPX@@88Y53#7"͔gd10!!dd dy/10!!dOydy/10!!d8ydy/10!!d8yy/10!!y&__J&BBB@ 10#53ӤR?@ 103#ӤR՘?@ 10%3#ӤR@#5R՘?m '@   1<20#53#53ӤRӤR??m '@   1<203#%3#ӤRӤRլ@@m '@    1<20%3#%3#ӤRfӤR@@m #5!#5RmRխ??9; '@  YW Y <<1<203!!#!5!oo\]9;>@   Y W Y <<2<<2122220%!#!5!!5!3!!!oooo\\3!   \ 104632#"&3~|}}||}3q31/073#k1/<20%3#%3#V #@   1/<<220%3#%3#%3#ki3#iq L #'3?K@D$%&%&'$'B@ .(F4 :&$L%IC'1+C =  1 =I 7+ ! L9912<<2220KSXY"KTK T[K T[K T[K T[KT[XL@LL878Y"32654&'2#"&5462#"&546!3#"32654&2#"&546"32654&WddWUccUt%ZVcbWWcdWccWUccܻۻۻۼܻۻ q r "-7;EP\"32654&'2#"&546"32654&'2#"&546  &54%3#"26542#"&546"32654& WddWUccUyWddWUccU<¹ߠZucbcNWccWUccۻۻۻۼ5ۻ(`3(`u(`&  ,(`' ,&  X(`#3W`u(`&  ,(`& ' X , #'#Rs#G@%Bon29190KSXY" 5s-+#R#I@&Bop<9190KSXY"5 +-#^R^  &K'N''=NO'^O$#5>323#7>54'&L Za^gHZX/'-93A% #C98ŸLVV/5<4BR-5^1Y7| B_ % ij991@  <202$7#"$'56:<hh~vvuw~ign % ij991@  <202&$#"56$6;>nvv~hhgi~wuI3 # #bbc$$v=' {' { 3_!!V_+@B10KSXY"3#-\X 3!!#3hX^#"#JX 53#5!!53X^JݏޏJ&""gJ&"JJ'^"d] 7 91@ B  <20KSXY327# 'du](; 2###׎辸( 3+"&5463yv}~}|( ';2+v~}O|}=k {B# #5#5R#۬@n& " #=o'  BC''Hd1#"'&'&'&#"5>32326撔 錄ܔ撰 錂1OD;>MSOE<>L~ #8| #'7!5!'737!!qaqqaq)`rrbqr2 535353,(`$' ,& '  XfN 53!535353fXp fN 5353535353,p  3#3#'d 3#%3#3#3#dipD %53535353#!5!3!,|f  feP> 3#3#3#>w 3#3#3#3#W "27654/2#"&5462332233VVVVVVVz@ <<1@03#3#zttttg? @   ] <291<290KTKT[KT[KT[K T[K T[X@878YKTKT[X@878Y@T /9IFYi       "5GK S[ e]] !33##5!55bf]myf !!67632#"&'53264&#"y^^a`<~B9>>Eoo4h6_ MLKJq ff\/"327654'&&'&#"67632#"&547632X3333XW33331221DD &9:DTTXWll122m45[Z4554Z[54bg KL1LMONuv l!#!liH30Y *:"32764'%&'&546 #"'&54767327654'&#"55j]\655T./RQ./SZ85UVUV56-/.UQ100/SS0/*,+KLV,++]12Hdt::dJ01:7PyAAAAyN98?&%%$A?&%%$S.532767#"&547632#"'&2654'&#"1220DC #<9EWXWXkl122Xf33XU5443g KK/MNoouv rh\Z4554Z\44k !!#!5!Q_i_k_8_83!!'3_a!!!!''^_o #&'&4767TRRTe^///._~g3#676'&ge_/../_eT)**)~~~u0@ 32tNN^luu)qJy}wYYk\sa88WT NdC{d^TtdbTud?C PdfC Qd\T RdlC SdYT TdST Ud Vd8 Wd  Xdoif Ydgif ZdMdGdGdu!sdGdugrdugdzgdu{du [dudud#%dV##"32.#"3267!!!!!!Oc%eNLbbL:/667756GFDFG ks9'.473&'3267#"'#7&'#7&'&76%73&'hA>/(%:@w]ayA9&AX}R4>C5Ai<)^_HH?WghйKp(`,%6767# !2.#"3>32.#".aXj]aye6{_]w|^0n&<$'/_HGghGG_^ٜu]\Y!!!!3###5qZpP~WHE9Eb#!!53#535#535632.#"!!!5-쿿=OL=tyB_))HB+#&'&#"#3676323632#4&#"#̪m49wSS>YXyzU6%X\xruxGM_a`f21>&>E3\""&)''#!333#3#!###535#53355P8ĢĢ8PP4&{{&&{{{ P32654&#+#!233!!;532654&/.54632.#"#"&'5#"&5qzzWQeGl`[z_<`HJU];Ufɘ/ϒjqqR>N#55YQKP%$((TT@I!*##`3E326&##.+#! 32654&/.54632.#"#"'&ٿJx}A{>[b`cae@fLNZb?ĥZa,/b؍$~3YQKP%$((TT@I!*;"&)-1'#53'3!73!733#3#####5!73'!!7]:1000019]zu }Luuguuguuuu_ % #4&#!#)"33!3_SV*$oNq&1@: "+ /) 2+"!)#&  , & &*!/<29999999999122<20K TK T[K T[KT[KT[KT[X222@878Y@z  1Ti lnooooiko o!o"o#n$l%i'i-  !"#$%&'()*+,-2   USjg ]].#"!!!!3267#"#734&5465#7332[f A78 ʝf[Y`(77(6bbiZȻ{.# .{ZiHH"{/ #/{"G(33!!###5uX_Tws1s!5!!77#'%5'&PPM4Mo؈onوn9 -bw'67>32#"'&'"326767654'&'&67'>7632#"'.'&/#"'&54632326767654'&'&&#"32">1aJ{%A01Q[W7>/W1   >$<  . #dCw-^URB$`>DL_K>.3b @N\uLMiI(S395l9,8G(/&  -9)ЗiRm:3Xwdg7? 2j7#=5(6$ 629T/ (2M !:5S}$@{mbq~Es/4 -& "TAB`]|@8nRkcd]aC".)5'632327&547632#527654'#"'&#"%654'&#"o|@X"07PYtaTk~j[IwmqJ2530D#24!`NkBX``S㫣†qJ323!!!3267# $547#5\J5 ;_srigCS1r{jJ,{ +kv67&&UB{\* {;^~FE/0K?{r*.#267#&'&576753r\ee\Z\X[dtye]X\[CvlCiZZiH$"v9Bt"$CuflC !!!!#!ժx j&7!!!##&'&+532767!7!&'&#j77O57=A}A=;AٿKDFxJD7-JZ{{N{~U]$HDh01C>r{C4X !#'%5'%3772hN4L4PP~n؉noوo.C!2+!!##535#5;2654&+ *⦦ 3!~d=!5!'3 G~d=z!#'73!5~~͛=z5!'3#7=~~d͛{ 3#%3#%3#yfP{ 3#%3#%3#%3#ky)=z #'73!'3#7~~<~~͛͛C $(B"326=7#5#"&54634&#"5>32%3#.#"3267#"&54632pSHfmƩogDc\GD^o8yy8o^IICBRCI M >OW\ 7$44"C +EI.46'&#"#&'53254&'"326=7#5#"&54634&#"5>32%3#VNz$p;i0ʪ%={pSHfmƩogDc\GD}|49d$, !5Lf,1BRCI M >OW\ 7$s'!.#"3267# !2'Y藣yyYjzS #bvAZ4-4ZBuHHghG[!!m&r&F+,/-/ܸܸ,(и(/A&6FVfv ]A] и ии# /!"+!0153&'&'6767!!5&'&76wI3cc3I86QLNN7887NNMR48_ki:rq;zn #++$ * rn<(2.#"3267#"&54632%3#"326&$  &54^o8yy8o^IICDkavva`ww~44"K <M-1332653#5#"&.#"3267#"&54632%3#\QPcu`^o8yy8o^IICDLriuD P44"K{Ro#&&r)Io!6767632#"'&#"32767#"'&'&547!#"'&54632327676"#"'&'&54767632l(9BKc{=&%%03!((!,739%7`lG;7 25]hB4,'5  'B[QF$%]c'G  %! }Kr~,1ьIg)*!&!(D;w},75;!_']7:y}[Ϟ\@4>#,!, 'QFj(JG4$$,*)/9yK#%P73276767654'&'&#"&'&"'632654'&'&54767767#"'&'672#"*i(X%# 1FSE/ O.55FuPU[QF[00rl~"KI}!;IFs;n;_T^͌Q79}w^l.Gyr\[4O9%#i#^MX;yv@c}e.ID\7I;>2V秉uӰ3!3%!!!!!!nnq  dx+%H#>54&#"#3>32u j_ y/wFx \/HT^Ȧ^m$RZ3%632##"#'7-P4-> {|a\=BcL;t9#"'&5476323276765"#"'&54767632thn<7# ;KQ>!|Za,4(XM!},‚<7D9#7.M=.1?@ '(MXI(' jF!2?632327654'&54?#"'&#"632327#"&#"jou9!ydG>PPPP5ʺ68^nm{z}}ȋo֏zZ'PVaK~pmdykb^OP681/::b:DnJ327654'7#"'&'$#5"'47676766767632#"'&'&'&#"32nZS_n0VBRny#HB?X!$9BMw>7l. ;7%,;(ӧuy,D0&3273#"'#67&5477632654#0)W:K32#"&'####53&  O:{{:ܧ$}daad}j %# !3!# dX0dd q+6+/BB/,/<-ݰ.<-ް#? < # 9 FhH)##Ii;BB=#IbiF`FaC`#BC`CUXC`C8Y& <BB00<İ< 6< <9 FhH #Ih; < ְ ݰ,9, FhH &ְ& #Ii;/,#Ih:1#IC`#BC`CPX& ,/C`C8K RX #IC`#BC`C@PXC`C@aC`#B C`C8YYYBB=#IbiF`FaC`#BC`CUXC`C8Y#)<BB1#IRX   <  < Y3525!463"!4632#"&732654&#"5!6jgggg92299229k̀k@4nNggNNggD{{ "-! ! ! ! '32654&#%!2+# JR12)uyӲckkc?L00ey wXQPXdn;C0<67632#"'67327654'&#"#"'&57&547276545[ۄFIyeL )qz]E& JEYq:?.蔁0.A ƂMkeLPק<+(h|H=y|n=B {u.F/4_NT 33!27&#%!2+!67654'&,d.@nX<-]\,q jdZ)VV)s!)%#'# ! % 7& 676'&B 3y;:x+lllli$ #ab[ 2222jT%%5$c$B2 _327654'&'&'#"'&5476323276765""'&5476!6?232767#"'&B=]iS\ZV30Fn7;#FfS9!!< #5,h";<2XngZR{,##9>;K!QIag£S D5@7*'S:y}*7H0 5#!,Il @3Xnh0{(2r:=OSlIX&54'&#"#"'&527654'&#"3"'&547632763227767654'&#"R(O*\xggfg-.@@?@@?\QA@@@S6fggfeӻp/$~AB}:1$ -*MJJ@f[+8vuuv zVWWWXWWVVW\uvuuu# bW1W{|^1$h{vC[SK\GChfy /2 &.2&'&+3!.+!! !27&#676'&%3LDEx-Me5q>HJxnu1EA+ZY*01/O~hbb)j)V>U)-  /!/ и/ ܸи!ܸA]A)9IYiy ] и /9 ///+ +0132654&#+#!273 # #s sNCI/ϒ_6۬kk%T$+.3&##&'&''7#!27%7 67654#?\A>:AٿKE6ToF^~_ ,8~|T3Jۏ/HDh0& ,ok؍]-Dbg('4.#"#"&'532654&/.54632733###UW'AG/E8pi4sG[d/EK7?8pc|3iиY"*/( VAO[`*,2,* M=H\T(l0`!!#!!!!!!!3!!rso+` `ffff'F >@!    b b cbc91<<2<<903#######5Jq7rqr/B^^"h %73# ' 3,o-MoF+,\ %#!!!5!8kO8d qddd XL/ 654&#!5!5!5!!2!"'X $''ߦԧc̆eeaԊfJN>NsDU767654'&#"#"'&5733272632632!"'4'&'&#"'6763232767654'&'&#"_}yj#1Q\$####,TGG\n#?QY>kDM4giMqE#"'&'&5476?&'&547632#"'&547654'&#"3"32767'_ilE_ml=Oc{T3-2") %+fa@aP/Z_|{w:maZu> IhA"%@_l$=PczS2VN-2!$+%$+@e}N069na[u>_T M#"'&'!#!"'&547632327676=!7!&#"#"'&5476!27327#X':'7?<=**M_4. B^l{>!'Ba>nG#&#w4$B00!K=DcK_4B( 03B{>ceDInFT=I,Fw7K. 0# )5!!5!3#Pʪ9Bk32767"'&'&47'&'&'#"'&547632326765&#"6767632377632#"'&'&'&#",5(.'*'E`97y{7a;f7;>F3.^PeMD*#7@,j!HhH<=.%_yipp3 T}B',$ *5܀/,,@!;Da97TVM;nwF^O?/,%!;>jytX<;}f?E'_n H''#  .hJ) 4&#"322#"&54WOmVPm˜ݢt}t{أأg4 4'+5654/&4?'&547 '&5474/c2>Bd=VE/b5c2ltc2c2uc1LS2?Bd,>8?]/c6c1LS2tc1LS2c1LS2903#!".54?>3!4'.#!".54>323!2O,""$%@;5H *Y[#$"x2 1[G(  WA,!2#"&/#!"54?>3!!"&5462TPl 0%= -d,mF"$mG- .7#*(/ $"Sae(!q~B;V&!"&54>323!2#"&'&5 mG * 5G 0%9 . q~( 0 (/ &Js!S'DQIF 4632#"&3!53#5!pQOooOQpoTQooQOonuyy5yZR; ! ! ! ! HH#[[breH !#y;:x L`  !!!!#!3#'!#33 # #DjwZDZ֏R``C5MR.}$z`-1%5"'&'&5#2327#"'&5#!#"#463!#3#, 9Yl(Ht*=Z2dr!Z4@'!8 ֦zEB bLs{dYsZ{3#"#4763 3׮UEEl4FũdGQnCF\xB*WbOZ=0 3%!!,:*nq dd3!3!!!! nn8q  qwS ! ! !!5 5Y*dccS!!6$3 !"$'53 !"kJu^uopkoSUggHF_`2/.2%:1/0!#!5!)+:1/0!5!_++!# #3bef9WJ " )327&#!3676654'&|tK"P"coAfյ|cv~dAA xPfUmZ #2!7#"547632!3 32767654'&#"* 6B8wx!Nbb|˞"#>|OO'vN 2wx87tKsO=  =d01 PD10d^dTd6Jthi[{ (232767# '&5477632!7!654'&#" N&#G_yZ\klmk}Z5fF 9NJC0<7h:J(u*oDMcFPZd82vRsO 3#3#!!ɸ.Ԇ$N9`V 3##676#732767!ɸ.fʆ#5H2K1i0/N)deеT0Hd01``;&0 #473>32#"&'532654&7>54&#";Ht]h202޸SUWDi;2[UԠ_I@Yr~YW׀c?}<$$/1oX3gQX?@Q` $@   F 21@/0!5!!5!`o`' '5&{ Sdt' '5&{ Ud ' '5&{'{d NdX&{' '5ud^X&t' '5ud^&{' '5 Qd^^&t' '5 Qdb^&u' '5 Qd?^& P' '5 Qd~&{' '5 Rdf~& Q' '5 Rdw&{' '5 Tdbw&u' '5 Tdfw& Q' '5 Tdlw& S' '5 Td&{ '5,'&,,&,',,(Q&,9h9&9,,&9',, &9',',,-&,;=;;=&;,=B&;',,j/s'&'0yL&LLpY&L'LpLA&LY=`Y=&YLD=-&Y'LDL=&Y'LD'LL$J&L[;y`[;&[L[;D&['L[LyOq{FqZG{Py }  ) !3 !## !5hPPh55~ji.,w# + ++ A]A)9IYiy ] A]A)9IYiy ]%"+++ + 013 !#3 #32654&#! )5HHNhPaY.,职~y }(1C3 +3 !32654&+! ) #"35# !35#"&546!`HH5NNPhthNN5H/ó., ji~s'H{sV'.# !267## !2'ff vzSb_^^_$ghGWX' '5'ud Nd?8   2@ @@ 00 ]1@   990@   <<@ <<KSX << Y5!!dx=xUZxx @   991  2@ OO ?? ]0@   <<@ <<KSX << Y3'#'-Zxxvx<xuP8   2@ OO __ ]1@  990@   <<@ <<KSX << Y'7!5!'7Pwx=xZwxx @  991  2@ @@ PP ]0@   <<@ <<KSX << Y#737Zvxxx76767632&'&'&#"#"'&/#7!#/)85,0F"<;NJX[GR7<"#!2)85,/$#?2WG[XJN;?,!F0O<:" %7xxUZxaxxaxuP8 '7!' 7!'7Pwxx>xaxUwxx>>xxwd?8 !5!3#xwx-xZxY %'3'!!5xZxZxvx檪uP8 22@ O O _ _ ]1@   990@   <<@ <<KSX  <<  Y!#3!'7'8窪xwx-\xwZwx !5!!7#7\xxZxx+xvx7!!5!7'3'xxxxxZxxvxxvxd>%52#!5! 767>54&'&'&>42/+-+-':1 Hxwxܪ-)o=  xwZwx(.46<=69)-d>>3276767654'&'&'&"5476767632+#5!5 6 +/24>A1:'-+/24>xwx  =69)-(.46=<69)-xZxvP>54'&'&'&"3)'7'7!#5#"'&'&'&5476767632# 6 +lxwx>42/+-':1A>42/+ׂ  xwZwx-)96<=64.(-)96=dP8X#532267676767632267676;'7'7#""'&'&'&'&'&""'&'&'& xwx 0$#$   "%'-0$' !  ' '- xwx  ('Z&("  "(&Z'( -xZx$ -#%"&* 'xwZwx ""&*  *&"" dPF%'!5!!'7'7!pxwxpdxwx^:5xZxo:xwZwx* %'7 !^ b9YXxbZ  #!5 xwxoxZx[ !'7'7!#xwxxwZwxZ  !5!3 ixwxDxZx[ 3!'7'7xwxDxwZwx 7#7!5xwZwx=xwxd? !5!3?=xwx-xZx,-eX&7#754767676 #4&'&'&"9xxZvx.-\ZnllnZ\-.BB54'&/#7!!#"'&'&'&54767D !BB54'&x\-..0YXplgtTY0../Z#,@#B"!BB@RNJV]xwx]TQ>]xwx]xLii `iiT4]xZx]4]xwZwx]JiiiiuP8!7'!7!5!7!'7'7!'7!5giiyYuI0]xwx]uIiixK]xwZwx]Kxd?8!!5!!]xwx]7Qix]xZx]xi#'3'#'x\xZx^xhP8^xvx^huP87'!5!'7'7!5$iiQ7]xwx]iix]xwZwx]x737#73jhx^xvZxx\x%hh^xvx^8dP8!7'!!5!'7'iili\]xwx]]xwxiii]xZx]]xwZwx7''3'7#7iii]xZx]]xwZwxliii{]xwx]\]xwx  #7!##PU?,UvU,?UP5#'#5!#5'U,?UvU?ԄU4 753!5373U?ԃUPqPU?U 433!'3ɕPU?UqPU?,Ud?8!!!!5!!c$R&xwxxxxZxxuP8!5!'!5!7'!5!Q$܊xwx&RFxxxwZwxxd?8#''''#53777?(FncxwxFn-FnxZxFnuP8577773'7'7#'''unFxwxcnF-nFxwZwxnF3'!!!!#!5!5!5!'-Zx((ت&&xvxTrx#7!5!5!5!3!!!!7Zxx((&&xxrTxd?8 5!!5!35!dxqxUZxxa 3'#'3#3#-ZxxbvxrxVuP8  '7!5!'7%!#'#5PwxqxUwxxw( 737533-vxxvxrxv4k?9 !#3?xvxתx~\xuI9 !'73#'7!uxvxxvvx7?~ 5!! !!  d }*^V 3! !!d}*p  d HP~ !! !!    ^V #!# !!!d e n ^V !! !3 3!!!E*dr*r$| \d^V )3! !3#!5#3 3 ȃ\Pdx @t %#!5#3'!3!3! !33'ȡdxd:tZdd\nt^V%#!3!3! !3!5#3ĹtIt\Px^V%3 3!!! !!3 37r*kd d| ^V %#!5#3 3!3!! !!33 37ȃ:͊` \h u}~ 7!! !5#35! u\Pdx f:bȃ  zM!#7!!#Mc"?,^xc?x^zM35!3!5!73zpc?Jx^cr+a?^xJ^V 3 3# '! !! !  e   dCuP8)5A '7!"'&'&'&'#5367676762!'7$"!&'&'!27676Pwx 21@=:C.2  21@=:C.2 _x_R#)l$h$#R#$Uwx@21.2@@21.2@xw#w;' , utP'7!5!'7!5!'7!5!'7Pwx===xUZwxתתxwZd?D5!3!!#!dx3xUZxmmxuPD '7!#!5!3!'7Pwxͪ3xUwxmmxwdPD3!'7'7!#!5xwxwwxwxmxwZwxmxZxd?D5!333!!###!dx⪪YxUZxmmmmxuPD '7!###!5!333!'7PwxYxUwxmmmmxwdPD333!'7'7!###!5d xwxdxwxmmxwZwxmmxZx7?@  !JBJAu}@ 7'!5! PJBł}BB7}@7'! ! 6BB A}BBh %!3!3۠ՈR+nm+A&6FVfv ]A]+ +0132#&'&#"327673#" B!OO!BzcI7͙7Ic_L 0"'&547632654'&#"563 3276767&#"\m`cu\6% GGnth r5?,/H@3H5,Y:$UeI+HQ\N,tqzSd69->eSY׮l !5!!5!!5>+5!#7#53!5!!5!733!Kcd04+^^``k](673#"'&'#7&'&$32 '&#" 32$767&'&YjiEd80~i?/c`RQQ$g'-"SRR:;nSz_'BTc_ N@DROg`8@91/90@cmpxyvn]] !3!^DC?`%! !3f<?I!!"$54$3!!!W?JGcGK@ sJxNL``ȟMOx]I&/!!!!3!!"''&'&54$;7#"ؖI$$$GA?d`,,cFU;}YI7ʟ 7c``JxH NGx]g% $54$)!!3!+*(FiNv%FrO:0QI&'&'&'!5!2#!5!676767!5?JGcGK@ 'JxNLȟMOx]I&/'7!5!!5!&#!5!2+4'&'&'3276765 I^Q$$GA?d`,,#FT;}YI7ʟ 7c;JxH HNGx]g )5%2767!5&'&!5(*FiNv%FtFgP:1R, //01!!,wq@gg120!#!# }wq@gg1<03!3wJ}w; ]@    91990@0QVPZ spvupz  Z pp{ t  ]]!! !!5 7AJI3!-10!!ת !#!5!3!!5!--+}ת W+и и и / + +и 01!!#!5!3#-Ө-5B<%?P%73% %#'TUUTUTTUDGrXY %=} *@    91903##'%\sB}}`s-Pb;=v& us=e&  Ps 127#"#"'&'&'#"'&547632676;#"3cd3668+MI6641C;ItY^^SI6?+((C;ItK@tkHMfpEF?$Tx5@ejre!93Ex5@#/;&'#"'&54763267632#"'&%27#""327654'&1C;JsY^^TI6?+((C;JsY^^TI666cd3778s~d3778]$Tx5@ejre!93Ex5@ejreMHMfpEFHMfpEFI%!3!~,I%!3IfIA//+к99к901%&'&'3!!#4'!&'7`'JAW`LqR]+X* Pʋs^(Rs57756u5 +  // 9 9 901 7&'7%%'6 676r{EG%y44RW!L!$Ҿ &!L {JP+3#+fJ+ 7+и//9 90137#'PMVo)gnJ+3#3#@+fJ+{//и/ܸи ܸܸ и и// // 9 9 9 9013737##'[P]ME+qd @oxpAn!3# ih^T3 3##"T^32#4&#"#P(*7332653#"RP7*uM>2&#""&'7327~9GA~9G⧅}}uM&  %uM& ' % JuM-6?67632&#"#"'&'7327&'&5476767654'&'SOJMG79GcBnnVsSOJMG79G]InoSu=,EG%,=,HK%DAF7K|oUDAF71IosV/HgjG$4.JhgH$uMMQZc67632&#"!67632&#"#"'&'7327!#"'&'7327&'&54767!!67654'&SOJMG79G~SOJMG79GcBnnVsSOJMG79GSOJMG79G]InoSu~=,HK% =,EG%DAF77DAF7K|oUDAF7$çDAF70IosV!.JhgH$+/HgjG$uMmqu~67632&#"!67632&#"!67632&#"#"'&'7327!#"'&'7327!#"'&'7327&'&54767!)!67654'&SOJMG79G~SOJMG79G~SOJMG79GcBnnVsSOJMG79GSOJMG79GSOJMG79G]InoSu,~=,HK%2=,EG%DAF77DAF77DAF7K|oUDAF7$çDAF7$çDAF70IosV!.JhgH$+/HgjG$uL.3&#"7#'754'&'#"&'7327#4767>32";EY?w^H6H\O3,,HO;E+@/VfmVmHO?u]HH]sM3 gz.VrmV_zuM<%4'>7'7&#"7"&'7327&'&54767>2=,HK%=Q Hl;EYLmHH7'&#"'"&'7327&'&54767>2=,HK%m#6,=iSH;EcHKs;E]InoSuJ.JghH$6B0+@TH?HK|z1IosV32326ian ^Xbian ^V2NE;=LTNE;=K23276767632.#"#"&'gV^ naibX^ nai2UK=;ENTL=;EN1).#"3".54>323265.#72#"&:QHRdhNi\dnx>@HRdhNi\dnx.ttlH=YOHL\}X[lH=YOHL\}W#"'"#322{dfftX{dfftX#*$0!#.5476767654&'30ND:323267#"''cDXbia]yeEVgia`yS LTNE+~F KUNE,F #"/&'&#"5>32326!!ian^Xbian ^VeoNE;=LTNE;=K`#"/&'&#"5>32326!!ian^Xbian^VeOE;=LSNE; =Kkb%&32767#"'!!'7!5!7&#"5>32%H\ iaBP﹉lZXbian3}o -X"OEd8LSNE;I"#"/&'&#"5>32326!!!!ian^Xbian^VeOE;=LSNE;?Kk˪.#"/&'&#"5>32326#5!7!5!7!!!!'ian^Xbian^VLoKɦoOE;=LSNE;?KL˪s˪sB.32767#"'!!!!'7#5!7!5!7'&#"5>327b K`Jqia'+\+zlh>Tm?u2^Xbianc"%]OE˪Nt˪=LSNE;%N;?@.9*-" *19" <-<<219999990#"'&'&'&#"5>32326#"'&'&'&#"5>32326ian ^Xbian ^Vgian ^Xbian ^VoNE;=LTNE;=KڲOE;=LSNE;=K43267#"'3267#"/'&#"5>327&#"5>29+Vgia@LJZVgia}9+Xbia@MHZXbi a KUOE8KUNE; @^ LTNE8LSNE;f@59#"/&'&#"5>32326#"/&'&#"5>32326!!ian^Xbian^Vgiaq^Xbian3VeLOE;=LSNE;?KҲOE;=LSNE;?Ky5P#"/&'&#"5>32326#"/&'&#"5>32326#"/&'&#"5>32326ian^Xbian^Vgian^Xbian^Vgiaq^Xbian3VײOE;=LSNE;?KҲOE;=LSNE;?KҲOE;=LSNE;?K"32?632.#"#"&'!5!5gV^naibX^naiUK?;ENSL=;EOȪ+  %5 % $%5$[g&Y%ZhӦ69%676767!!"'&'&'!5!!5!676762!!&'&'&[C-87VYYW6 8.CC.8d 6WYYV7 e8-,CE[<0[2332[39\DD+N+DD\93[2332[0<[EC,` !5!676762!!&'&'&!![C.8d 6WYYV7 e8-;++DD\93[2332[0<[EC,`' P ' P&  P' P&  P0' P&  P.62' P' P W63& ' P P` 3654'!!5!&547!5!!4434w~0IG00GG2?8>;_8` !!!!"264&'2#"&546HdddeH;k'**z{DbFE``bq+((d:svv`K!!!! &!56뗲`!!!! 3# $c'`!!!!33#$'c`!!!!!!'+]^*^]N䰰` !!!!!3!Np!NNf`07GO!!!!#"3###535463!3267#"&546324&#"'53#5#"&4632264&"?$mmC???DNB&H#$J'`qk[Q_C<17HBB@,I\\I,@p`ctiG6B?9i=$#tu#gSSS`*!!!!>32#4&#"#4&#"#3>32!]?U\Z79EPZ7:DPZZV:;S==:xoHOM]QHPL^P%U20=` ,!!!!3#7#546?>54&#"5>324eeb_--B6'Z0/`4\o$-,N2A+,/-7#!^aO&E++ '>@"     <291<2<<990!!!!!'7!5!7!}/H{};fըfӪL !@  <<<<10!!!!!!ת4!5!7!!!!!!'7!5!7!5!DQ"rn遙RoLT˪˪T˪  )@    <<10!!!!!!!!K T@.B $# <2291/90KSXY" 5 !!@po V@/B$ # <<291/90KSXY"55 !5AǪV 3!! 5 !!@poV !!555 !5BkǪ!5!7!5!7!!!!' 5'`ȉ)P"_=6@ss1stFpo!5!7!5!7!!!!'55'`ȉ)P"_=6ss1stF. 5 5:6:6pr pr . 55556:86:'!67&'&54767&'676'&'{)#Y4JJ4Y#))#Y4JJ4Y#)AAAAGF㞢GGGG➣FG2;;;<<;2;5$?$%5%67$'W eĔd?NĔ])]o& bR)`q% Rd%'%5% >zmzF<˶@6 o@hGp%5'75%7-孈m%˶C@ʴ@hGp/V !5!%5%%%!!'/xvH-rf5LOlUrC@=Vlь=/V%'!5!75%7%5!!' GWb[mmNL>ߪwe=ت=$%#"'&'&'&#"5>32326 5jbn ^Xbh`n ^Vg@ND:3232655jbn ^Xbh`n ^VfNF<>LTNF<>L>)P14%&#"5>32%5%%%3267#"'&'&/' k Xbh`'+kuE%sk ^Vhjbn "Pv1-LTND9ATj͊LTNF<= &TN#wf=J;N} 55 58@'poN} 5 55@'pom`!-%5%%%'5%%5 MM`ZDOA@FZDt@m*_TW&o}䎲w&-r~bUm`!7/%5%%'%5%75%Jvad",,V`bL"_D2,/*/&O{¸[&}P %5$r osaa^~||P 55%$so a||^a)W!%5%5$gV$}]]x|)W3%55%$Vg}$BW|]]RW(%#"'&'&'&#"5>32326%5$ian ^Xbian ^Vg$}NE;=LTNE;=K$]]x|RW(%#"'&'&'&#"5>3232655%$ian ^Xbian ^Ve}$NE;=LTNE;=K$|]]&%5$%67%'Et֋$k}uU)?eKtuu" K 9''567$'567&'%=⃹t֋~}uRU)?Kuu,ަK9'_%!"54763!!"3!슊@^`@ƍ^`_75!27654&#!5!2#@`^@Ȋʣ`^; #";3!!!!#"54763^`0rrndflppꊊ^`&pphƍ3 32654'&+ #!5!!5!32#^`0rrpp9^`phƍ7!!!"'&54763!!"3!Ɋ@_`@,ƍ^`7!!5!27654&#!5!2#@`_@Ȋɖ,`^ȋ '!";!!!!'7!5!7&'&54763!7!!ʉ_`'}E=aLT>scL0R^`5ƍ7 '327654'&/!5!7+!!'7!5!7!5!^`__BV 5cTpX?bLm>U`^`C 7 Xȋ5j )5!7!!'!"'&54763!!"3!.Bqx-qxDɊ@_`@Z54&'&'$  &'&'&547676!!#!5!]\LMLLML\]]\LMLLML\bc1111cbbc1111cbdd''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcbee$7!!"2767>54&'&'$  &'&'&547676r$]\LMLLML\]]\LMLLML\bc1111cbbc1111cbתa''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcb$3?"2767>54&'&'$  &'&'&547676''7'77]\LMLLML\]]\LMLLML\bc1111cbbc1111cbxyx''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcbxyx$7 "2767>54&'&'$  &'&'&547676pxg]\LMLLML\]]\LMLLML\bc1111cbbc1111cbpx''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcb$73#"2767>54&'&'$  &'&'&547676]\LMLLML\]]\LMLLML\bc1111cbbc1111cb''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcb$ 2L"264&'2#"&54>"2767>54&'&'$  &'&'&547676ZPnnnoO@v+..]\LMLLML\]]\LMLLML\bc1111cbbc1111cbAoPOmmp1.-rB''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcb$+E %#'-73%"2767>54&'&'$  &'&'&547676C4f4C4/f/]\LMLLML\]]\LMLLML\bc1111cbbc1111cb1XSXYS''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcb$!;!!!!"2767>54&'&'$  &'&'&547676]\LMLLML\]]\LMLLML\bc1111cbbc1111cbj''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcb$37"2767>54&'&'$  &'&'&547676!!]\LMLLML\]]\LMLLML\bc1111cbbc1111cb8''LMmjML''''LMjmML'dbcwvwvcbddbcvwvwcb$!%!!!!#!5!QX>ddYee$ !!!%!!rPX>ת\$   ' 7 %!%!!=kyykyjjX>xjyjjyk$$ 3#!%!!aX>J@ <1<033!!upJ!#!5!3JI!#!5IssI35!3!|33!!Nup| !#3!!!!.NN$J !#3!!!!.$J !3!!!#3GupJ !#33!!!#3.GVfupJ!#3#3!!!!.cGGf$J33!!!'!'Ssj\s=u5Y6pJ!!!!'!#3!7!sjshxj56$$J!!'!#3!#3s6s=5Y6puJ!#3!!!!!'!#37!s:jsjG$-56$]*5$%67654&#"'632#"'732654'&'$@e=M>P7sZw㔰Zs7P>M=e.(Y7O0<0:>~jy[<<[yj~>:0<0O7Y]*327#"&5476%$'&54632&#"ee=M>P7sZw㔰Zs7P>M=e@.(Y7O0<0:>~jy[<<[yj~>:0<0O7Y( 51  ^ bb:d 5! 5bd 5! ^bbb:yg62"'&'!"&462!6"264S몧Q3Q3TW4drOOsOOSQ3CB3RU4CDPrOOqyg"&462!6762"'&'!$264&"aS몧Q33TW4QrOOsOSQ3CB3RU4CDPrOOqbgR 7!6762"'&'$&"26b1[륢S4OsPOtO.D/YR3BPQqOOy;d 3#!!#3%!5!( 󀨨 ds <!##5!#T~N 35!3 3#K#"T^ !!3# K@ih^T !!3 3#K@#"쪠T^~ )3!!&'.'&ZVF%,E=Ώ?~%FVZDA?=~ !53*,Ԫ֪w # #}}wJw 3 3!#wJww@ 1@ 0"# #4$H̭9B( w@ 1@ 02$53 3H4CC1 (B9 rHF103#F1  !!'+]^*^]䰰3#3#!5!7 !! 'RLxxLux66x<ux6xx6x'B  ' ''ٛ>PNq^D^'B %  !'''tNP^D'B 5  5!''6bNP'B5 5tN>]P'B 5 'Nt>P`32?632.#"#"&'!5gV^naibX^naiUK= ;ENSL=;EOȪcy 33#cu?Ik8ff%q#cy 33#cffI?#q% )!"3!!"'&5463!! '&76)!"3!k:((P:jZYk񼽽jȊ ()9:PZXD  ȋ )5!2#!5!2654'&#5!27654'&#!5! !YZj:P((:kɊj XZP:9)(ƍN$!4&"#47632! #4'& PtPZXD|p:PP::ȀZX8x8Ȋ:1$2653#"&5! '&3 765PtPZX1::PP:8ZX:8Ȋ|84'&'##47673#Z:KK:ZllY:::ZaȌlala4###!5!5!5!333!!!!'5#Y~~~~,,33ͨ^ 3# 57Ѧ^ 3#55=d//m.   5 5 5 :6:6:6pr pr pr .  5555556:86::6:.  5 !5! 5?@Npo. 5 5!55?ްop9 %5 5!@op9 7 5 !5!?)W5$%5$Ti}$_|x]])W5$%$5iT$}B!]]|!&!%'&'57&%5$%67&%7*?;i@]0qw^%KA6#(AF+3273267#"'' 5cCXbh`^xnieEVhjb_zl]@LTND*F JVND+Fpo"%&#"5>3273267#"''55cCXbh`^xnieEVhjb_zl[LTND*F JVND+FͰW&&#"5>3273267#"''%5$cDXbia]ymieEVgia`yl]$}. LTNE+F KUNE,F]]x|W&&#"5>3273267#"''55%$cDXbia]ymieEVgia`yl[}$3 LTNE+F KUNE,F|]] 7%'%5 '瞃۞L О  @Y8@\9@a ' 7%͞G۞О@?Y@<9@}5!%57%!!'71|Iv\' :qߦ[@Z8@_}7!!'7#5!7%%%9Jpv\]FGjq8@ǹ@  &vvrn66\]]\6666\]]\65kk\SS]\6666\]]\6666\!YZ  "27654/2#"&5465732332233VVVVVVVV)t'>32#"&'#'%53%&  s:{{:!8#!rܧ$daad]chaam@j.!3!3:^ &ۺ+#+#+A&6FVfv ]A]A]A)9IYiy ]+ + $%+$01! 4$32! 4$#"35%33!??qqW|A?rpG~+/ 8?+3&+3+A&6FVfv ]A]A]A)9IYiy ]3и/A&&]A&)&9&I&Y&i&y&&&&&&& ],9+ + +0)+001! 4$32! 4$#"!!56$7>54&#"5>32??qqWO\R!>/_N;sa=0>A?rpGM"?U(?N&:$}:iF D+B5+B+A&6FVfv ]A]A]A)9IYiy ]A55]A5)595I5Y5i5y5555555 ]5B9,5B9,/A,,]A,),9,I,Y,i,y,,,,,,, ]ܺ&9;9+ + )"+)?8+?2/+2/2901! 4$32! 4$#"#"&'532654&+532654&#"5>32??qqW v@X[}DuskcZX\[4yk_=hA?rpG]0OLGN<:,+>2+201! 4$32! 4$#""32654&.#"632#"&5432??qqWN\\NN\\Ta/w N 5jA?rpGb[ZbbZ[b#P =  "#/$/ܸ#и/A&6FVfv ]A]A]A)9IYiy ] 9!9+ + !+01! 4$32! 4$#"!#!??qqWkQ1A?rpGK '?K +=+1F+1+A&6FVfv ]A]A]A)9IYiy ]A&6FVfv ]A]AFF]AF)F9FIFYFiFyFFFFFFF ]%F19%/A%%]A%)%9%I%Y%i%y%%%%%%% ]+=9+/4F19%7ܸ+@+ + ":+".I+.C+C4C901! 4$32! 4$#""32654&%.54632#"&546732654&#"??qqWT__TT__jivvWQMKRRKMQA?rpGPIIPQHIPIvSttSv\\=BB=>BB 4@+>)+>+/8+/A&6FVfv ]A]A]A)9IYiy ]A>&>6>F>V>f>v>>>>>>> ]A>>])>9A88]A8)898I8Y8i8y8888888 ]+ +  2+ ,;+,5&+501! 4$32! 4$#"532676#"&54632#"&2654&#"??qqWUa.w O 5kN[[NN\\A?rpG$O <b[[bb[[b &2>+#+#*<+*60+6+A&6FVfv ]A]A]A)9IYiy ]A00]A0)090I0Y0i0y0000000 ]A<<]A<)<9<I<Y<i<y<<<<<<< ]+ + -9+-$%+$3'+3$01! 4$32! 4$#"35733!"32654&'2#"&546??qqW͞u>@EE@?FF?A?rpG>>'*6ޗ{j5!jl!X3 !@ <j 5!!5!!5!r#B#B#j<l !!!!!r#B#B#XXXm 333mjjjm !!!@@@mjjj<j 53353353353X 5!###ljjjX !5!!!5!4l t,> 3!!!--Axj 333!xjkx 3!3!kA 5!5!5!3,,=jX 5!333jkkX 5!35!3̠= 3!!!!-- ABx 333!!xs  x 3!33!!-skA jB !5!5!5!3,,X !5!333xtjk X 5!3!5!33t,  !5!!5!4B 5!!###sjjj 5!!5!3!!t,-sjB 5!5!3!,-XAj 5!333!jkk 5!5!333!XkA!5!5!5!3!!!!,,--AB5!333!!###sjkkjj !!!!5!5!333!-s t,jBkA 43!!"yY[p~| 4&#!5!2[Yxp~|j 5!2653#xY[j~|qj !"&533![Yyjq|~*m3YѲ/ Y*m#3*/ Y*m # # 3 3*iSjh5!|j3?hj5!h}j3@hl|X!@?hl!h}X!@@l5!5!!5ijVV333PP?@l!!!iXVV#!#P@P?@;\;?!O?;j!Oj;!Ok;!O@;!O;!O;B!OB;!O q! ! ! !' I!] ! 3 :d'bm #'+/37;?53!5353!5353!5353!5353!5353!5353!5353!53('O'('('(67576('l #'+/37;?CGKOSW[_cgkosw{#5353353353!3#%3#%3#%3#3#5#53#53#1#53#1#53#75353535313#3#3#3#75353535313#3#3#3#75353535313#3#3#3#bb~! 'm 59=A35#%35#35#%35#%35#35#35#35#35#35!35!#5#!5#35735#35#wNOŶZXYI6ZB;YBq:g!(@;n'n;!!!;(' @;'nn';!!;@@ ;!!!O;n';&nn';!!'($! $!!!,7r+uv ))xxp) )$7632#"'327$%&#"%632#"'~~~~eMM>yJJJJJ6````qq|qq#u"@91990  9%-p) 327$%&#"%632#"'MM>y````qq|qqr' '/7?G%&'&'6767&'&'7%'676727"'64'7&"'62&47\+;.81F9K58.42d;E9G,:.80G9J6&8.;+d1O9FLL&_`JnLL'`_n<1& j(0=Ju &,A=N:0('<1& j(0=Ju &1<>EB0(n_II'[[JnII'[[p) %/36%632#"'327&#"6767&'&6py AAAA,+-,,-+A@@Rqq|qq%%mܱ[0$ %@%|"p) )73276'&#"7632#"'327$%&#"%632#"'r99:9rr9:99XWXXXXWXMM>yB!!BB!!oe33eje33````qq|qqp @ 104767632#"'&'&pihѵhiihҵhiѶiiiiѶiiiip $32#"$27$%&#pkk<MAk^a``p $32#"$"3pkk<MAk^``p $32#"$%&#"pkkAk^>``p $32#"$327$pkk\MMAk^>``p $  $"327$!pkk]<MMgAk^```p $  $"!pkk]<Ak^`p})6%63"'pRqq)#2y|q*q( 2654&#"!|~}}|v< ( $%632#"'327$%&#"!IMM>y_O````|qqqqH( ( !#%&#")%632OyyMMqq>~``  3327$3!#"'$@1>qq``) %63"æqv`) 2#%&#u)q>` 527$3Muyv`>q "'$33yuMq`p)%632#%&#"puqq>``p03327$3#"'$puMMuyy``>qq!$ !$ !$! !$!$3! 2654&#"4632"&nȊce;~|ddcc||}$!%!!d r<$!%!!We r<$!%!W7 r<$!%!W7 r<$ !%!!!!+c,b r<<!$ 462"! W|VV} ,|VV|V !$! c  !$! b  p(  7& $  %;<*X֖$ !!!!!!,7,rWb<)) Ie$ !!!!%!!,crWbM)MM^??@7`d?\gOOOOy>*<?v^h"3263#!5276;'4?'4?26u'6"gP39.4! '*C0.xV#m14He '1l1 Z+dd?33 #&'&+"'&#"/573;2?"#'57#&'#"#5676!5:+#9,p!j[%+ > 7VCCc":8}V .e3B=Se` e9*=9 3@=}k %C`:d;emu}'S3273&'3327&'67&'67&'67'32654'&'2327654&#"3672 $54767&'&47'&327632#"/#"57#"54?'&5432'&27632#"/#"57#"54?'&5432'&327632#"/#"57#"54?'&5432'&27632#"/#"57#"54?'&5432'&327632#"/#"57#"54?'&5432'&27632#"/"57#"54?'&5432'4327632#"/#"57#"54?'&5432'&27632#"/#"57#"54?'&5432'&27632#"/#"57#"54?'&5432'&27632#"/#"57#"4?'&54327'4327632#"/#"57#"54?'&54327'&27632#"/"57#"54?'&5432&'67&'67&'67'&327632#"/#"57#"54?'&5432'&27632#"/"57#"54?'&5432'&27632#"/"57#"54?'&5432'&27632#"/"57#"54?'&5432'&327632#"/#"57#"54?'&5432B~ %<z*+')+(@&'$||e<-A}]\B-71SLoWj\vLL)(0/ (( .1(%%,* # $ )*f$% +) $ #*+f%%,* $ $ )*  \o  [ %)#&'%&)#`#$ *) $ #+,U  Q  0 E%% +) $ $*+&EC&V*,)-)-*,%&%&fБfU 3HhfeefhH2pu^QFs棥sKQGh!99!  !77!  4 4 22 K44 22 22  11                   7        %&%&%'%&%'%&22  //  g               44 22  ->O`q +&'&54?632332?654/&#"2#"/54762#"/54762#"/54762#"/54762#"/54762#"/54762#"/547672#"/54762#"/54762#"/5476%2#"/5476%2#"/5476%2#"/5476D.2`{4&/<) e>O ,4H3R 07K $   $   #  #  #  $   #  $   $  U $   # " $   #  7Q=KG<s-8PZy9z _e""#/2dt0&2j ,: . 4 . = ,  ,   -  -  -  -   .  .   ,   -   !! WV9`8 !! 7 ! !WVDu9`8N I 7%7&54769 }V&7A 6$ 8'^4? !2 7%7&547!&'6I@Y%14HFS"="l-2DC[9 &! 4$32 4$ #"&54>2JJhhq0^mNMn2Z^Z2K7iwBNmmN1Z00Z} C"32654%"32654&%#"&54767654$ #"&767&54! ggJIhIhhIJgg[ZQoy y}WZ[zADgJIggIJggJIhhIJgU\\Q srW\\^} A4&#"26%4&#"326! 547&'&632 $54'&'&632hIJgggMgJIhhIJg#@@z[ZW}yOOyoQZ[sIhhIJggJJggJIgg ][[Xrq Q\\} "32654&7#"32ɏǾ/`T_ȐɎ;P12Y}1"264&"3264#"54327&5432#"'&'3xyx& کZTdIU  k#5AMYer3#"'%&547654'!#"'4%$53!76=332654&#"#"&54632'#"&54632#"&54632&'&67632#"&'&676'.547>'.76$6&'&54%6&'&6>#"'.54>32#"'.54 [$gi< D""D =if%LW쥨驧r^]]^ !! !! . . *)X,),*))+. } +G  G+vKK9__9KKݧꧦ]]_""""s!!""W&. - . - a)," "  ))    !) /     p%-5AMYdp|5#!4'&'5#2#"&546"264"264"2647>'.7>'.676&'&>&'&7>'.%7>'.676&'&676&'&53!76=3%#"'676%27+%&547654'7327&'$%'#327%654'&54718楣. . . .  - -Y - -))G))))U*)>- - ~- - VK; yA C0B Ax ;K'6FJ> $06# >JF6&@@1AeA1@@H磤椣筁 . . . .E - -- ,1))),(9)())u- , - - G77W6 W77G D&& ee˥ &&D "(=pp=("u !!'!Pn8hv "!!'!##+572367676MoL)>u eI3?ba8hA:F;/Itxv !!'!  ##' Mo_h[ei[i8hi[ef[l[@36273 ##'5) U.WW1@ US Vdv#,5>~3+&=43+&=4%3+&=43+&=43+&=43+&=43+&=4%33 #&'&+"'&#"/573;2?"#'57#&'#"#5676!5\:V\9\:\:]:&]9[\::+#9,p!j[%+ > 7VCCc":8 #8d#7$6$8;$7i$7 #9pPL  )Z. ;6ZV Z3%Y63 .87p  3DMy!674#!!6?676545&#'323276767654#3#&'&'454632767!672!&=75$/563&43!32+'!67#>54&53# ? I :W0 96;E,Q 2:&l6x0 bm! o۸"\>%Ef~e2U6g!6V#p5C+ C ? P9 @7H4XmM7RV /M(=H: ,qLUD)8Wqke-Pex NW =$ U  /0c)H?2@[nDF8T$.J? !' !T4XKGwL5_K !'7W4Z~wDS&5476322632%632#"'&'#64'#"'&'&54654&'&54767632xJX%&XA,B:\8 [EMH95##Fl% !9@!#jL p_Mi#"?8" %lF##58HN4hok@RRr*%te BB9'7*$%) "fXS5EIf" )%#,7'9CB >E3#"'4332327$'#"$4727%672567654&5&oJ7.b9M D ,B3 qY 5**]d=HN9% sW$,J ]T-MMm@ed: ,'Z M'cM&T)$$ < I2%!"&54676737#&'&54>;7!"&546767!7!"&54>3!6763!26P+=6/2D>R+>2,+v*>>+2  ,2 =,2  =,3>,2463!2!2#!!#!32#3#!>*v+,1>+R=D206=+P#,>3,=  2,= 2,  2+>{"D%4&#!"!0#"3!!"3!#";#"3&'6737#&'6737!"'67!7!&'63!67!2I0!6OS SS: SS>SS]]J]]]]h\\, Bv*>K%39LKIOKHLKIhghghghgE?-L!D72654'6#"'4#"'54#"'54#"'675674767#%$4:JILLHOKHLKIhghgighgD>-sJ1 b6'SS cRR SS?SS\\K\\;\\]]!A*>K{!C%254+'3254+'!254#!'!24+!&#!"463!!2!!#!3#3SS?SS *vA!,]]j\\\\K\\IKLHKOIKL93%N-?EghghghgiL!C32=732=7325732'654&#'%2&'&5&'5&'IKLHKOHLLIJ:4$N->DghgighghSS=SS SSb SS'6a!0J)K>*B \\]]:]]J]]}O &*.26:> 3656;2#'7+"/#"'+"5&54775%"'5476;25'7&56%635&56;374765'75'76=4'&+ +"'4!#"'4543$365&5&#%#754'&5&&547'5367&547+&'&'735&2?"5%75537'7'3533553535'32767&5%2?&#%55'5757757751:e,$?F?Y>F_LA3ELH3,8LYLlEF'!0< k#gF  EeY!! Gp&iq.8ZN$%`BCf F4"4._?ee3&{E(1-+$Kt8 -  $Gs sM rEF"2 >_plTErf^5.>=9|5"-l)d ,&>vv]cccWpC-+ d8 Bpp>W]oaxvuPp82,D ^8, ^B$K+ "1R[+e*; 2 W QP I&? gpo% w ^SA$ 2 9i-5n02 Ai&IY^P]D%\??\OWC ,,1 /211/=;7777=321811{908hN%b\Dh,)h?17I21!122223 21&2%2#"'&=477654'#"'5473Bq4|l anN ilm b 9 b؍MOb>YaYƮ58l7P P@ $0<FX + &=6&# 3 6=%&#"';27!5%67%!&'&'2+"'&=476r cR~UY082.ԍ_W_V"+}IR8D).P9H'S]ٱZYHYoX(I_ ;.2lOP%.G6R%&I8d)Nl>54'67&54&#"&'632.547#"'&'#"'3267654'7327323.#'654'567654&&5476;'&'%&+"#"8DH$$yU ?L[>!WtJ([Fho*m.2\=w\`|UP7:/E" @7?EP]Eix pF@T5ym,"&eB@q(A _% #+B7!N &".OS$XE/K(Aa]dLP*'FCaYr=C44mo C (FKWYFvbph'UD'R< $d#+?Vm#327&"#"'7'632&'$54#&73254'&#"'5&567#&''5$'67'654'6'5$'67'654$'67&'654'''5$56732#"'&#"&'$'63&47"7&'7&'7&'7&'54'6546767675477&545?&''5&#" '6%35&'.54>23#67!&#"W  OB7[l#> F_Vh " "@.,=6tJ4Vp1EQJqMi vhpHI!:JJJ =4m\8B*?o v!"t,`s&*_~P1>5='g=>24<+-s[,*&sd1PT>3J@='h<42J-H#*YT_Y)*)X^TY*$D  ?>}>  *0t"J.  &b54CUE ''!`9 !,(MTE *! }q~=/+)f[4f !B" <@0&9c?"V+GoMK~a? }b9e\ P&0@k"?c*GEJX ?e}9 \4 \6 '''' 6\ N(&'65&'67327&+!65+"3yyys{w ccޱqXeXc6 6 c ,35'533#3!'#'5!5!5#53!5!5#!!-ʷ}} ckvG G @<<3ffX苜qXGccGJ 326&#!2+73 ### 3(ttvgnؐB(33#!!#'!'57!5#'5735׫$"q~q+!#!573#'5!3!'573!#'73!#'5;jjŠJss<wѡIjj8/w{,32#' 3%+ &5%6323'#57'53^VQ6>ѨABؒ6ʞG2k >Y3~||~Obs32732753"'#"'4323$4'5;+"'#"'53275'&'&5?5572%#&'&5%634%476=%@.!%,BE,#!-Q2" $nL/PuHED832#"&546324&"26%! !  Őb{=&*<<*(;E;R::R;KJ67Ϛ{ɬ)::)*<<**<<*):<'L67I&b'bb &b'bc &b'cb &b'cc &c'bb &c'bc &c'cb &c'cc  @FLRX^djp3264'&#"&47367'676756273#'#'5&'&'7&'677&'67'%%&'&'%6767%&'0/CB^0/AC/pkTcR|'N(OfUippqUfO''NQaQh!$ b)dLQk KRt!% c'd&//^000'N'|P_PfppoQ`Qy'N'P\ QgppmQ \Py,  M N>&`7" bK*V&"g{ M Mjn !-=4632#"&%462#"&! ! ! ! 676 &'& Q;:RR:;QBRtSS:;Qtu <=CA$32%s'l(;QQvRS:;QQ;:SSu tC<=@%8338H,'(+jn !-=4632#"&%462#"&! ! ! ! 7 767 '&Q;:RR:;QBRtSS:;Qtu <=CAs('s%23;QQvRS:;QQ;:SSu tC<=@G+'',H833jn !13264&#"32654&"! ! % 767' '&'Q;:RR:;QBQ;:SStRtu s$32%s'l(:SRvQQ;:SS:;QQu [8338H,''+  "*2:AIX3#''%#&'52#"'&5476!!'5%!!'53'5%3'5%3#'32765'&#"sNN99=>-1\ H0e%FKSwZGr=;=NN$E| 1 ?'_>?@7`d@\hPPPPy?+<>w_VG{?,rCA+ +"'5$76%&'547327676=&#~jt1/Q}](+VRxbO P >nS]] =fP+! &56;2'5$%75#"3ui1.P~N](7P,VSZycOpO >S\^ f0:1>7#'#53'&'&54767&'&=33676=3#326'&i($lm$(($[Uu&tU[$&uU[[UV$|ddb e|$% ZSSZ %_TYYT- #"32654&&5432!!#!5!&礡ɩPS'䤣أL"~|| - #%2654&#"#"767!5!3!!礡7䤣أLޜ~|| "326&#"!7!礡YpipH=U g\uS5264&#"#43233#!5 z{ym㗗y{(|j#53533#632#4654&#"#*jjoon}mZyH{zF2 1"32654'#"&4767!!53#5!!3!!#3!!pOO87O:=0LmkL/>Λ2  1O79NN970LؙL1KӘJJ-'<%#5#535&'&'5'73'3#'73'676=35'73'33◰zhNgeMjzzTThOʍ7NjYYӖy?! #!!!'!27674'&#.d ;6zFH%QM_\ǃ$P<]$!#"#&5463 67!2#654&#"V⩁"T]ts]U"X"1((1"u." 6&'67>3"#"54767&'&#52&͕LVa{.+ؔ)0zHUM\&ϖ=Bll)'ҕ*l8lB=j&'5 %$ 56?63#'[Wtutu4ZZ//[[5  @Eo&<"3264,'532'&54632264&" &$#"#"&547>B_^^l;͓hI^9l:͓hI (+|TlgMLx)+{TlϔgMM M>54'.#"32463227#"&5454&#"#"&'&54767632254&K2q'$#K1o'#0ߴGdAoc.% 3t88bWDs-Kx68<32>32#&'567'45'#&+"#4'3>$4&+"?w(K>R0D32>32gYYYD,.:?#)v$E?w(K>Ro}vvxJvaAjtAO]ƀwϧ  / ? !5!?=lXjj=?l=Xj=jj)127632#"'#576&#"4'5267>327&'"SkQmyz,~zi2@:$(.-)zW] ݾgvx-aX[&ŝ9{'Q32263227632&#""'&#"#"'&#"#'3232762327632&#"#"'&#"#"'&"#'Es- p86rV+)|m^?_354.#"!&'.54>325467675#53533#63232>54.#"P#3JTRJWVJQSOMJ4"?*&ElnhPL$ llill %LOhnlD')----+)QPQ((QPQ)+/ 6klj$?6FWWF6?$jlk6 }++--JHNRh|&'4>32"'4>32&'4>32&54>32&54>32#!5!'!567>54.#"32767>4.#"327732>4.#"327>54.#"732>54.#"M_ 6694S55.+C55C&.66 V\+55 c$M##$ 6$#$s`%#$d0"%)h #"#_33@]22-"40446/*33UJ"+33^1/K=0T* ####  #&$$&##&$$&#  B #### *"$$" U!'-2!35!#3!53573#'5#5!35!75!!5'57!s\\ss]]s JRRIJ~֛E77__vtt4!v7CQ^&54767&'&'5676767&'&54>32! 535#5##3654."!2>4.#"  <$))+N-N*)N-M,**%:  @ v<-MTM-?K5:66459<5&?HPPIK* ')+K**K+)' *KIPPH>&5<:6uN|l||l|-I+N))N+@6:55:5Q)5>o654&547!&54='&'654'67.5476;+"'5#"=6&'76767%25#654&'Fz-6 Z8. ,N0H!h6%`+EH )#M ;,Jga#iR k' M +1^hgo8:(@s.Pmz nx?.#1p#41`&>%!ac,,LHJ x}647| + OJJ)!0 P[32>4.#"32>54.#"!5&54767&'&546767&'&4>32'&'.#":e79e89f76e`[S &(*UM,N)(N-KV)&& \@ECApd88dpg669:%N&KRS* 'TM**MT' *SRK&N۠:9}qyyq}c $Tdhy67&'&"!3!67>54.#"!&'.54>325467675#53533#63232>54.#"!57!&'.54>3234'67632!P#3JTRJWVJQSOMJ4"?*&ElnhPL$ llill %LOhnlD')----s=BDw@>=))==AwDB=+)QPQ((QPQ)+/ 6klj$?6FWWF6?$jlk6 }++-- !yCB{C!$$!C{BCy! JHLP&'4>32"'4>32&'4>32&54>32&54>32#!5!5!M_ 6694S55.+C55C&.66 V\+55 c$))_33@]22-"40446/*33UJ"+33^1/NNOOU%)5!5!!35!#3!53573#'5#5!35!s\\ss]]s ^^/oo#E77v4@4767&'&'5676767&'&54>32!&535#5##3  <$))+N-N*)N-M,**%:  @%v<5&?HPPIK* ')+K**K+)' *KIPPH>&5<:6n5|l||l|L3?HN654&5473#!&5454'+#"#7&'654'67654&547;2547#";65'"3%:U"-6 Bu Zg0krX0c-h8E+`%s H>4wM-'9.QY / o8:qhPSmh #%Bz1"0@)5"@YR0.&54767&'&546767&'&4>32; &(*UM,N)(N-KV)&& 9:%N&KRS* 'TM**MT' *SRK&N۠:9C##"'##56'##"/547?^'5@_*SU&/UL ;Yԧ9UP(` XI.s222732#&547636=4'&# #4'&#"*t pz&=<xQ>hG:V Hek%PF5NP B|-&pA&NFX &&5 <F:^;" V gdG7236;2"##'65##"'&5476;235&'&=476e x<JT`(GeRUdfB3 VNTMT,P$ 66$0_ u3dUdt_}s*$"Rt0XX__/ik=ZG8*F 1 . ъf)MC =g9EkO 9!(-);&  ]t!y" & 2| ba$ U+  #8M35733!&54?'7'327!!"'&%#'7367654'77'7'&#"'676ի,&T>=c#]K9.U:1ʈ%`T?7>54&#"5>32&54?'7'327!!"'&%#'7367654'77'7'&#"'676]T@1$J=c#]K9.U:1ʈ%`T?32&54?'7'327!!"'&%#'7367654'77'7'&#"'676Z _3lFHe5^\VOosHGJI)`VKm1Sj,&T>=c#]K9.U:1ʈ%`T?=c#]K9.U:1ʈ%`T?=c#]K9.U:1ʈ%`T?=c#]K9.U:1ʈ%`T?=c#]K9.U:1ʈ%`T?=c#]K9.U:1ʈ%`T?32#"&e|e(<X<ħñ"32#"&$2#".46e|e(<X<ħñ"@<#"4.#"e|e:<#"< !<"#;zch =B4.#"$32>4."e|e:<#"< !<"#;"< !<"#<@;zch =B54.#"##"'5##"$'&'0!5!5&'.4>32!!676767'%''H&(G()G'%H(%'V W3WImuw>DE}AB|GE=md^JW4W Vs'H''H'(H''H`XAK|@X1(ԁ3"|}DD}|" 2/ "1X@|AX1# / 673&/'67 &'"&'6?&'3 ' K[]><+Gg['fBBe&\h?(K?]\K !;32T $ #AC,MMMv A5p_9D-M**  B@0"@R//>wA&oc/D&3.YaQ/5"1'"uE62/u= =!m- .... y 7%  %  32+#".=!"&'&'#&=4;7337_% 8)0/_^^M^1/ 9534<&&<&*(D>?GGzB6C{GG?>D9/C}&632#"&'.#"'#!#!#Ҹ62K#+~KF0R!9'/Nx_TV_T 'NQ9;:#8HL"CD|))Z) 532>4.#";267#&=&$32735&'.4>22[02[24Z1/[)'5*+X A323#67#&"#"/&'&547&#""'6%676V n*[n%'ZxL0<{2;&b;0&8a>!U*~EmLK}`? {a7c[ O&0>j!>a)E~CKW ={d{7 [+M57LL75M-Z '*''*' Y (5[ J5( \d (5J [4 ''/7O_2#".54>&'32367&%2327654'&''67&'&'&'676765467654'&#"7>326323#"'##"'&'#"&'&54767&'&54767232&'&#"6&%6767&'&'&#"676&5467&'&6732767&$$$$OG3%V cc V%4GL944m/122102/.303112.OF}6&V e"w?>v"pt #87! vn":;@A<:"nx !66# sp%./13/.UVT\<>"$!! !"#">kc V &6|FO 93399 <>#"#><  "$ZTU./43..V5$##$59gT;&'9Z^^Z9'':Tg9'(''&()I8:9889: Z_59eU;'( :8.>euvc>-7:bccb;7-?cwud?/8KWZZW **D@@D+8(':Te95^&)(&''(DA:AD.*!Y[[Y!& !-x67&'67&'4&6%67.'%4'6&#"&'6767&54?67&'&#"#&'#&'5&'"'67&'&47632>4.#"%2#".4>'7,3 3%/0),7=*#0*+3.22'8  YfT,1'').UfY >98 "2 B2;F_ XB?2C 3" 894ihgikce"S[XVWXZ#ejpMcNTvJKrZ1VlLWMI p jk%nA V{ww[11[ ww{V @#fd-#JM 7B/""0C7 NK",df#νhhοggQUXXUS !!Y,q@I@,qȤ7TU7S '!57|,q,Iw,q,ɤ窪8d %3!'#!52#"62#".54>" h9|M463%&$$5 O Dn; $$$$33'554#$/[QwGSGUW GJGZ*1=C&32632!!#!#!5!&"327&7&!&7326&#"6'XP}}R?99XezfH9?A:uutLFF"~|| -  GP8lGrr[0 $,8>& 67& '&'&'&!7!!! 6'&265"ut.77!u$lYoip@qDi4tEu.$rl,36l%eUg\xuvSc?\7 =1lHr-ؤ-9E6'#"'!!#!5!&'&326!7!%"327&7&326&#"suuW~WdP|ojp?9:v8?A:llGrE, || ~LDJg\u HOU(&  6&32!7!!!#!5!&yEߩPhpCLn[u~||  +D#"'&'&'&47>76327'7'%'27>764'&'."(F3"D"&%#}bV`ZZ^;D"&&$[X]:3G9:]:F=~=HS]^X&% iiD^29i\=<<92-1X?:<91*=X62'%'!!#5!5!5&'&'.546767''7'''7"2767>54&'&'&4p69].(EGGE@Z-<81VDEGFF'19T]9T:G5>+.11./:95>+.11./:9 \2:a(Eb_E@( %CE_bG(Hij:ο\ij+.wBAw./+.wABw./4+F!!#"'&'.546767675!5!' 2767>54&'&'&"<-Z@EGGEDVRbfNZ@EGGEDV18kbbjC9:/.11.+>59:/.11.+>5疑 (@E_bEC%##(@Eb_EC% kajP/.wBAw.+/.wABw.+ +F####"&'&'&54767>32333'7 '%32676764'&'.#"ܖU (@E_bEC%##(@Eb_EC% Uܭkaj/.wBAw.+/.wABw.+<-Z@EGGEDVRbfNZ@EGGEDV18kjC9:/.11.+>59:/.11.+>55 @  10432#"732654&#"陽…5 @  10432#"K +@kk k kKTX8Y104632#"&732654&#"ϑϑϘuSSuuSSu͒ΐSuuSSvvdPK!)7eK RX@ *.,&"($ k3,k($kk8991@&"6k0k 8<2<299990Y4632632#"'#"&7323&547&#"%6547232654&#"dϑRDDRϑRDDRϘuS?>Su^222Z>?SuuS ͒!!ΐSuXqpWv28ML88LM{WpqXuSSvTZ`z8Rm3#"2767>54&'&/2"'&'.5467676"2767>54&'&/2"'&'.5467676R#)$#R#$ $LK:C.25521@=:C.25521@=R#)$#R#$ $LK:C.25521@=:C.25521@=zZF)(JG()K.2IF21.2FI21F)(JG()K.2IF21.2FI21 J7Qk>767632"'&'.'!"'&'.546767632$"2767>54&'&'$"2767>54&'&'#61@=HK:C.25521@=:C.5%'21@=:C.25521@=HK:C.6#R#$$#R#$$R#)$#R#$ $5[51.2IF21.4`]21.2FI21.5[F)(GG()FF)(JG()KR 5%%%xr6׊eMM^xxV)7654'&'575#!&54767'5!s_vR$N::N$Rv_{aT,X@X,Ta{4b\)1%==%1)\b4ߴ:`\KDDK\`-&  6& #&yEߩPSCL"~{Y,!#!5!326& '6 !I(4~uP|Gjt ~|, 23"#"#"#5237 >>![ZVL;|| oJ,737!!'!!#!5!'!5!{{~zz~zdz|{||R{|xT% ! !5! #!7!# #T??LLwJ|A|JZt|J,$264&"&7673% %&uuu>hH]%VgVYFhݦuuv#gGέҔEgDX!#!5!&'&5%676'!HfN)]H;btWUJn|3Lu.:;͢8%|V^m3 76= '&  7654'7! '.54676! NΫ.8l?ΫNΫ,spppsppp>9`VhhV`"xx  hVc`VhhV`cVY9QN9ss9N^Q9sV^ -E  7654' 76= '& 76= '&! '.54676! ΫNΫkNΫ.8l?*NΫ.8l?spppsppp>9ghVc`VhhV`cV|`VhhV`"xx `VhhV`"xx Z9QN9ss9N^^Q9sV^m !1?U! '&'!  '&'&76767 76= '&  7654'7! '.54676! x8;41 ::; 9٫NΫ.8l?ΫNΫ,spppsppp>9d]]c]]] Փ`VhhV`"xx  hVc`VhhV`cVY9QN9ss9N^Q9sV^!-;K[s  '&'&76767! '&'! ! '&'!   7654' 76= '& 76= '&! '.54676! K ::; 98;418;41 ΫNΫkNΫ.8l?*NΫ.8l?spppsppp>9]]] ]]c]]cehVc`VhhV`cV|`VhhV`"xx `VhhV`"xx Z9QN9ss9N^^Q9s- ,"&54632#"767' 2654&#"@a^CF[^ccZ礡}[D>XUAB]~Lޜ~g]䤣أlPj'#"'&#"'&'&'&47>7632327>76&'&'&/&'&'&47>762!2!%327>764'&'.#"&#"327>764'&'&s* 0$+$$$ 1#*# ZaZ%% NT12 4 #HH  ")mROeb  , 0  +   ) . $J . %'.D"&B 1 $C mR )Ky    !   V!Edz267>54&'."#"'%"'&'.5467676;27>4.'&+"'&'.54676762%632$"267>54&'&.&&.&m,mQjP(!N!"(! aVf&&bZ55!("!N!(PjoQm,.&&.&q    l?W,>&#< A#"< " (( " <"#A <#&>,W?~    lOOj3!#!"'.'&47676?6767>'.'&#"#"'.'&47>763276;%32676764'.'&#"676764'.'&#"32eOuRd2!  HH# 7   ZTN +Za21#+$0 4$$$+$0 's  *   * OK) Rd#!>& 3"9*$"D. ' - D! 2 . , T% #: & (  IZx-4H67&'&'&+"'&'&'&476767632%632 #"'%#"'&'&'&54767676;276276767654'&'&'&"276767654'&'&'&""'&'&'&547676762"'&'&'&547676762'&'&'&547654'&'&'&";276-&#"+"276767654'&5476%327%&"'&'&476762I  Q\C--%("(/*0.,+"( /X]\9<\X/"$)0*3')"* %1*0CR[        22 2 2 2 %'   &J  &%C\d#_*]OhXC%&  J&   O]*       ")&`&"'$"/' <%ZS  % SZ%< /'* "%5"-($# ;8\= !  !  " /VC "  !  !  [uV/+    V^au 767>54&'&'&#"&54767632 '.5467&54732#"#"676767#"'&#"'67654 ozwbda_f_zx|wbdaM,krnulspsnunNJ*D$ lQ$" 6*D?"5'K(2- # >   :72 331cd툍i`4331cd퍇>mwn<;;8ro졘wp:;;BV0/M8:D@*|sa  -F(7 "*=8&0!2  1-5$& 6:B4V^ (B\w.'%&'&"632%6767>54$2"'&'.546767" 767>54&'&'&'2 '&'&547676?'*&$ 1$-+h+-$F3782**?1 $&>>9|wbdabc`zwbda_f_zxspsnunˎspsnulwI_"2[$  "" gI $[2!v 55 55 31cd퍅caf31cd툍i`43d;8ro졘wp:;;8rown<;x,A-57'36%33#3#!2#!3#3##$'#7$@d5{sVd]F0 0F]dVs{5⒒d@( jPP,PP` 0 ")- !676762!"'&'&'&54!X$#R#+/RFF$#R#$1Sh,  k-"s!|P476?6763&'&'&547632676767654'&547632!54'&'&54'&&#"'&/&'&'&#"#"'&'&/&'&#"&'&'&?6'&'#"'&'&#"!'476='654'&545454'327654'&'&327654'&/%4-)"$0JK&  )7    %0'# #6 +-L __^/s4* 1( .266 |/(1   \   #:7  lS&   x71]/~[#<$  o_%@,: $";vR $X$+|!5DX&PY;9Do6 b'n2  83eF] 4T&  &  /50$?- 1@& 3l K  C"P1 :03<D:5XI.)D&[+-1:   q/A8   g+jl9Lp{7654'"'&#"+"'&54?67676763276323273#5%6767'&#"6"/67#"27632327654'73654'676547&p/l0&J!cS%YE]{@C"$4>-;% ,(6Y>m!N$X6"/,(4sS?X$U>"sJ?K(`./4+2K2.0>S Zp0+1^' ;cs  /^"|Y/ 428ۇϕl%%ot5oA='Y$ aT* ''G+- %_kj~r}jL`І|\gK@/.85c($ (2LS>54/##326?%%3254'654'3>7632#"&547>32'% ;66I   }g ?6qn   -> 9@ H67;  zh| 8 >6!q    B5>%+?F4&'&/76765'7! !'!654'!4'!!$467>2"&'&!654' 33 ^^^RXI#J2VlP# ~!88!~ Kppph,p<(##(#id (2LS.#"227654&'''%'654+.#"65.'&54632#"'.6#"%  I66; o |>?%6!q   9  ;76H   |h> 86qm    BX{[%G'23 %%.'&"27>7%$!"#232%"'&'.4676762%#"#2%k      A>>dIID`nS   SnGYn 5>5 n)(%$#"#64'232%%&'&'&"27676&22k**!n``n!##3W 2327632#"'&'&5476'( > !~GH ".4F+@xH )0$'*' 23277632#"'&'&54763'( e` }{*279HF`0@xJL 1 ,A  ' 7 Ɏ877Ɏ77ɍ8ɍ? tt7tt7t7tt7uB2632#"'&'#"'&54767'&54763267632676 Q   x L$3 z(   6X3  6*=P*> "#  R26#"'#"'&'+"'&'#"'&547&'&54767&&5476326763276T 디% $$YyX$ zc0 + j :  (̢1#: _$ #- Խ =1 '2ĺ pD #!!!!!%!!!!!!!!#!5!36HVBBXBBUHVPBXyBpD !!!!!!""p"p"#pD35#7!!#!5!3rrsrspD!!%!!!!!!r"p"#p"#Rb !!#!5!3ppEU l3!!'#'!!#!!3!5@,r,,_ r,,_>v #!!!!!'!!!!!!!!#!5!3hm_|P_H_pDK#";54&'&'&#'!326767657'&'&'.+3!76767>5{dIB,$2$*DEh{LGC_RQ|66R_CIJ{hED*$2$,BFd{LGC_RQ66R_CIJKIB`OT|87O\FGKzdGB+%2%+BIdzKGF\OT87O`BHL{dGB+%2%+BId  #!! !!! 373#'7#ZAA:Llحmllmzlmllm|}}|d d}cT`C54'&54762327632#"'&+"'&5476=#"#"'&476323(L,68x86,L zFvd0000dvFz L,68x86,L zFvd0000dvFz zFvd0000dvFz L,68x86,L yFvd0110dvFy L,68x86,LV^&'##"&'&'&4767>32367675&'&'.5467676236767>32#"&'&'&'#"'&'.546767675&   R.-R  R-.R "  *!""! ((\(( !""!#%   " R.-R  R-.R    %#!""! ((\(( !""!**!""! ((\(( !""!#%    R.-R  R-.R "   %#!""! ((\(( !""!*  " R.-R  R-.R   Sa4&'&'&'.546767622676767>32#"&'&'&'.#"'&'.54676767>5"#"&'&'&4767>32(,$ ((*& :.r06$&**& )'De!  'd8:b&$$&b:8d'  )a@/!  ')*&$6/r/6$&*)'  ')?c'  &d8:b&!$&b:=_& (bCc"  &d8:b& $&b:=_& (a?/!  ')*&$6/r/6$&*)'  ')De!  'd8:b&$$&b:8d'  )a@)' ((*& :.r06$&**& ((T`0267632#"'&'&'!&'&'&54676763267632#"'&'#"'&'&'&5476767!6767632#"'&'"'&'&'&54767#"'&'&'&5476767632!#"'&'&'&54767#"'&'&'&476767632&'&5476767632!#"'.'&5476767632&'&54767676Z   ( &            <   4          % (      (   2     6           %    <    %  (   W_2767653"4'&'&Wspsnullunsps;8rown<;;j>-'O^__^Oq44H4"hdd0!% %!-@jjjk**37'73 #'xxxx.xx.x..x  pD #'!5!73!GFdFGrEGdGErFGqFGdGFqGEd@L     - FOFc,OO,cFd,PO,dGOP T` '%%%%%% % -wD{wwe#w%f{wwy||y{xxe#w%f{wwxEy||y % %  Zp/AppA/}}ET`     - Zq NqqN  NrqN qrT`% % -ZyllylyyT`%% %% -ZtGcVGttGVcGGstGWcGtsGcpD/3%!!%#'''%!5!%777xo:U.cF.d;UǩoxoU:e.Ec.U9oE.f:UūoxoU9g.Ff.U:oxo9U. 54'&5476276767632+"#"32;2#"'&'&/"'&5476=&'&'#"'&'&547676;232?&547'&#"+"'&'&54767632676'K,68x86,L qA'C<4GW>L d  f L>WG4L d  d L>WG454&'&/54'&5476276767632+"#"32;2#"'&'&/"'&5476=&'&'#"'&'&547676;232?&547'&#"+"'&'&54767632676o**YK,68x86,L qA'C<4GW>L d  f L>WG4L d  d L>WG42#'"372"'&'&/"'&476="'&547>Q!//VZ *nN+G80j@6RR6@j0/P1N TP#00VZ ,lO@W+G80j@6RN6@j03L/N  ]H,`,H Yc!77\4OO4VA7gU3',H^ ]H,`,L&3c!77\4OO7VA7fV4&,H^67654'&"327632#"'&'&/#"'&5476=#"'&'&5476763232?'&#"#"'&'&5476763254'&5476276767632#"'&#"#"'&#"327676%32767654'&'&#"#"Z8%1T1%85e %ZF\ +m8BS/?JV@6RTXN6@VGB1QB8n* \FZ% e53e!&ZFZ *n8BS/?JV@6RR6@VGB1QB8m+ \FZ&!e3DA 5<; > +F$H$F+ > ;<5 AcJ2QD++DQ2J (5H,'9,J&0f) T|\`j4OO7g`\|T 'g/& H,9',I4( (3J,&9-H &0f) T|\`j4OO4j`\|T 'g/&J,9',H5(""'!$(:UJJU:($!'""nFw"2767>54&'&'767632"'"'&'.'"'&'.546767"'&'.546767632.546767632=>343343>==>343343>x>%85670-),(-%8/[0!-(,)-02y/8%0%)-02y/8%-(.'&$W/:#-(,)-02;>/;),)-02;>/8%-( 06{IF{6006{FI{605+'g>:c.&".c;=g'+&1N%&W'+&.c:>k#"$.c:>g'+,B:>g'+&.c;=?nF\v%"'&'.546767"'&'.546767632.5467676267632"'"'&'.27654&'&'&"67&'&'&'276767&5467'&'&#"32767>54&/76767>54&'&'&#"Z0%8/y20-),(-!0[/8%-(,)0-<1:3%>(-%8/|/8%-(>%85670-),(-%8/[0!-(,)-02y/8%0M=  H C# B/g H /*x#$  8## H g/B PP  $#x*/%N1&+'g=;c."&.c:>g'.5 ?=;c.&&.c;=? 5+'g>:c.&".c;=g'+&1N8GG$> >$ c.,bB$#>  Ir0C >'#> LM >#$Bb,.$ >#'> C0rI T`)T:e&'#"&'&'&4767>3267'&#"327%32676764'&'.#"7632#"#.4767676324676762>322##"&'"'&'.5#"'.'&467"&'&'&4767>&'&'.'&'>76?&'326767767>5&'&'.#"767>7.'&/32>7674&'&'67'&'.#"67'&'.'67676767"2767>54&'&'"'&'.54?&'2767>54'7654&'&'&"67'&54676762:    $4 4$ww4 4 xy   %" !()-+U$"! ((\(( !"&S+-)(! '7M"# V2% A()-.R$"! ((\(( !"(O-,*(A"#2P"# "M    ! *4 2 kk  4 2 uKK        i2 4* !== 2 4  `_  wR#$$#R#$$  8 < c !<>     8 < d!!<>   "%UV*) !!$3R  R3&!-(-%Z& "#%(.2$( &&S+,))A!$3R  R3'A))XT$""#%(`$( "      i3+!x== 3 _`        !+3 kk 3 uKJ   F)(GG()F$    %3 3%ww3 3 xy   V^3N^"2767>54&'&/2"'&'.4676762 '&'&547676% %-z35++++++53z35++++++5pWDM69?=;9JHDM69?=;9JHSspsnunˎspsnul}}(.h<;h.((.h; +F$$> +F$H ;<5 A~ ;<5 A+DQ2J (5H,'9,J&0f) T|\`j4OO7g`\|T 'g/& H,9',I4( (3J,&9-H &0f) T|\`j4OO4j`\|T 'g/&J,9',H5(G+DQ2J$(:U$(:U3!'""!'""A''7'753'75377537'7'#5''#5'7#5'7'7<B-DH2#"2767>5!"&54$3!57!#"'&'.5467676#_>I-743TP>CPNDG-2.1/&D9 88 '.* !-8D_2{j@F'%.3r@Md7+4V/2&'&54676762"'&'.546767Zy*,&''&%1]~|45,-++-,54|45,-++-,5(+&a4|d΃fz4a&$(F*.j=3"&'&'&54767>32rJ6464NN4646Jp`684F@NLBD64:866D@NLBD668^~* i654'&#"632327632!"'&5!267&'&#"#"'&54763247632327654'&547632#" 6+Jo.^V|;-˙it36?̺fQMeEJS?(*$ s]vh2K)*NL13^v:Mc*ZeC03N35%&-Kt\K%9S >BWN=!$?$8(F!5{^?Z Q67654 547&'&+327#"'#536767&'&'&5432&5476323254'&5432?-BO>=v06&%K`dC+(k$'eM?$#=Hb B=)+8=.m9eb PB>$3g:84!EB7WPfG+1KHP<Ff#&T'0P+A'<}DC/'"05276767654'&'4rceNS((((`hm@DDF/CD}>C/GFCG !&547>2; 0!!6P<:! !$ ! "#{! !{54&#">32!5!>??qq>0ţ=as;N_/>!RL}A?rFi:}$:&N?(U?"Mt 6+A]A)9IYiy ]1.+. + !'+!+9*'!901! 4$32%4&#">32+32#"&'32654&'26??qq|=_ky4[\XZcksuD}[X@v hA?rs ?<:32#"&'32654&#"75!5!??qqYe2hvvhDw_X@ϰ?A?r%aVUa/  23/4/3и/4ܸA]A)9IYiy ]A&6FVfv ]A] +  + +,&+,/&,901! 4$32#"&54632"32654&#"7>325.??qq\NN\\NN\qºN w/aTJjA?rZbbZ[bb*= P# + + 01! 4$32%!35!??qqlUA?rv]K 1=++ +A]A)9IYiy ]A&6FVfv ]A]A ]A ) 9 I Y i y ]/9;9;/A;;]A;);9;I;Y;i;y;;;;;;; ]5+ )+ +28+201! 4$32#"&5463232654&'>54&#"2#"&546??qq_TT__TT_⾭vijvkKRRKMQQA?rlHQPIIPPI\vSttSvB>=BB=>B &23/4/ܸA]A)9IYiy ]3'и'/-A-&-6-F-V-f-v------- ]A--]+ +  +*0+*# 901! 4$32254&#"326#"&'4632#"&??qq鿹ºO w.aUJk<\NN[[NN\A?rK < O$[bb[[bb $0Ӻ%+%+++A]A)9IYiy ]A++]A+)+9+I+Y+i+y+++++++ ]+ .+ (01! 4$32!5##7##"&5463232654&#"??qq$ŸuF?@EE@?FpA?r*'$ =$>  767654'&'!5%3!!  '&'&54767̆mommom4mommomP\|~{{~||~{{~|96oooo6996oo  oo6}9:݈@>}~Ա~}>@@>}~,,~}> =6P  767654'&'!!567>54&#"5>32  '&'&54767̆mommom4mommom)4 \=)N=kP`aF7I׺\|~{{~||~{{~|96oooo6996oo  oo6_A.Xx;_x55'(IZV@>}~Ա~}>@@>}~,,~}> =B\  767654'&'#"&'532654&+532654&#"5>32  '&'&54767̆mommom4mommomttLUDWx~zB\RGr=\|~{{~||~{{~|96oooo6996oo  oo6yt'(xrjw_Z\bd @>}~Ա~}>@@>}~,,~}> ='A  767654'&'!33##!5  '&'&54767̆mommom4mommomh*˪+\|~{{~||~{{~|96oooo6996oo  oo6 @>}~Ա~}>@@>}~,,~}> =7Q  767654'&'!!>32#"&'532654&#"  '&'&54767̆mommom4mommomz#G#KSLVAC\|~{{~||~{{~|96oooo6996oo  oo6c ۻ)%}|X@>}~Ա~}>@@>}~,,~}> =%>X  767654'&'"32654&.#">32#"32  '&'&54767̆mommom4mommomllm=|< /Vڵ =|^\|~{{~||~{{~|96oooo6996oo  oo6EKۼ>-O@>}~Ա~}>@@>}~,,~}> = :  767654'&'!#!  '&'&54767̆mommom4mommom\N\|~{{~||~{{~|96oooo6996oo  oo6`E#@>}~Ա~}>@@>}~,,~}> =#9E_  767654'&'"2654&%.546  &54632654&#"  '&'&54767̆mommom4mommoms慄htdthutԄ9tihvvhit0\|~{{~||~{{~|96oooo6996oo  oo6,{{|kl{Eggss\hh\]hh@>}~Ա~}>@@>}~,,~}> =2>X  767654'&'53267#"&54632#"&2654&#"  '&'&54767̆mommom4mommom=|< .Vڴ=}mmlJ\|~{{~||~{{~|96oooo6996oo  oo6DJټ@>}~Ա~}>@@>}~,,~}> =+8Ca  76767654'&'&'"32654'.  735733!  '&'&'&5476767̆mo5885om4mo5885omT,+VUVV++2QPPQΠP3p\|~-,g%&݈@>}~~}>@@>}~~}> = $!5!#%  '&'&54767{\|~{{~||~{{~|#:9q @>}~Ա~}>@@>}~,,~}> =6>7>54&#">32!5  '&'&54767I7ݺFa`Lk=N)\\|~{{~||~{{~| ZI('55x_;xX._@>}~Ա~}>@@>}~,,~}> =(B>54&#">32+32#"&'32654&  '&'&54767ir׸G\\Bz~xWDUL2\|~{{~||~{{~|db\Z_wjrx('°t=@>}~Ա~}>@@>}~,,~}> = '! !335#$  '&'&54767hno\|~{{~||~{{~|  @>}~Ա~}>@@>}~,,~}> =7>32#"&'32654&#"!5  '&'&54767CAVHSK#G#\|~{{~||~{{~|=|}'' %@>}~Ա~}>@@>}~,,~}> = $>2#"&546.#"32654&#">32  '&'&54767PmmlC|=ϵѴV/ <|=\|~{{~||~{{~|+޸KE@>}~Ա~}>@@>}~,,~}> = !35$  '&'&54767>h\|~{{~||~{{~|@fE@>}~Ա~}>@@>}~,,~}> = +E2"&46' 654&'>54& 74632#"&  '&'&54767Yt愄/tԃuhtt-tihvvhit0\|~{{~||~{{~|{lk|{{Essgg]hh]\hh@>}~Ա~}>@@>}~,,~}> =$>%32#"3267#"&'"&54632  '&'&54767!C}= дѳV. <|=Allm\|~{{~||~{{~|Q/=޸JDg@>}~Ա~}>@@>}~,,~}> =  :2#"&546$  !5##7  '&'&54767eddedddB¡\|~{{~||~{{~|>-/#&%q @>}~Ա~}>@@>}~,,~}>uPj !!5!!Pp#@pppt 7%FN4NGuP85 zD<22pJJt '-ZKFGNuP!!u\lE>~~>uu+"&'.546?!".4>3!'.5467>2p4,,$$,,42.p ,.".2."., puP8!5! %JZPJJuP8!5! %JHJJuP8 #3#3#3!!5 xx<<oJpppJJuP8 55!#3#3#3oPxx<<΄ΊXXXXu}~ !! ~PD! 6>l>>PD ! DR>l>>P  BlvvuPb3!5 5! '&'.u$##+* ZJMM*+##$0U%!JJ!%UuP84676763!5 5! u$##+* ZJMM*+##$0U%!JJ!%U0!! ^r{VXeoouP855!Dq΄Ξ0uj%5!!53  !<9h9>uj%5!!53  !<9h9>+Z !73#57!!+ Id&+ъ2&+Z 5!'53#'!!!+dI|&22 !'!'!53 !Odcndh 2 3#5!7!!! ndnd;ch dd !53#'5!'! !]n2n22r-hJdc;dJdd 7!573#5!! !2+2n2nr-hLJd;cdJ<6767632"'&'&'! <'CZmo~yti^Z\X^Vqoti^?)X6nGCZ.//+]Y݀z_X0//+]>Iʞ BP "&*.37#37#37#37#5!!!!3'#3'#3'#3'#<<< 7&#"7'7 !%*BF8WU{FC*9oX:WubP 55!5!!'!XXddPRt '327'' !!iFB*8X:*CF9XUpt>*%&#">7'&'&">327&5467>7tBEH#&NKX$W/,0$" D5Hp*G6$"!0,0Y"W!F&'&#GGCuaP'467#"!4676?'&'.5!3!.5P5#$%"//"%X$# 5eeJ(0Y! "X0(Jet*.'.54?'#"&'2767.'32t)H5 X"$ #0,0X"KN&#EHEBCGG&'&KW"Y0,0$"E6GsPX'<6%"'&'.54676$4676762"'&'&&'.54676762$/+z >_ $#R#af#R#)>xbQu 88RK68# 88  vc<*676767632#"'&'&'&%.5467.546A ''+/54<3o8n23'9%%%%bb%%%&:?$ fLLf#&#/:&'X23X'rr'X32XV2c"'&'.54?654&'&'&#!"#!".4?64/&4676763!23!2767>54/&546767622 Z ;:td Z   c   uu  c  2c"'&'.54?654&'&'&+"#!".4?64'&4676763!2;2767>54/&546767622pW\xj IJ \W   8  uP^'#76767&'&/3#>7!5!!5!.'PSJl..&GG&GlHSi7*nK Kn**7OUnm'66'1U=Hd)dH=n&*'$&'&#"'67667 h7Hm^:-3 RE SRQO1̡LHO&57$'&54&#""OER 3-:^mH7hH܏1OQ S #u ! ! j.u-10 3%!#3!Zddd/ #3!53#5ddZd{3 #pph # 3hp&HHT&IIT[[ '#'#'##'x\xxjjxx\x,x\ehhP8\xYY73373737+.x\xxjjxx\x.x\8Phhe\x,OlD=072767>54'&'&'&"7#7676767632#"'&ew@RNJV !'7$"!3!&'&'&'!#!2767676wx !1cbbc1! "1cbbc1" `x]\LM&  &ML\;RR &ML\]]\LM&ZwxZQvcbddbcvQZ[RwcbddbcwR[xV''LM\7=e=7\ML'e;6\ML''''LM\6d 8   2@ @@ 00 ]1@   990@   <<@ <<KSX << Y5!!dx yxUZxxu 8   2@ OO __ ]1@  990@   <<@ <<KSX << Y'7!5!'7 wxy xZwxxd 8ڶ 22@ PP_ _O O]1@    9220@   <<@ <<@ <<@ <<KSX <<<< Y5!'7'7!dxxwxxUZxxwZwxxd 8!!5!! s]xwx]ix]xZx]xiu 87'!5!'7'7!5 ii]xwx]iix]xwZwx]xd 8!7'!!5!'7'XiiiI]xwx]h]xwxiii]xZx]]xwZwxd 8 !5!3# Y#xwxݪ-xZxYu 8 #3!'7'7xwx-\xwZwxd 8 !5!53#5! Y]xwx]Q7ii]xZx]Eiiu 8 !'7'7!#3!7'Q]xwx]iic]xwZwx]\iiu 8%77777773'7'7#'''''''uFFxwxcnFFFxwZwxnF,X@j,,X j,,X@'j,j,,Xj,,X@'j,j,,X 'j,j,,X@'j,'j,j,@j,@'j,j,@'j,j,@'j,'j,j,@'j,j,@'j,'j,j,@'j,'j,j,@'j,'j,'j,j j,@'j,j, 'j,j,@'j,'j,j, 'j,j,@'j,'j,j, 'j,'j,j,@'j,'j,'j,j@'jj,@'j,'jj,@'j,'jj,@'j,'j,'jj,@'j,'jj,@'j,'j,'jj,@'j,'j,'jj,@'j,'j,'j,'jjj,@'j,j, 'j,j,@'j,'j,j,'j,j,@'j,'j,j, 'j,'j,j,@'j,'j,'j,j@'jj,@'j,'jj,@'j,'jj,@'j,'j,'jj,@'j,'jj,@'j,'j,'jj,@'j,'j,'jj,@'j,'j,'j,'jj 'jj,@'j,'jj, 'j,'jj,@'j,'j,'jj, 'j,'jj,@'j,'j,'jj, 'j,'j,'jj,@'j,'j,'j,'jj@'j'jj,@'j,'j'jj,@'j,'j'jj,@'j,'j,'j'jj,@'j,'j'jj,@'j,'j,'j'jj,@'j,'j,'j'jj,@'j,'j,'j,'j'jj,pXj,p,pX@'j,j,p,pX 'j,j,p,pX@'j,'j,j,p,pX'j,j,p,pX@'j,'j,j,p,pX 'j,'j,j,p,pX@'j,'j,'j,j,p,p@'jj,p,p@'j,'jj,p,p@'j,'jj,p,p@'j,'j,'jj,p,p@'j,'jj,p,p@'j,'j,'jj,p,p@'j,'j,'jj,p,p@'j,'j,'j,'jj,p,p 'jj,p,p@'j,'jj,p,p 'j,'jj,p,p@'j,'j,'jj,p,p 'j,'jj,p,p@'j,'j,'jj,p,p 'j,'j,'jj,p,p@'j,'j,'j,'jj,p,p@'j'jj,p,p@'j,'j'jj,p,p@'j,'j'jj,p,p@'j,'j,'j'jj,p,p@'j,'j'jj,p,p@'j,'j,'j'jj,p,p@'j,'j,'j'jj,p,p@'j,'j,'j,'j'jj,p,p'jj,p,p@'j,'jj,p,p 'j,'jj,p,p@'j,'j,'jj,p,p'j,'jj,p,p@'j,'j,'jj,p,p 'j,'j,'jj,p,p@'j,'j,'j,'jj,p,p@'j'jj,p,p@'j,'j'jj,p,p@'j,'j'jj,p,p@'j,'j,'j'jj,p,p@'j,'j'jj,p,p@'j,'j,'j'jj,p,p@'j,'j,'j'jj,p,p@'j,'j,'j,'j'jj,p,p 'j'jj,p,p@'j,'j'jj,p,p 'j,'j'jj,p,p@'j,'j,'j'jj,p,p 'j,'j'jj,p,p@'j,'j,'j'jj,p,p 'j,'j,'j'jj,p,p@'j,'j,'j,'j'jj,p,p@'j'j'jj,p,p@'j,'j'j'jj,p,p@'j,'j'j'jj,p,p@'j,'j,'j'j'jj,p,p@'j,'j'j'jj,p,p@'j,'j,'j'j'jj,p,p@'j,'j,'j'j'jj,p,p@'j,'j,'j,'j'j'jj,ppjp,p@'j,jp,p 'j,jp,p@'j,'j,jp,p'j,jp,p@'j,'j,jp,p 'j,'j,jp,p@'j,'j,'j,jpp@'jjp,p@'j,'jjp,p@'j,'jjp,p@'j,'j,'jjp,p@'j,'jjp,p@'j,'j,'jjp,p@'j,'j,'jjp,p@'j,'j,'j,'jjpp 'jjp,p@'j,'jjp,p 'j,'jjp,p@'j,'j,'jjp,p 'j,'jjp,p@'j,'j,'jjp,p 'j,'j,'jjp,p@'j,'j,'j,'jjpp@'j'jjp,p@'j,'j'jjp,p@'j,'j'jjp,p@'j,'j,'j'jjp,p@'j,'j'jjp,p@'j,'j,'j'jjp,p@'j,'j,'j'jjp,p@'j,'j,'j,'j'jjpp'jjp,p@'j,'jjp,p 'j,'jjp,p@'j,'j,'jjp,p'j,'jjp,p@'j,'j,'jjp,p 'j,'j,'jjp,p@'j,'j,'j,'jjpp@'j'jjp,p@'j,'j'jjp,p@'j,'j'jjp,p@'j,'j,'j'jjp,p@'j,'j'jjp,p@'j,'j,'j'jjp,p@'j,'j,'j'jjp,p@'j,'j,'j,'j'jjpp 'j'jjp,p@'j,'j'jjp,p 'j,'j'jjp,p@'j,'j,'j'jjp,p 'j,'j'jjp,p@'j,'j,'j'jjp,p 'j,'j,'j'jjp,p@'j,'j,'j,'j'jjpp@'j'j'jjp,p@'j,'j'j'jjp,p@'j,'j'j'jjp,p@'j,'j,'j'j'jjp,p@'j,'j'j'jjp,p@'j,'j,'j'j'jjp,p@'j,'j,'j'j'jjp,p@'j,'j,'j,'j'j'jjp,p'j,pjp,p@'j,'j,pjp,p 'j,'j,pjp,p@'j,'j,'j,pjp,p'j,'j,pjp,p@'j,'j,'j,pjp,p 'j,'j,'j,pjp,p@'j,'j,'j,'j,pjp,p@'j'j,pjp,p@'j,'j'j,pjp,p@'j,'j'j,pjp,p@'j,'j,'j'j,pjp,p@'j,'j'j,pjp,p@'j,'j,'j'j,pjp,p@'j,'j,'j'j,pjp,p@'j,'j,'j,'j'j,pjp,p 'j'j,pjp,p@'j,'j'j,pjp,p 'j,'j'j,pjp,p@'j,'j,'j'j,pjp,p 'j,'j'j,pjp,p@'j,'j,'j'j,pjp,p 'j,'j,'j'j,pjp,p@'j,'j,'j,'j'j,pjp,p@'j'j'j,pjp,p@'j,'j'j'j,pjp,p@'j,'j'j'j,pjp,p@'j,'j,'j'j'j,pjp,p@'j,'j'j'j,pjp,p@'j,'j,'j'j'j,pjp,p@'j,'j,'j'j'j,pjp,p@'j,'j,'j,'j'j'j,pjp,p'j'j,pjp,p@'j,'j'j,pjp,p 'j,'j'j,pjp,p@'j,'j,'j'j,pjp,p'j,'j'j,pjp,p@'j,'j,'j'j,pjp,p 'j,'j,'j'j,pjp,p@'j,'j,'j,'j'j,pjp,p@'j'j'j,pjp,p@'j,'j'j'j,pjp,p@'j,'j'j'j,pjp,p@'j,'j,'j'j'j,pjp,p@'j,'j'j'j,pjp,p@'j,'j,'j'j'j,pjp,p@'j,'j,'j'j'j,pjp,p@'j,'j,'j,'j'j'j,pjp,p 'j'j'j,pjp,p@'j,'j'j'j,pjp,p 'j,'j'j'j,pjp,p@'j,'j,'j'j'j,pjp,p 'j,'j'j'j,pjp,p@'j,'j,'j'j'j,pjp,p 'j,'j,'j'j'j,pjp,p@'j,'j,'j,'j'j'j,pjp,p@'j'j'j'j,pjp,p@'j,'j'j'j'j,pjp,p@'j,'j'j'j'j,pjp,p@'j,'j,'j'j'j'j,pjp,p@'j,'j'j'j'j,pjp,p@'j,'j,'j'j'j'j,pjp,p@'j,'j,'j'j'j'j,pjp,p@'j,'j,'j,'j'j'j'j,pjpd?8 !5!53#5!s]xwx]ii]xZx]EiiuP8 !'7'7!#3!7']xwx]siic]xwZwx]\ii 3'#'##-Z-x\xxx\.x\n #\733737#x\xxx\xZ'x\# n\xO'=%"'&'&'&767670327676764'&'&'&pk_V1..1Vbrx`Xk_V1..1V_kpIxXE?#!!';B]YQS@?#!!';BQ9.-\ZnllnZ_.x$-\ZnllnZ\-.)xF!F@RNJV>lmGСBk>DdW0Xdtsݓ.W@#.  -&.%)/K TX)8Y299ܴ]<<999991@ &$-/22907&54&'>5!2;#"#!532654&+CI02Kl>>l5UU5D>kB0GmstݔdXЎW2  5 1Vd22h' %#3 5' :' 73 ٪L^8bb:'B 7''ٛ>PNq'B '''ٛ>PNq^D'B ''>PN'B%  '''tNP'B5  5''bNP#u  u-3!3!!#!#!5 L3ͨ--Ӫ--333333#######5Ϩ---Ӫ---:k7!!  767654'&'$  $'&'&547676h08rtrrtr@rtrrtr VGFFGrGFFG;:rs죟sr:;;:rssr:Ŭɪ:k3?  767654'&'$  $'&'&547676!!#!5!rtrrtr@rtrrtr VGFFGrGFFGssB;:rs죟sr:;;:rssr:ŬɪKss:k3?  767654'&'$  $'&'&547676   ' rtrrtr@rtrrtr VGFFGrGFFG]x3w32x3B;:rs죟sr:;;:rssr:Ŭɪ3x23w3xuM %' o& ' % JuM327!5!>2&#"!!"&' ;E 2&#"!!!!"&' ;E $;E Ϊ@z٨zuM&#"%"&'73275%>2";EC;EJ綠mzzuM*3&#"&'67"&'7327&'&54767>2";EIq(P >6D;E]InoSu=,HK%)AH!+p$ z1IosV2";E+@/V]H6H\nUm;D [>wfP3,,I6x/Ur]HH]lVzM>wrN3 F4uM!3#!!>2&#"!!"&'732w~9F 9 }9Gr0}}uM+3#>2&#""&'73273264&c)~9GcBnnVs~9F (6o~ç|K|oU}uMp.3#327264&#">2&#"632#"'"&'z;E-8pƖqS;E;DܛWI3>6я]z!zuM 13#64&"327&'&767>2&#""&'˔֐;E]InoSu;EcBnnVszяϐ-1Io7sV2&#"!!"&'73273!#3;~9G9G ūI}ޭ{ tMm-&#"!2#567&'!"&'7327!5!>2";Ed_``!;D ܻ`;`*I6ƌebIz`:H:`*F4uM#&#"7'"&'7327'7'7>2";Exx;EzxXyxzyxإzuM*327#467>2&#"#4'"&' ;E-A 4yy;E Z>Vy|-2PIϼ+zEa82JzuM'&#"63"&'7327&'&53>2";E*y;E\?Vy~+&8'zLFaI1zuM>32&#"#"&'7327!5KL~9GALK~9G⧅}}gkb>32&#"#"&'73275!KL~9GALK~9G⧅}}Р?j&  P$j& ' PW P@$ 5 5FѶeѦ 55FѶ///m' P/& P'' P'' P' P/ ' PN:A%#"'&'&'&#"5>32326#"'&'&'&#"5>32326 5jbn ^Xbh`n ^Vhjbn ^Xbh`n ^Vg@PNE;=LTNE;=KPD:32326#"'&'&'&#"5>3232655jbn ^Xbh`n ^Vhjbn ^Xbh`n ^VePNE;=LTNE;=KPD:327&#"56767326 5jbDS4WVhjbm\Y@/Xbh`ES3VXbhZmMp[Y@1Vg@PD4KUNE;@LTNE4LRN"*,@J^po_N5<#"'3267#"/'7&#"5>327&#"5>32732655jbDS4WVhjbm\Y@/Xbh`ES3VXbh`n[Y@1VePD4KUNE;@LTNE4LRND:@J^T 5!5!-5 !5!uu/0\^ҲЪ~T -55!55!usҲЪ᪪/0N%#"/&'&#"5>32326!! 5jan^Xbh`n^Vf@PD:32326!!55jan^Xbh`n^VfPD:323265-5ian^Xbian^VgsuOE;=LSNE; =KJ/0:ҲЪ !(#"/&'&#"5>32326-5 5ian^Xbian^VeuOE;=LSNE; =KJҲЪ/0, -55!55!us%ҲЪ᪪(/0٪, 5!5!-5 !5!uu%/0\~ҲЪ^6 5 5 -55uu/0V/ҲЪа/6 -555 5uuҲЪ۰/'/0K/& 55p/ѦѶ& 5 5p/om//&' P/&' P{ 5!5 5!@Ѫop9{ !5! 5 !5!@Ѫ555@pNpop 55 5@p pU(".#"#"&'5327>76325hV^ n`hbX^ nbj@TL>7632 5hV^ n`hbX^ nbj?TL>֪VJ<:DNTL<:DNDop$+5!5!.#"#"&'53276767632 5hV^ n`hbX^ nbj@>֪VJ<:DNTL<:DNDf $!!!5!676762!!&'&'&!!C.8d 6WYYV7 e8-;Z{+DD\93[2332[0<[EC,W7!!%5$$}y]]x|W%!5505%$}$y|]]W !!'7!5!%5$ZZ N$}qPP]]x|W !!'7!5!55%$ZZ N}$qPP|]] K75!5!%5$!:[]3֪k-QtXVv K75!5!55$%$][:!3֪kVXQ-qK!5!7!5!7!!!!'%5$&`ȉ)P"_=6!:[]ss1st-QtXVvqK!5!7!5!7!!!!'55$%$&`ȉ)P"_=6][:!ss1stVXQ-y:E#"'&'&'&#"5>76326#"'&'&'&#"5>32>%5$ian ^Xbib` ^Vgian ^Xbian g!:[](NE;=LTN9 A=KOE;=LSNE;C E-QtXVvy:E#"'&'&'&#"5>76326#"'&'&'&#"5>32>55$%$ian ^Xbib` ^Vgian ^Xbian e][:!(NE;=LTN9 A=KOE;=LSNE;C EVXQ-6A#"'3267#"/'7&#"5>327&#"56767326%5$jbDS4WVhjbm\Y@/Xbh`ES3VXbhZmMp[Y@1Vg!:[]$PD4KUNE;@LTNE4LRN"*,@J-QtXVv6A#"'3267#"/'7&#"5>327&#"5676732655$%$jbDS4WVhjbm\Y@/Xbh`ES3VXbhZmMp[Y@1Ve][:!$PD4KUNE;@LTNE4LRN"*,@JVXQ-7 5@pppo%5555òi ' '!]#\e#N\#]x#L   !77 ! \ݿ##N]##4 !7 7:\#]x#L]ݿ#\eL#1 4  %''' !]ݿ#\eL#1\ݿ#]j#7P~ % ! !!5 5!3!   7?~% !!3 *^V !!^*  ^V!!!^ ' '!##L  !  ##4%7 7#L4L#1 4  ! L#1#7P~ % ! !3!߆^V ! !! !ECuR #7!5!7Zxx/{xx:xu-R '!5!'xx vx:xH% 7!!7vx{/xxxƪxvH-% 3'!!'Zxx vxx$!%!!W7 r$!!!W7 $!!,7r32 &}f[_ &}f[, %$R/ %$R !2+##5332654&+!ʿ[qrqqϐђАfT$@  $ !? %29999991@&  B  $/999990KSX9Y"@&]@Bz%%%&'&&& &66FFhuuw]]#.+;#"&! 32654&#A{>ٿJxn?M~hb–m؍OH#(07#5#"''7&546;7&'&#"5>327354326=-?\g`n;) T`TeZx_958>cc3Vfa<}NV{ E..''rOs+Ax.ٴ) 3{ B333#;#"'&'##53w1ѪKsQ fև3͏oNP r>7!#4&#"#3676323#d||BYZucce22wxLj%3###3!E3A1wH33 3###%̟8ǹiEL#\ !!#!5!sP=g՚oAX` !!#!5!qjLl}e`R%sw-@ 221/053#5# !232#"MT+焀\\xEEf! !+53265##-}-MDnh %!#3!3҈Rsw%#3>3 !"&#" 7MT!焀UHUk:E={0#3 632#54&"$\^TރQr)m`Tῆrr:T*D  # #3 3 67632#54&#"f:9:76&7#"733%3h kjwrq-xўD|Hv`Q~PH_##"$'5332654&76#"#3$32Ұ $Zh9dR˟YP[yfPu .63 ?# 6%#"?3327632#5&7$'E ۬99 Z. IBauyh$4Q ( dcZe~PA45Gx=%' iK_#6#"#3$324RhiFu~ufP_#6#"#3$32$32#6#"UlhQtxlokg_Q,X $6#"72767#"7#"'53;73$32iO_{k'jh jWz{+Ά8B~BADk6Yl4_ "6#"3%$32#6#"#7#"73$32eRr;'ih삸{$, }Q,ȯW_!#6#"#3$32h5^ ,hs 6#"32%##"73$32oNqm]I4,ǰP9_ "6#"32%#7#"76##3$32$36eoNmIy[fy_,ο}_m )32767$76%&#"#"=3326323Esyx cV&J]z7Sr#1X-;=m;@@]krw?|ӆP[!#6#"#'! #5&#"$36is" ~ ,8|{aAth_%#"7332%332%3#7#",r\iӮ-Ӯ-hr_%#"7332%3#,rs¯Ӯ-##7#"7#"73327332%3LB\Nt2־Q BizhNs^y'h 3$32#6#"#7#"73325mT@ rhsÊr2UyQ,ԯӮh]332%3#7#"7M7ph^ʈa&Ԯ-fJ_6#"$7327! 3$36je!Duc2x-+9KFƺF4DZhJ%#"7#"7332%332%'3#r\h$xmЯӮ-Ӯ,i{"_ -6&32%6#2%#7#'#7!"3$32$32oGp }YH֖t U@,tSP-mnϯ-;,ů1.dE%##3%3%73#mgQ6Z{OBUE3{P\oR{oS{x~P\_$32#6#"#3{ɇ^qQ,_hr`%#"733273#rɜگӮ.@~hh_%#"7332%3!#532$‰rir;.UIYӮ-W׋PT#3$32#6#"|jcKJ={th,8Q,VA`27#73$32 #6#"[wX$(+~sez3殮z3 7# %33C8id,*>'jKp'2ֲ\-XC)P`!6#"#3$32$32#6#"s_`\Dui‰qt,`Q,<\_ #6#"6 %'73 &!"#3$6")g]سŝ,*#s^Qzn(`a_3 73!8`!T^$97C%F`$# /33 73$'#%&'R$waְ;",dRmQr54'&'&s~&&~~ڢ~.]=@N\N\.]=zz❞zz}qa !SM!R}|pas?#-n@.  '&$ /$ .9999991@ .'& ) )./9999999046$327#"''7&7&#"4'32>s~&Ġn~ڢĠnՑꏧw֜\w֜\zvijޝzwkj!^`|g^` .@   <<<2221/03#3#3#3#):@  1/<0@22 # #3.]F; -@    1@  /<<03!#!#!"9q><@  9/1 ]@ /<220KBPX@     @     @ Y333 # # \Xds3{ 1@   <2<2??]1/<2<20%3#3#3#3#\ 7@  91/0@ BKSXY" !!!!&TdD՚ohh $@    1/<<2203#3#3#hhh8o !@  /221/220!!!!5!!o&.-ժo1/,@! ',01*$ 022122<20!"'53 !"563 676!2&# !27# '&%4rmyymrO4%%4Trmyymr4*B6!*:'(8) 6AB6 )*!6oP@   <<222<<<<21@   /<2<<22<<2203!3!!!!#!#!5!!5!!n""xxyyrr3@21/03!!!ժ,o7@   /<<2<<21@ /<2<203!!!!#!5!!5!CCPPxyr7@ KTX@8Y221/0@ 0 @ P ` ]73#3#>@ 10@ BKSXY"47!5!32654'3! $x˿ßwNetwc #/9@1E- !'E0<2<21@ 0*$002654&#""$54$322654&#""$54$32,,,,PIIPPIIPPIIPPIIPs'(@ ) (1@ #(046$32#"$&732>54.#"s~&&~~ڢ~\ww֜\\ww֜\zz❞zz}``}|``s,P@  ! #.# -9991@ ! ((-99046$32'#"$&73277654.#"s~&&~l~\wj\ww֜\zz➞ikwz|`^jI|``; -@   1@   /2203!3!#,dq9d (@   <<<<1/03#3#3#QIh ?@     <2<2??? ]1/<2<20#53#533#3#3#h+Is'+>@- )(( ,9//)]1@+(#,046$32#"$&732>54.#"3#s~&&~~ڢ~\ww֜\\ww֜\zz❞zz}``}|``s>,P@  %$#& !.! -9991@ #&$%((-99046$327#"$&732>54''&#"s~&Ġn~ڢ~\ww֜\pw֜\zvikzz|``|?l^`sr%1=G@8&,20><2<21@/; 5 )##>9//0! #"&547 !&54632! 32654&#"4&#"326sS_  _mz,,,,,,,,gs'O;H66H;O'sz<11<;22<11<;//d #@   <<1/<203!!#!5!IIjk=;;sr3?Kf@F4%+6:0L2<2<29/<<1@=(I C (7##11L9///<20! #"&547"333###3&54632! 32654&#"4&#"326sS_ ̻A;z,,,,,,,,gs'O;H6ߊ6H;OO4z<11<;22<11<;//;@   2<21/220]!!!33##!!!>ժFh*;@ 1/<0)3!3;+y=@ B <1/20KSX@Y!# 5!!!8ks#O@%$!  /<<22<2<21@  /<<<2<<<2032653#2#4&##"#3"3ʊyʊy+VVF%F.@ KTX@8Y1/0!##u-s+f@- ,&'  #+ /<<<222<2<21@+*   #*'"/<<<2<<<29/<205!5"3332653#!!2#4&##"#35ʊAyʊy>FV>=VF=6-@ 1/20!3!3M-n^$36767#"&546?>7>5#53 Ya^gHZX/'-93B$S #C98ŸLVV/5<4-5^1Y72&  PW:X!##:o#5!#&X3!3hXo!533oXKN'6'6'6'6'6&6'6&6'6&6&6'6&6'6&6&6'6'6'6&6'6&6&6'6'6&6&6&6&6&6&6&6'6&6'6&6&6&6&6'6&6&6&6&6&6&6'6&6&6&6&6&6&6&6&6&6&6&6&6&6&6&6&6'6%3F %#"3!"$54$;3@8ʦ1X'7)5!3!+s*j.v !#! !3vb w1Oks'&s'H\=#)# 031j/6T5;.'3;3! &546#"3A{>ٿJx8w~bw+؍hh9;+ .#"#632[hq`QQ,D:=;;< !"3!"3!"&5467.54$3!DՔ| #orqp ˘h$h(E )5!!5!!5!EP9ª,s2)8)*@ 8AKTX8Y1  /<03! #4&#"!!ˮî$*\u sZ@  21  /0# !3! !5aPh//+ji>!!>>!##>@>'_>'_# !##!!>@##!!!!>#!!!!V>֨`!!!!VVrid{jXn`+v)4>@01, *$6E591@ $ *052220#"'&'&#"#"'&547673!27676323 4'&'3ft[na`zxz{n[tfCGo~[U]LKfdKJ]U[~oFCD@@DDDk63366336Fk!<@!  # E"91@  ! "2220!"$"# 33276762324rTRrƒ>IxddyI?ВP8[ 77 [8G<r&,>{&s   !3#!! ! H0x:;hLH+fabgp{ "326&33###" rhո  983#!#!#3! !9҈_:o%+kj{"-#5#"&547!#3!63!54&#"5>32"326=?/j`TeZ߬ofasP`A"..''f{bsٴ)e767!!3##!#!!&aO)p(?x4&A D+k`76765!!3##!#!![(bR-f}v(UԓR:d6T356765!!#!T:WO)fb0d+L`356765!!+!L3DS{X^}з3oP! !!+##-}) `! !!### >?h˸ʹ`3'Ps'y2qu{&Ry.se3#%3# '&76   1L  F<HqC{3#%3#"32654&' ! hJ IHn98s j&&'yryq{'yo'y.':W '/7?GO%3#%3#3#%3#3#%3#"264"264$"264"264$"264"264$"2642+ '&' &547"#"&546;&546 676 3#J"{iihiihiihiihiihiihiihG4UU32UU4IF]97R̬\dfʬ\ʫZee̫ZҜf!!!2+5327654&#!#!qmL>87||ժFwrKK"9+32+532765||BuƣF1n!&edH08L*!!!2!"'&'5327654'&+5!#!^eicUQsj~cd\]ժ˚8+lhzy$1KKIJJ+7L402!"'&'5327654'&+5!;#"&5#533!AicUQ^cdjTmcd\[jKsբe8+lh%12KKKJN`>¨{Rg|1&'&547632&'&#";#"32767#"$546p<HmmFEMUUU8%~` !!!!#+`Ӕo{V 3 3#!+!# ! !J9҈_҈_%s%>+{'{ 5@M"326=%#5#"'#5#"&5463!54&#"5>3205>32"326=63!54&#"߬o?nQ?`TeZxeZ߬o5y`[A3f{bsٴ)Lfa' fa..''~D''f{bsٴ)hn< - 3676! ! '&'!# !  J-p;:xżP.g%H}[[Xr%H{{{"-82 '&'#"&5463!54&#"5>3 6"326="32654&y7!``TeZ*qO߬o{ǝ>REa..''f{bsٴ)nq !3!2653! '!#%{J®sv%_r\4h{{(3%#"&5463!54&#"5>3232653#5# "326=H`TeZ||Cu߬oߍo..''{fcPf{bsٴ) !!#3 3%Lj_:+{N{ ("326=5#"&5463!54&#"5>323߬o?`TeZ^\3f{bsٴ)ͪfa..''5 )!#!#333#%~gY_:gci5R{N{"-0!5#"&5463!54&#"5>32333#"326=!#u?`TeZxgƚÛ߬oGfa..''~mc3f{bsٴ)V !+53276?!#3 3%lKMJ|ثL*+2_:q?=$%2@{VN{'2!5#"&5463!54&#"5>323+5326?"326=u?`TeZ^N|lLT3߬ofa..''wj8zHB3f{bsٴ)s'{ j33#! !##53ʿ3ʿչwH1r3!!3 ###53¹"%kǹ#Ĥf 37!!_(^M*c37#xIS 33#!!#53ʨ_YQx 33###53YR j% 3#! '&#5376 !&'! 76;:~ ż ~HjiF wvҵCҤֆ {'23##"'&'#53676"!&'&!3276o ~~ oV?s?VLVVM{~͐~sUUu%gstgs j$. 676! ! '&'!     ':/##.;:xŽ.$#.yHH5==5[[4=<4HHHq{ 1"32654&!"32654&'267632#"'&'#",nn霜ǝ98 32654&#%!2+3###538ʿ/ϒqrqqĐV{&  533>32#"&'3##5ܧ$:{{:djgdaad̐2'!2+##"&'&5476;2654&+-\0:32#"'# XSܧ3),4:32#"&ܧ$5:{{dj+daa 326&#332+3##5#538ʿ'ђ H^ddV&  533>32#"&'3##5ܧ$:{{:djgdaad̐ !#!5!)+Vy{3#\{V3"&4&#"#367632o͚Qfe}–}CBV{3"&'4&#"#3>32dJlʣ||Bua ed#Ib !5!5!5!b>>I5:@ K TX8Y991@ _]0 P]3#5qeo7@ KTKT[X8Y10@ @P`p]#o+MVT&#"3733#;#"&50# 76327M\P=x1F<5K9p|t !33#!#ĪjA{3##4&#"#3>32d||BuR`ed9(%!5!#$'&''7&5!2.# !26uƍ(T+p^op+jkSU̒%nHF_`_w%V{&0:%!"&'5326=#"'&''7&53253732654'&'&#"ZaQQR9|~Ch!t|9!.R 2R,*[cbSp#c'8>:bc2c==W367'&'&#"dǹ!BucJ! 3>|\jTo2c=edxY1c90u@O_$+#.+#'7! 7-&'&'#3767A{>ٿJxʚ+~I+,JJ= ~hb3BFk>dNU ?(B(D8cJ{.#"%#'73>32JI,!-!:.˾4bTo2c<fc/.#"%!"&'532654&/'%&'&54$32Hs_wT"+W6lj{ra+eM0ei76vcew'8l0/EF~n|w".`&'{/.#"%#"&'532654&/'%&'&54632NZb-!K,RZlfae$O!#>&Lf?((TT@I! cc (L##55YQKP%ca&J#546;!3#!#-v"0^i1Fd+93!533##!##53!5jj2ii}}}\rk\\~~G'1.#"!3267#"&'#"&54632>32$"32654ogW`usC~>?Ce3-XX*1Zml^]lUdaY2jpa==><><<>L⁂po`w #!5!!5Pp+ɪF #";##"$54$3@/+X 3333! +m3#mD U%3 3# # #3>:9w+X10!5!-ЈX'3I(sInhX#'3h'OW`4X#'3v5]dDZX#'3 |;d07!X#(ẌI$Qh$Rn4$S`$TnhX#7OhWh$VhQ4RnS`4X#7]vDdn4$[4V4QdRdZX%#7d|!70`$`n[VdQ0<0^X133ֈX: #'+/37ڷ/$0(7,48<<<<<#+ 3'<<<<< <<<<< <<<<<9̰XKRX8K bf TX30<xN\adUrn$.%675!26! $547&# !&'$! $ ce'4'T8 N!Vm0w_c-SR@@]wlo6! 3!4!"#! 6"Zn':SE/Os n6! 3! 54'#5364##'! 85<'0[%|s}Ns(P n &"673! ! 7! 547&5! 3hf=G=e H6ߴ_c vK]XKRF.%@n "! ! %!$#"! !663 I W .<s~\Fy-Cm!n## #! ƤۮȤ{C~fSyn6! 3 5+53$54'!6+<'0ܯd$DxxfnU(}1(!n6! 73! 54'#53$54$53605!s]{%0߳?C-?rݲynn5% 3! %5'6%5%5!!3`]JVO;2x}ΰ_n&#5% #! #5%$!$7 63 } u|bbIK^(w4J! 3! 54'73J#+5-J-RkmvIK&|nj$3254'##533! 547 X֒./w@DD\u&IR2'! 3! &#"#&'#53263 '48DB§Kiq.5ThMn 7! ! !#"$53;265+5765!"%5!263 F2dڂJ|;o&I+mvyف;͉5pr)Z{n5! 73! '#'235#7E!?дB<mݩN$^n*7675!23 #=! # !&'$x`ىxvS8߱b" K a`TTr@mMpRcEn4! 3! 54736734!"!?5 Dc (Ԕ( t9n4#&#  ! 5!4#"#54!5 63 s`1 ㈲6UKIr] =`|::J74#"6#=&#! ĮځUuh=^ub|p# 325&+53$54$73(r׆̫Z ${Hp,=ceTn4 &# ! ! 5!63zD䦔PpYql>J=RII$!26! #54#"67 ! 3! 5!"[`%礧 0Iωy:DDUwqR`n\##3#! 73! # !3'33;#"[?~W#;H 5^g^%"Di5N-6ϥGJ !! 7! 363ˢ9&&UXvn_%N2!%!5 65&%'%7764&#532MPsyPjJ@JKp̊nz(ES8KJ7n5&! 3! 4+53254'#53254!#53 35!!-ӚmvvQIųyaNGnx>& 3! 4'#5327&+53654!5 #\0'loM}ҥSg~\VNn_6 $! 27#! 3"%#',$*۾?.hxx`S^;^On4! 53! 5+53$#%4%%({[FLxqnB ! 53! !#53$#3!2735! -ng!!} )掎F`own%!$! 5)4! ! %#$! 3#3&29 # ㌌bxl!j`?~ʸn6 ,4&#  !"'!5 5!"! 5$7 6!| '/ڋ%v'Úk݇lx'Ŀz#32%$76#"##"6%3$32Bpr.HZd D]]ǩ*H$OH5*N,FhI(2:(jnd(0<$47'&'#&%'67$%&573265&5#"6kVorb32#4&#"#9`M1Cuȸ||MM 7BuƸ||e,'"xMfca?'Gzed\V5<!"'&76763!!32653#5#"&5#3!#"&5332765!"3ە^SWsv||CusCuȸ||WVۃ^SBWLa{fcBVfcf__{{V H!&#5#"&5332654/&763!6763232653#5#"'&=4&#"#9`M1Cuȸ||MM 7c%Zk>8nClbd||xe,'"xMfca?'Gz2XO{fcx{䟞[B`&0NV 332673 &Vv aWV` v ޞKKJLJ[`&DN~`27676=3#!5!c\@&@exj`Qq73>53zK-b[;Iwz?-b\;2Nh`i8^X dwNji8^X VnZ7X`##!5!ʺe/я`#4.#!5!2%# '>ni+ՅĔ^;kjt@,?]ET`X`!2#!5!2>4.#!Xז\##\(qf==fq(`AiiA*HnuunH*X!!3!\\CL$`)!2!4.#5Ӄ5W !3WmO`<٧^h=)X`#4.#!5!2Tt7ӂ5m)<``#4.#!#"'53265#5!2 $@pP${5NA&G.Cl,^`Jce:%r3C-!5!3!----XS&"@NS&N@XS'X&"@XS'X'"L@`&'u`&'`&'XH`'(X`'!)X`'*`&+Y`' ,Y`' -zk&/Y`' 0XV`'1X`'2X'Q3Xp&05Xx`'!7`&8Vd`&q:`&|;X`'Q=V_&>X`'?XS`'9 @`&At'",XH' q(X' A2& ;X, ##54>7#33>53r`#8!2-A,x#8"2.@,eX5AnEQ`Q2,  BL 6AnE RaQ1, '19 '193 X&41 ~X'15 '0 '03 X&40c ~X&50c '2 '23 X&42c ~X&52c'19'193&41L~&51L'2&32&42cL~&52cL'L&3L0a&4L+p~a&5L+p'/x~\F&6/x?&7/,~ x&8/>'2xx\F&62x?&72,x x&82> (f'1[ >f'1}>\/&1 8>>/&1 8 (f'.[ >f'.&8\/&.88>/&.8 (f'0[ >f'02>\/&08>>/&08 (f'2[ >f'22>\/&28>>/&28}R'.+]}GR'.+}'.]}G'.}'/]}G'/}'L]}G'L '/ _ ~&/ /&_L> ~/&L>)7%#"'$47332767654'&54767;#"'&/cͷ?Ahž#62 #dGG&+@XA:g!axL54#"%473303576 !! &5"'&3254&'?TKJ&|tD.^(.E:q!&PET*4pHuJ6>(E&7 kcCryblBc5/iCp4  '/qFl&/qFl&/Kl&/K @&qx 6&x @r'>q 6r'> @4&q 64& @&q/, 6&/, J&r1'1 X'14 ~X&51X %+53276=3+HZ#c,1VV,1jٻ~X%+53276=3;#"+MZ#c,11,c7nVV,1jj1,JrX&Q.c~X&R.cpn"56$3=gi~wun52&$=Ԛuw~ig* '/&'&#"#67632O,$e5Fqp[?8WH7  $0GJI  '327673#"'&'O,$a9Gqp[?8W7  $,KJI Pt,l&it,Pu,i,k ;#"'&=3!1,cK\WL71,\W+Pv,Pw,l'w,iPx,l'x,iPy,l'y,idz,l'z,i<{,l&i{,UO'|U&|l9'}Ul9&} @'}>q 6&}>l '~Ul &~'}>r&}XD&Q}+p~&R}+pyU 3;#"'&1,cKPWskj1,\e'-9&3-9X&4-~X&5-'.p^&.^ '. &3.&4.cR~&5.cR'/&3/&4/cR~&5/cR (f'-[ >f&-\/&- >/&- (f[ >f0%3#"'&'&'!27# '&5767"#"5$3 "(1{R=IrbJIԖ^` __&m3HZdP^vc–e4)?6 [_w\/&'&'&5672+5327676SSgURHKLXJKݣdht^#4b4bBPH:jV>/);#"'&'+53276767&'&'&5672~AI2hrBV~(;E)Kݣdht^eSgURHK 4b)N"w6a.%PH:jV# ('-?[ >&-?\L&- >L&- }R]}GR &'3;#"'#"'532767654"9aRQS,cKa].-fgsT!"#?zNuIS,!&* 1p*D}'-E]}G&-E b&_ ~&3;#"'!5 767654x I*eK2D0# &pgM,>ꅗ:H~ b'-q _ ~&-q a GF%7653323;#"'#"'&''&'#&'$473327676'&/3N0%@nS,cKvDm% I01_@8'TPxmil_Qb_y^@@$:|_2&aS,`[ F{GHܳ&%0l}=J<~ 1%+53276=3327653763#"'&'#"'&+8LcKc,P,+hm,%@n\Kf%#?70`DAbH<;!.,Pd@dczg2&q\ =!1(78#"'&'#"'&'+53276=3327653763;#"'%#?70`DAbH<)+8LcKc,P,+hm,%@nS,cKvD =!1(I;!.,Pd@dczg2&aS,`Z '/ a G&/  &/ 7&/ c <I)"'&5#&'$47332767654'367676;#"/"3276'&'&u&4-JXPxmil_Qf[+!' (s{lHX}a*=RKgL~큻%MGHܳ&%Dl7(2l^F"%GMF ,\v7Ql?[F2 .327654'&#"!"'&'+53276=36767632Ш큺%0LJNA'fKc,P-e_KUskl?[F*#=,PdrNP2T?!'Dmx+8)"'&'+53276=36767632;#"/327654'&#"JNA'fKc,P-e_KUqm*=RKg਑큺%0L*#=,PdrNP2T?!'DKH ,\vl?[F '- c &- 2&- &- e))5!3%632;#"/%3276'&'&#"@o\Dui*=RKg큻%0Pz\?c!'EMF ,\v?]DQx %3276'&'&#")5!3%6329큻%0Pzu \Duiʸ?]DQx\?c!'Emx))5!3%632;#"/%3276'&'&#"8 \Dui*=RKg큻%0Pz\?c!'EMF ,\v?]DQx'-Re&-R&-R&-Ru *gu %+! '&7.54762;# '!2764"[b=D}a_[9^DU)k_1ocz2t*n@00@p[C+ @Mkl=v8`3$*727&'&5763"327%+5SF7J \X];d}M4F!Ť$/%+532767&'&5476762;#""654'v`kB;(aD hYYh MXD=p`vʨ4/gg/($'UZ'-)74--47)-'bM,(U __ u F'-wgu L&-F&-wL&-'-~\L&6-?&7-~ ~&8-kH'.R~k &9.k?&:.,~ ~&;.8l!D#"'5327654'&'&7676'&'$54733276763;#"'J&P DfXRNB8D-<9_h$$EB|=Q#!v+6(  %{{qe))5!27654'&54767;#"'&/66-62 hGG&+@XA:g!a_h$$EB|=Q#!v+6(  %?+)x.m#$%653;#"'#"'$&733276N1,cKpNyUcE@A(IPmI~jkj1,3.(B"[\ss~B"5 +5327653WPKc,1se\,1j%+5327653;#"SMKc,11,cKVV,1jkj1,^ngt5%327654'&'&#"#"'&#4763&547632;#"bzL,5;(.;D K2KxAZM\HT((&iK*9:X DD(PNNOmf7*(?$GC,,m$%#"'+5326767632%327654'&#"dan@ht4W^Q[a>/4(*X.[4fb0G1P8TYNE5EK&)/4:''5)24fb0$#1P8S1>,E5EX !a%H'-?  +&8-?&'-R4~'-R5p^ $&'&'&'3;#"'&'#"'&5476 xRot$8pKZI-&8:m*12e CY>)2'+eO,3;I0D-=67654'&#"27&'&5476&'5#"'+5327654'&$"':A4N--0M,Q@(Jxb 41}! @H=.%4-+#%v iEN@TSZ 'D49g=ql)D%'i.C!v-3j  ;AWE L9P)8K6(S/VL_+Y9K1\SJr765&'&'&54767632;#"'&#"#"'$4733276L[/,4PT*uW ##rpl$-AIqYhu?AB[M!3!+ (;=A<^ĸ#0{bV` )gZZrN J'. r '. X&Q.c~X&R.c.&|,.&|,&},&}, &~ &~T#"'53273676537M͞jK`Uq%BUG FA+7T#"'5327367653;#"'&4;IʡjK`Uq%"@Pif<[A FA+7DT)TL* 35'5467676?67654&#">32,X\"$߸g^aOl39ZZ8L{4<+VZ@EL89CFnY1^5YVed 73!!d00#N@ | $327654&#632!"'327654&#|4w=Ưވdudo^,~#r;BYWa{zzp4=8hd  kxww 33 !7>'.'Z '8ZA xzebcz\  #%%|  6uSXSX #%| *uSX !#3!53#Xմm8 !!!!!p+E$## $&6$ 67!!!!&zzz : zSN{{ : {{ 0 |3#ȴ+ !#3 |x.xq={C #3|M3M  3  #t8/.R9/S.Lw3  #t8R9SL !!!!!!!|p,Dܴ$E+!$$o$'64."26 $&6$ "&462^^ޟ^^zzz : zRtRRtRߞ__ߞ__X{{ : {{RRtRR$54$!"#63 ( 57~bYfԶ* 33 3# ȴ^+_C 4."2>#&'.4>2fH}}HH}}zf@HGAhxyy|HH|}HHL;%w%]a{w DD663! )327&#!36'hPcp~qAA k{qS3%!!!!!!-x9vq dddsd !!!!!#3#oQn.ddqs&&$#"32767!5!# !2deVRuu^oRaG@;@&5dSUmnHFcIf3%!#3!53#.nXddddq dddd fY6765%!#!53265-V?O?nqd J^ dd0 !3 #!3pdw@1q 2 !!!3ddo o !#!! !3!3_Gbn}qR+q  r'( ! '&76 7& 676'&&:żGlllli$ #ab2222jT%%5$c$-6&/.4%&  %5 64&/.$ Pdo&nŢmngzoʷ-[ʚ)'NXd''pui$2Xf| / 3%!!!!rpq ddq $!&%! 65! X!!Y fqba@`|gd5\*$ 3%! 3!dq d+D 3!3%! ! 3! !D5D:9:9d|q  d+l 3%! 3 ! #(\~vbL:H|dq d22{ 3!! #3ndp29V{{",34&'3!5#"&546;54&#"5>3 5#">76/=Kd?Vu`Tw86/^b;:gCzӆ]YfaH..t''UNHGgwt-!>32#"&'!4'&'676763&#"327N:||:^,<<,9RKM_]daadt= z =OsKTdihtJq{#%#"!2&'&#"3276%M]-ULEmGJXHCQRHV,${z$d$$>:##dWS%&-!!5#"323327654'&'&#"N:||v9,<<,^(]_MK^daDDaZKsO=  =Td6Jthio}{!327# 32!.#"}K_mk)#i̩J@b]u-)8 CqzӾ/ 3476%#"!!!#5354763g.9:9|WX -8J_D8d97ddddTVqV{#.=65326=#"325!!"&32767654'&#"jlQR:||:Nry^,<<,9/KM_]=ʌo,*qdaDDad-w=  =OsKihtJH "34'&3'!>32!4&#"! GS5‡OIƁkk h@[:Lded\ПU5 33#!!JKOhV #676#532765!3#%G(=1l$%OQRaеT0Hd01``2 !3 #!3OHіmdi#L&5#"'&5!3J=(G%RQOLiH0T0Z``~J^d{"&1<!>32>32!4&#"!4&#"!3%34'&%34'&OIƁԝTށkkkkd[ GS5 GS5`edJv\П\ПUh h@[: h@[:H{ "34'&%3'!>32!4&#"! GS5‡OIƁkk h@[:hded\ПUqu{ #2#"27&"676'&s3x33x3d4'pp'3(pp({98  kp-$-R-ۀ-qV{-%!!>32#"&4'&'&'676#&#"32N:||9,<<,^؆]_MKdaaKsO= z =oHJthiqV{-%#"325!!3#32767654'&#":||:N<^,<<,9(KM_]daDDad=  =OsK2HHihtJ{3'!>32.#"!N:4I,hdfc˾zo{E67654'&/&'&5432654&/.54632.#"#"&'i'K&'q4=B%%U+.39GSOjqL4vfLJ\_opPx3Zl=vf03"3;@{R?Bsl37'*7CoT78^UNO,, z1$YXDL#/%%7%&7#!!;!"&5#53*\{KsբjU|7N(dUNdudTD` "%&'&5##!5#"&5!3265! GS5CIƁTkkTS hl[:hded0=` 3%! 3!YT^^d\hdTV`3!3%!!3! !bTNdhhdjjjL` 3%! 3 ! #U|p|[hd-s=V`7%! 3+53267>^]_lP|XQ+ۙdi8{dCYXb` 3%!!!5!\vwhddhddh$%s'&'(#)s*;+f-j.j/031s23s4T567)8h9D:=;;<\={-{DEq{FqZGq{H/IqVZ{JdKyLVyMN9u{Pd{Qqu{RV{SqVZ{TJ{Uo{V7WX{X=`YV5`Z;y`[=V`\X`]   6/&"27 d3{44{3s s#Տ0,-k37!!5!5%6bJJgq ddd HdH(7!676'&'$32!!7676&#")`"LlDbZE0Q](=ymd͕@9\9pd9hbiddAbs$*0"'5327&+5327&#"56325654&'>54+!ĪeO6?;2:L uWEdJj D d <h@Ѳ|!ŐUl$yXZ#3 !!3#!!5Qpq3d\#66'&#"!! !"'532gd1jKEн܁\`I Kd# F_h$$EB|=Q&v+6(  %z|qe #!5!27653WPEc,1se\,1j<m%%#"'#!5!26767632%327654'&#"an@h 4W^Q[a>/4(*X-.*4fb0G1P8TYNE5EK&)d%&H-Rn1%+53276=3327653763#"'&'#"'&+8Lcc,P,+hm,%@n\Kf%#?70`DAbH<;!.,Pd@dczg2&q\ =!1(*?'-~7 .327654'&#"!"'&'+53276=367676324큺%0LJNA'fc,P-e_KUskl?[F*#=,PdrNP2T?!'Dmx?n&/ &H.]R'/]RHL&-q'-? F'-4'-}"-'7G3;27&'&5476&'5#"'+"'&327654'&67654'&#"1,c4N--0M,Q@(JxPWFb 41}!$"':Askj1, iEN@TSZ 'D49g=ql)\e;%'i.C!_\PWskj1,h$$EB|=Q&v+6(  %z|qe\e/%327654'&#"#"'+"'&53;26767632>/4(*X-.*an@hPW1,c4W^Q[aE5EK&)d%fb0\ekj1,G1P8TY'-R4z;3;276=3327653763#"'&'#"'&'+"'&1,cnc,P,+hm,%@n\Kf%#?70`DAbH<)+8LcܚPWskj1,,Pd@dczg2&q\ =!1(I;!.\eh$3;27&'&5763"327%+"'&1,csK4X}ں>SF7J \PWskj1,];d}M4F!Ť\e'-~5+83;276=36767632)"'&'+"'&327654'&#"1,cnc,P-e_KUskJNA'fܚPW큺%0Lskj1,,PdrNP2T?!'Dmx*#=\e%l?[F'.~5z'/ ('.iR4'/iR4'-}"'-K +D'-7R#h'-)%#!"'&53;276=31HPW1,cc,1VV\ekj1,,1jٻ*:33!276767'&54767632#!"'&654'&321,cTO<?aNbNLZB`.NJ|mePW)B,4((7(*Hskj1, ]027EW-3E$2Hf3Џ,'\eX+M;3*)3P&F !;3#!. Y_$ F !; 7!'!%3 YٍF %=F !;"4767632"'&'&!'!%30&$I Yٍ$$% %=F !;,048"'&'&4767632"'&'&4767632!'!%3$$%$%$ Yٍ?H%$HG %=F !;+AEIM"'&'&4767632"'&'&476762"'&'&4767632!'!%3$$%$H$%$ Yٍ?H%$ JHHG %=F !;+AW[_c476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&!'!%3'H$%$%HH$%$%H Yٍ$JJ%$J%$S$J %=F !;+AWnrvz476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&4767632"'&'&!'!%3'H$%$%HH$%$%H&$I Yٍ$JJ%$J%$S$J$$% %= F !;*@Ui"'&'&476762"'&'&5476762 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632!'!%3$H%$HJ&$UJHJH~J&$ YٍRHJ%$HG$$%$HGH%$H %=F !;!476762"'&'&!'!%3JH Yٍ$$% %=F !;,048476762"'&'&%4767632"'&'&!'!%3JHd&$I Yٍ$$%%$$% %=F !;,BFJN476762"'&'&"'&'&4767632"'&'&4767632!'!%3JH$$%$%$ Yٍ$$%H%$HG %=F !;,AW[_c476762"'&'&"'&'&4767632"'&'&476762"'&'&4767632!'!%3JH$$%$H$%$ Yٍ$$%H%$ JHHG %=F !;+AWmquy476762"'&'&476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&!'!%3JH]H$%$%HH$%$%H Yٍ$$%.$JJ%$J%$S$J %= F !;+AWm476762"'&'&476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&4767632"'&'&!'!%3JH]H$%$%HH$%$%H&$I Yٍ$$%.$JJ%$J%$S$J$$% %= F !;*@Vk476762"'&'&%"'&'&476762"'&'&5476762 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632!'!%3JH$H%$HJ&$UJHJH~J&$ Yٍ$$%HJ%$HG$$%$HGH%$H %=F !;+/37"'&'&4767632"'&'&5476762!'!%3rJ%$$Jm Yٍ@H$%$%H %=F !;"8N4767632"'&'&!'!%3"'&'&4767632"'&'&54767620&$I YٍJ%$$J$$% %=H$%$%HF !;,048Nd"'&'&4767632"'&'&4767632!'!%3"'&'&4767632"'&'&5476762$$%$%$ YٍJ%$$J?H%$HG %=H$%$%HF !;+AEIMcy"'&'&4767632"'&'&476762"'&'&4767632!'!%3"'&'&4767632"'&'&5476762$$%$H$%$ YٍJ%$$J?H%$ JHHG %=H$%$%H F !;)>SW[_t476762"'&'&476762"'&'$476762"'&'476762"'&'&!'!%3"'&'&4767632"'&'&476762'HIIHHIIH YٍI%$$I$II%$I%$S$I %=HIIH F !;+AWnrvz476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&4767632"'&'&!'!%3"'&'&4767632"'&'&5476762'H$%$%HH$%$%H&$I YٍJ%$$J$JJ%$J%$S$J$$% %=H$%$%H F !;*@Ui"'&'&476762"'&'&5476762 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632!'!%3"'&'&4767632"'&'&5476762$H%$HJ&$UJHJH~J&$ YٍJ%$$JRHJ%$HG$$%$HGH%$H %=H$%$%HF !;+AEIM"'&'&4767632%"'&'&4767632"'&'&5476762!'!%3rJ%$$$%$Jm Yٍ@H$%JI $%H %=F !;+AX\`d"'&'&4767632%"'&'&4767632"'&'&54767624767632"'&'&!'!%3rJ%$$$%$J&$I Yٍ@H$%JI $%H$$% %=F !;+AXnrvz"'&'&4767632%"'&'&4767632"'&'&5476762"'&'&4767632"'&'&4767632!'!%3rJ%$$$%$JV$$%$%$ Yٍ@H$%JI $%HH%$HG %= F !;+AXm"'&'&4767632%"'&'&4767632"'&'&5476762"'&'&4767632"'&'&476762"'&'&4767632!'!%3rJ%$$$%$JV$$%$H$%$ Yٍ@H$%JI $%HH%$ JHHG %= F !;+AWm"'&'&4767632%"'&'&4767632"'&'&5476762%476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&!'!%3rJ%$$$%$JH$%$%HH$%$%H Yٍ@H$%JI $%Hz$JJ%$J%$S$J %= F !;+AWm"'&'&4767632%"'&'&4767632"'&'&5476762%476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&4767632"'&'&!'!%3rJ%$$$%$JH$%$%HH$%$%H&$I Yٍ@H$%JI $%Hz$JJ%$J%$S$J$$% %= F !;+AVl"'&'&4767632%"'&'&4767632"'&'&5476762!"'&'&476762"'&'&5476762 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632!'!%3rJ%$$$%$JI$H%$HJ&$UJHJH~J&$ Yٍ@H$%JI $%HHJ%$HG$$%$HGH%$H %=F !;)>SW[_476762"'&'&476762"'&'$476762"'&'476762"'&'&!'!%3HIIHHIIH Yٍ$II%$I%$S$I %=F !;!6K`u4767632"'&'&!'!%3476762"'&'&476762"'&'$476762"'&'476762"'&'&0&$I YٍHIIHHIIH$I %=m$II%$I%$S$I F !;,048Ndz"'&'&4767632"'&'&4767632!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&$$%$%$ YٍH$%$%HH$%$%H?H%$HG %=m$JJ%$J%$S$J F !;+AEIMcy"'&'&4767632"'&'&476762"'&'&4767632!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&$$%$H$%$ YٍH$%$%HH$%$%H?H%$ JHHG %=m$JJ%$J%$S$J F !;+AW[_cy476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&'H$%$%HH$%$%H YٍH$%$%HH$%$%H$JJ%$J%$S$J %=m$JJ%$J%$S$J F !;+AWnrvz476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&4767632"'&'&!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&'H$%$%HH$%$%H&$I YٍH$%$%HH$%$%H$JJ%$J%$S$J$$% %=m$JJ%$J%$S$J F !;*@Ui"'&'&476762"'&'&5476762 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&$H%$HJ&$UJHJH~J&$ YٍH$%$%HH$%$%HRHJ%$HG$$%$HGH%$H %=m$JJ%$J%$S$JF !; !7Mcy7!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&476762"'&'& YٍH$%$%HH$%$%HJHF %=m$JJ%$J%$S$J$$% F !;"8Ndz4767632"'&'&!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&476762"'&'&0&$I YٍH$%$%HH$%$%HJH$$% %=m$JJ%$J%$S$J$$% F !;,048Ndz"'&'&4767632"'&'&4767632!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&476762"'&'&$$%$%$ YٍH$%$%HH$%$%HJH?H%$HG %=m$JJ%$J%$S$J$$% F !;+AEIMcy"'&'&4767632"'&'&476762"'&'&4767632!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&476762"'&'&$$%$H$%$ YٍH$%$%HH$%$%HJH?H%$ JHHG %=m$JJ%$J%$S$J$$% F !;+AW[_cy476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&476762"'&'&'H$%$%HH$%$%H YٍH$%$%HH$%$%HJH$JJ%$J%$S$J %=m$JJ%$J%$S$J$$% F !;+AWnrvz476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&4767632"'&'&!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&476762"'&'&'H$%$%HH$%$%H&$I YٍH$%$%HH$%$%HJH$JJ%$J%$S$J$$% %=m$JJ%$J%$S$J$$%F !;*@Ui"'&'&476762"'&'&5476762 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632!'!%3476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&476762"'&'&$H%$HJ&$UJHJH~J&$ YٍH$%$%HH$%$%HJHRHJ%$HG$$%$HGH%$H %=m$JJ%$J%$S$J$$% F !; ":Pf|7!'!%3"'&'&54767632"'&'&54767632 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632 Yٍ$$%%$$%J%$0$H%$HJ%$F %=%$J&$$%H$%$%HH$%$%H F !;"9Qg}4767632"'&'&!'!%3"'&'&54767632"'&'&54767632 "'&'&4767632"'&'&'476762"'&'.76762"'&'&547676320&$I Yٍ$$%%$$%J%$0$H%$HJ%$$$% %=%$J&$$%H$%$%HH$%$%H F !;,048Og}"'&'&4767632"'&'&4767632!'!%3"'&'&54767632"'&'&54767632 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632$$%$%$ Yٍ$$%%$$%J%$0$H%$HJ%$?H%$HG %=%$J&$$%H$%$%HH$%$%H F !;+AEIMd|"'&'&4767632"'&'&476762"'&'&4767632!'!%3"'&'&54767632"'&'&54767632 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632$$%$H$%$ Yٍ$$%%$$%J%$0$H%$HJ%$?H%$ JHHG %=%$J&$$%H$%$%HH$%$%H F !;+AW[_cz476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&!'!%3"'&'&54767632"'&'&54767632 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632'H$%$%HH$%$%H Yٍ$$%%$$%J%$0$H%$HJ%$$JJ%$J%$S$J %=%$J&$$%H$%$%HH$%$%HF !;+AWnrvz476762#"'&'&4767632"'&'$476762#"'&'4767632"'&'&4767632"'&'&!'!%3"'&'&54767632"'&'&54767632 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632'H$%$%HH$%$%H&$I Yٍ$$%%$$%J%$0$H%$HJ%$$JJ%$J%$S$J$$% %=%$J&$$%H$%$%HH$%$%HF !;*@Ui "'&'&476762"'&'&5476762 "'&'&4767632"'&'&'476762"'&'.76762"'&'&54767632!'!%3"'&'&5476762"'&'&5476762 "'&'&4767632"'&'.76762"'&'.76762"'&'&4767632$H%$HI&$UIHIH}I&$ Yٍ$I%$II%$0$H%$HI%$RHI%$HG$$%$HGH%$H %=%$I&$IHIIHHIIHm!5!!$ fm !!7!!!! %=m?'m"2#"'&'&47676!!7!!!!E$$% %=&$Ih?'m+/37476762#"'&'&476762"'&'&!!7!!!!H%$HG %=|$I;$%$?'m+AEIM476762#"'&'&476762"'&'&476762"'&'&!!7!!!!H%$ JHHG %=|$$%2$H.$%$?'m+AW[_c2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676!!7!!!!N$JJ%$J%$S$J %=H$%$%H"H$%$%Hq?'m+AWnrvz2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676!!7!!!!N$JJ%$J%$S$J$$% %=H$%$%H"H$%$%H &$Ih?' m*@Ui%476762"'&'&%4767632"'&'&476762"'&'4767672"'&'$4767>"'&'4767632'&'!!7!!!!HJ%$HG$$%$HGH%$H %=$H%$H3J&$JHJHJ&$?'m!2#"'&'&47676!!7!!!!E$$% %=kJH?'m,0482#"'&'&476762#"'&'&47676!!7!!!!E$$%%$$% %=iJH&$Ih?'m,BFJN2#"'&'&47676476762#"'&'&476762"'&'&!!7!!!!E$$%H%$HG %=kJH$$%;$%$?'m,AW[_c2#"'&'&47676476762#"'&'&476762"'&'&476762"'&'&!!7!!!!E$$%H%$ JHHG %=kJH$$%2$H.$%$?'m+AWmquy2#"'&'&476762"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676!!7!!!!E$$%.$JJ%$J%$S$J %=kJHH$%$%H"H$%$%Hq?' m+AWm2#"'&'&476762"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676!!7!!!!E$$%.$JJ%$J%$S$J$$% %=kJHH$%$%H"H$%$%H &$Ih?' m*@Vk2#"'&'&47676476762"'&'&%4767632"'&'&476762"'&'4767672"'&'$4767>"'&'4767632'&'!!7!!!!E$$%HJ%$HG$$%$HGH%$H %=kJH&$H%$H3J&$JHJHJ&$?'m+/37476762#"'&'4767632"'&'&!!7!!!!H$%$%H %=J%$S$J?'m"8N2#"'&'&47676!!7!!!!476762#"'&'4767632"'&'&E$$% %=H$%$%H&$Ih?'IJ%$S$Jm,048Nd476762#"'&'&476762"'&'&!!7!!!!476762#"'&'4767632"'&'&H%$HG %=H$%$%H|$$%;$%$?'IJ%$S$Jm+AEIMcy476762#"'&'&476762"'&'&476762"'&'&!!7!!!!476762#"'&'4767632"'&'&H%$ JHHG %=H$%$%H|$$%2$H.$%$?'IJ%$S$J m+AW[_cy2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676!!7!!!!476762#"'&'4767632"'&'&N$JJ%$J%$S$J %=H$%$%HH$%$%H"H$%$%Hq?'IJ%$S$J m+AWnrvz2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676!!7!!!!476762#"'&'4767632"'&'&N$JJ%$J%$S$J$$% %=H$%$%HH$%$%H"H$%$%H &$Ih?'IJ%$S$J m*@Ui%476762"'&'&%4767632"'&'&476762"'&'4767672"'&'$4767>"'&'4767632'&'!!7!!!!476762#"'&'4767632"'&'&HJ%$HG$$%$HGH%$H %=H$%$%H$H%$H3J&$JHJHJ&$?'IJ%$S$Jm+AEIM476762#"'&'476762"'&'&4767632"'&'&!!7!!!!H$%JI $%H %=J%$J$$%.$J?'m+AX\`d476762#"'&'476762"'&'&4767632"'&'&2#"'&'&47676!!7!!!!H$%JI $%H$$% %=J%$J$$%.$J+&$Ih?'m+AXnrvz476762#"'&'476762"'&'&4767632"'&'&476762#"'&'&476762"'&'&!!7!!!!H$%JI $%HH%$HG %=J%$J$$%.$J$$%;$%$?' m+AXm476762#"'&'476762"'&'&4767632"'&'&476762#"'&'&476762"'&'&476762"'&'&!!7!!!!H$%JI $%HH%$ JHHG %=J%$J$$%.$J$$%2$H.$%$?' m+AWm476762#"'&'476762"'&'&4767632"'&'&2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676!!7!!!!H$%JI $%Hz$JJ%$J%$S$J %=J%$J$$%.$J4H$%$%H"H$%$%Hq?' m+AWm476762#"'&'476762"'&'&4767632"'&'&2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676!!7!!!!H$%JI $%Hz$JJ%$J%$S$J$$% %=J%$J$$%.$J4H$%$%H"H$%$%H &$Ih?' m+AVl476762#"'&'476762"'&'&4767632"'&'&476762"'&'&%4767632"'&'&476762"'&'4767672"'&'$4767>"'&'4767632'&'!!7!!!!H$%JI $%HHJ%$HG$$%$HGH%$H %=J%$J$$%.$J$H%$H3J&$JHJHJ&$?'m)>SW[_2"'&'&476762#"'&'&47672#"'&'&47672"'&'&47676!!7!!!!N$II%$I%$S$I %=tHIIH"HIIH ?'m!6K`u2"'&'&47676!!7!!!!2"'&'&476762#"'&'&47672#"'&'&47672"'&'&47676E$I %=m$II%$I%$S$I&$Ih?'HIIH"HIIH m,048Ndz476762#"'&'&476762"'&'&!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676H%$HG %=m$JJ%$J%$S$J|$$%;$%$?'H$%$%H"H$%$%H m+AEIMcy476762#"'&'&476762"'&'&476762"'&'&!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676H%$ JHHG %=m$JJ%$J%$S$J|$$%2$H.$%$?'H$%$%H"H$%$%H m+AW[_cy2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676N$JJ%$J%$S$J %=m$JJ%$J%$S$JH$%$%H"H$%$%Hq?'H$%$%H"H$%$%H m)>Simqu2"'&'&476762#"'&'&47672#"'&'&47672"'&'&476762"'&'&47676!!7!!!!2"'&'&476762#"'&'&47672#"'&'&47672"'&'&47676N$II%$I%$S$I$I %=m$II%$I%$S$IHIIH"HIIH &$Ih?'HIIH"HIIH m*@Ui%476762"'&'&%4767632"'&'&476762"'&'4767672"'&'$4767>"'&'4767632'&'!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676HJ%$HG$$%$HGH%$H %=m$JJ%$J%$S$J$H%$H3J&$JHJHJ&$?'H$%$%H"H$%$%Hm !7Mcy!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676 %=m$JJ%$J%$S$J$$%m?'H$%$%H"H$%$%H JH m"8Ndz2#"'&'&47676!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676E$$% %=m$JJ%$J%$S$J$$%&$Ih?'H$%$%H"H$%$%H JH m+/37Lav476762#"'&'&476762"'&'&!!7!!!!2"'&'&476762#"'&'&47672#"'&'&47672"'&'&476762"'&'&47676H%$HG %=m$II%$I%$S$I$I|$I;$%$?'HIIH"HIIH JH m+AEIMcy476762#"'&'&476762"'&'&476762"'&'&!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676H%$ JHHG %=m$JJ%$J%$S$J$$%|$$%2$H.$%$?'H$%$%H"H$%$%H JH m+AW[_cy2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676N$JJ%$J%$S$J %=m$JJ%$J%$S$J$$%H$%$%H"H$%$%Hq?'H$%$%H"H$%$%H JH m+AWnrvz2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676N$JJ%$J%$S$J$$% %=m$JJ%$J%$S$J$$%H$%$%H"H$%$%H &$Ih?'H$%$%H"H$%$%H JHm*@Ui%476762"'&'&%4767632"'&'&476762"'&'4767672"'&'$4767>"'&'4767632'&'!!7!!!!2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&476762#"'&'&47676HJ%$HG$$%$HGH%$H %=m$JJ%$J%$S$J$$%$H%$H3J&$JHJHJ&$?'H$%$%H"H$%$%H JH m ":Pf|!!7!!!!4767632"'&'&%4767632#"'&'&476762"'&'4767672"'&'&%4767>#"'&'&4767632'&' %=%$J&$$%H$%$%HH$%$%Hm?'|$$%%$$%3J%$$H%$HkJ%$ m"9Qg}2#"'&'&47676!!7!!!!4767632"'&'&%4767632#"'&'&476762"'&'4767672"'&'&%4767>#"'&'&4767632'&'E$$% %=%$J&$$%H$%$%HH$%$%H&$Ih?'|$$%%$$%3J%$$H%$HkJ%$ m,048Og}476762#"'&'&476762"'&'&!!7!!!!4767632"'&'&%4767632#"'&'&476762"'&'4767672"'&'&%4767>#"'&'&4767632'&'H%$HG %=%$J&$$%H$%$%HH$%$%H|$$%;$%$?'|$$%%$$%3J%$$H%$HkJ%$ m+AEIMd|476762#"'&'&476762"'&'&476762"'&'&!!7!!!!4767632"'&'&%4767632#"'&'&476762"'&'4767672"'&'&%4767>#"'&'&4767632'&'H%$ JHHG %=%$J&$$%H$%$%HH$%$%H|$$%2$H.$%$?'|$$%%$$%3J%$$H%$HkJ%$ m+AW[_cz2"'&'&5476762#"'&'&47672#"'&'&547672"'&'&47676!!7!!!!4767632"'&'&%4767632#"'&'&476762"'&'4767672"'&'&%4767>#"'&'&4767632'&'N$JJ%$J%$S$J %=%$J&$$%H$%$%HH$%$%HH$%$%H"H$%$%Hq?'|$$%%$$%3J%$$H%$HkJ%$m)>Simqu2"'&'&476762#"'&'&47672#"'&'&47672"'&'&476762"'&'&47676!!7!!!!4767632"'&'&%4767632"'&'&476762'&'4767>"'&'&%4767>"'&'&476762'&'N$II%$I%$S$I$I %=%$I&$IHIIHHIIHHIIH"HIIH &$Ih?'|$I%$I4I%$$H%$HlI%$m*@Ui %476762"'&'&%4767632"'&'&476762"'&'4767672"'&'$4767>"'&'4767632'&'!!7!!!!4767632"'&'&%4767632"'&'&476762'&'4767>"'&'&%4767>"'&'&476762'&'HI%$HG$$%$HGH%$H %=%$I&$IHIIHHIIH$H%$H4I&$IHIHI&$?'|$I%$I4I%$$H%$HlI%$gm "3!254#%!2#!"54!xxxxAA,Gxxxyxxygm$03;#"'##65##"5476"3!254#%!2#!"54!3#'!#A; lB;;Bl ;"xxxxAAK Қ DDy~&%Nkk̛N%&VxxxyxxyUgm$0I#"'##65##"5476"3!254#%!2#!"54!!56754&#"5>32A; lB;;Bl ;"xxxxAA"?XhU4zHM98y~&%Nkk̛N%&Vxxxyxxy?rn81^BQ##{l0gm$0Y#"'##65##"5476"3!254#%!2#!"54#"&'532654&+532654&#"5>32A; lB;;Bl ;"xxxxAA\e9}F4wCmxolV^^ad_(fQI7Zy~&%Nkk̛N%&VxxxyxxymR|yOFJLl?<:=svcE`gm$03>#"'##65##"5476"3!254#%!2#!"54 !33##5!5A; lB;;Bl ;"xxxxAA5by~&%Nkk̛N%&Vxxxyxxy]mygm$0Q#"'##65##"5476"3!254#%!2#!"54!!67632#"&'53264&#"A; lB;;Bl ;"xxxxAAy^^a`<~B9>>Eoo4h6y~&%Nkk̛N%&Vxxxyxxy_ MLKJq ffgm$0@`#"'##65##"5476"3!254#%!2#!"54"327654'&&'&#"67632#"&547632A; lB;;Bl ;"xxxxAAGX3333XW33331221DD &9:DTTXWll122y~&%Nkk̛N%&Vxxxyxxy45[Z4554Z[54bg KL1LMONuv gm$07#"'##65##"5476"3!254#%!2#!"54!#!A; lB;;Bl ;"xxxxAAiH3y~&%Nkk̛N%&Vxxxyxxy0gm$0=[j#"'##65##"5476"3!254#%!2#!"54"32764'%&'&546 #"'&54767327654'&"A; lB;;Bl ;"xxxxAA55j]\655T./RQ./SZ85UVUV56-/.UQ100/0/y~&%Nkk̛N%&Vxxxyxxy[,+KLV,++]12Hdt::dJ01:7PyAAAAyN98?&%%$A?&%%$gm$0P_#"'##65##"5476"3!254#%!2#!"54532767#"&547632#"'&2654'&#"A; lB;;Bl ;"xxxxAA.1220DC #<9EWXWXkl122Xf33XU5443y~&%Nkk̛N%&Vxxxyxxyg KK/MNoouv rh\Z4554Z\44gm$0>JQ#"'##65##"5476"3!254#%!2#!"54"27654/2#"&546573A; lB;;Bl ;"xxxxAA2332233yty~&%Nkk̛N%&VxxxyxxyVVVVVVV)t'gm$0=#"'##65##"5476"3!254#%!2#!"543+53265A; lB;;Bl ;"xxxxAAA@1(TFy~&%Nkk̛N%&VxxxyxxyܕFE`Tlgm$0J#"'##65##"5476"3!254#%!2#!"54#"&54632.#"3267A; lB;;Bl ;"xxxxAA<1e9ɴ9f0/j6||{}7j.y~&%Nkk̛N%&Vxxxyxxyt"$$"gm4@L"#"&54632&#"32#"'##65##"5476"3!254#%!2#!"54VX~_ Ua`UU`aA; lB;;Bl ;"xxxxAA,ۥ(j8pny~&%Nkk̛N%&Vxxxyxxygm$0;#"'##65##"5476"3!254#%!2#!"5433 ##A; lB;;Bl ;"xxxxAAjixy~&%Nkk̛N%&VxxxyxxyazBmgm!-0867632 &547632"3!254#%!2#!"54!3#'!#7>T>}}?V<7xxxxAAK Қ DDv>G-;n;-GAxxxyxxyUgm!-F67632 &547632"3!254#%!2#!"54!!56754&#"5>327>T>}}?V<7xxxxAA"?XhU4zHM98v>G-;n;-GAxxxyxxy?rn81^BQ##{l0gm!-V67632 &547632"3!254#%!2#!"54#"&'532654&+532654&#"5>327>T>}}?V<7xxxxAA\e9}F4wCmxolV^^ad_(fQI7Zv>G-;n;-GAxxxyxxymR|yOFJLl?<:=svcE`gm!-0;67632 &547632"3!254#%!2#!"54 !33##5!57>T>}}?V<7xxxxAA5bv>G-;n;-GAxxxyxxy]mygm!-N67632 &547632"3!254#%!2#!"54!!67632#"&'53264&#"7>T>}}?V<7xxxxAAy^^a`<~B9>>Eoo4h6v>G-;n;-GAxxxyxxy_ MLKJq ffgm!-=]67632 &547632"3!254#%!2#!"54"327654'&&'&#"67632#"&5476327>T>}}?V<7xxxxAAGX3333XW33331221DD &9:DTTXWll122v>G-;n;-GAxxxyxxy45[Z4554Z[54bg KL1LMONuv gm!-467632 &547632"3!254#%!2#!"54!#!7>T>}}?V<7xxxxAAiH3v>G-;n;-GAxxxyxxy0gm!-:Xg67632 &547632"3!254#%!2#!"54"32764'%&'&546 #"'&54767327654'&"7>T>}}?V<7xxxxAA55j]\655T./RQ./SZ85UVUV56-/.UQ100/0/v>G-;n;-GAxxxyxxy[,+KLV,++]12Hdt::dJ01:7PyAAAAyN98?&%%$A?&%%$gm!-M\67632 &547632"3!254#%!2#!"54532767#"&547632#"'&2654'&#"7>T>}}?V<7xxxxAA.1220DC #<9EWXWXkl122Xf33XU5443v>G-;n;-GAxxxyxxyg KK/MNoouv rh\Z4554Z\44gm!-;GN67632 &547632"3!254#%!2#!"54"27654/2#"&5465737>T>}}?V<7xxxxAA2332233ytv>G-;n;-GAxxxyxxyVVVVVVV)t'gm!-:67632 &547632"3!254#%!2#!"543+532657>T>}}?V<7xxxxAAA@1(TFv>G-;n;-GAxxxyxxyܕFE`Tlgm!-G67632 &547632"3!254#%!2#!"54#"&54632.#"32677>T>}}?V<7xxxxAA<1e9ɴ9f0/j6||{}7j.v>G-;n;-GAxxxyxxyt"$$"xm1=I"#"&54632&#"3267632 &547632"3!254#%!2#!"54VX~_ Ua`UU`a7>T>}}?V<7xxxxAA,ۥ(j8p0v>G-;n;-GAxxxyxxygm!-867632 &547632"3!254#%!2#!"5433 ##7>T>}}?V<7xxxxAAjixv>G-;n;-GAxxxyxxyazBmgm!)0 00"3!254#%!2#!"54!3#'!#hfxxxxAAK Қ DD xxxyxxyUgm70 00"3!254#%!2#!"54!!56754&#"5>32hfxxxxAA"?XhU4zHM98 xxxyxxy?rn81^BQ##{l0gmG0 00"3!254#%!2#!"54#"&'532654&+532654&#"5>32hfxxxxAA\e9}F4wCmxolV^^ad_(fQI7Z xxxyxxymR|yOFJLl?<:=svcE`gm!,0 00"3!254#%!2#!"54 !33##5!5hfxxxxAA5b xxxyxxy]mygm?0 00"3!254#%!2#!"54!!67632#"&'53264&#"hfxxxxAAy^^a`<~B9>>Eoo4h6 xxxyxxy_ MLKJq ffgm.N0 00"3!254#%!2#!"54"327654'&&'&#"67632#"&547632hfxxxxAAGX3333XW33331221DD &9:DTTXWll122 xxxyxxy45[Z4554Z[54bg KL1LMONuv gm%0 00"3!254#%!2#!"54!#!hfxxxxAAiH3 xxxyxxy0gm+IX0 00"3!254#%!2#!"54"32764'%&'&546 #"'&54767327654'&"hfxxxxAA55j]\655T./RQ./SZ85UVUV56-/.UQ100/0/ xxxyxxy[,+KLV,++]12Hdt::dJ01:7PyAAAAyN98?&%%$A?&%%$gm>M0 00"3!254#%!2#!"54532767#"&547632#"'&2654'&#"hfxxxxAA.1220DC #<9EWXWXkl122Xf33XU5443 xxxyxxyg KK/MNoouv rh\Z4554Z\44gm,8?0 00"3!254#%!2#!"54"27654/2#"&546573hfxxxxAA2332233yt xxxyxxyVVVVVVV)t'gm+0 00"3!254#%!2#!"543+53265hfxxxxAAA@1(TF xxxyxxyܕFE`Tlgm80 00"3!254#%!2#!"54#"&54632.#"3267hfxxxxAA<1e9ɴ9f0/j6||{}7j. xxxyxxyt"$$"gm".:0 00"#"&54632&#"32"3!254#%!2#!"54hf5VX~_ Ua`UU`auxxxxAAAۥ(j8poxxxyxxygm)0 00"3!254#%!2#!"5433 ##hfxxxxAAjix xxxyxxyazBmgm !! !"3!254#%!2#!"540xxxxAA1GG}xxxyxxygm#/;>F65'&'&547632&54 632'"3!254#%!2#!"54!3#'!#U9H5?K1||1K?5I9xxxxAAK Қ DDL C4$jj$4F LxxxyxxyUgm#/;T65'&'&547632&54 632'"3!254#%!2#!"54!!56754&#"5>32U9H5?K1||1K?5I9xxxxAA"?XhU4zHM98L C4$jj$4F Lxxxyxxy?rn81^BQ##{l0gm#/;d65'&'&547632&54 632'"3!254#%!2#!"54#"&'532654&+532654&#"5>32U9H5?K1||1K?5I9xxxxAA\e9}F4wCmxolV^^ad_(fQI7ZL C4$jj$4F LxxxyxxymR|yOFJLl?<:=svcE`gm#/;>I65'&'&547632&54 632'"3!254#%!2#!"54 !33##5!5U9H5?K1||1K?5I9xxxxAA5bL C4$jj$4F Lxxxyxxy]mygm#/;\65'&'&547632&54 632'"3!254#%!2#!"54!!67632#"&'53264&#"U9H5?K1||1K?5I9xxxxAAy^^a`<~B9>>Eoo4h6L C4$jj$4F Lxxxyxxy_ MLKJq ffgm#/;Kk65'&'&547632&54 632'"3!254#%!2#!"54"327654'&&'&#"67632#"&547632U9H5?K1||1K?5I9xxxxAAGX3333XW33331221DD &9:DTTXWll122L C4$jj$4F Lxxxyxxy45[Z4554Z[54bg KL1LMONuv gm#/;B65'&'&547632&54 632'"3!254#%!2#!"54!#!U9H5?K1||1K?5I9xxxxAAiH3L C4$jj$4F Lxxxyxxy0gm#/;Hfu65'&'&547632&54 632'"3!254#%!2#!"54"32764'%&'&546 #"'&54767327654'&"U9H5?K1||1K?5I9xxxxAA55j]\655T./RQ./SZ85UVUV56-/.UQ100/0/L C4$jj$4F Lxxxyxxy[,+KLV,++]12Hdt::dJ01:7PyAAAAyN98?&%%$A?&%%$gm#/;[j65'&'&547632&54 632'"3!254#%!2#!"54532767#"&547632#"'&2654'&#"U9H5?K1||1K?5I9xxxxAA.1220DC #<9EWXWXkl122Xf33XU5443L C4$jj$4F Lxxxyxxyg KK/MNoouv rh\Z4554Z\44gm#/;IU\65'&'&547632&54 632'"3!254#%!2#!"54"27654/2#"&546573U9H5?K1||1K?5I9xxxxAA2332233ytL C4$jj$4F LxxxyxxyVVVVVVV)t'gm#/;H65'&'&547632&54 632'"3!254#%!2#!"543+53265U9H5?K1||1K?5I9xxxxAAA@1(TFL C4$jj$4F LxxxyxxyܕFE`Tlgm#/;U65'&'&547632&54 632'"3!254#%!2#!"54#"&54632.#"3267U9H5?K1||1K?5I9xxxxAA<1e9ɴ9f0/j6||{}7j.L C4$jj$4F Lxxxyxxyt"$$"gm#/;MW65'&'&547632&54 632'"3!254#%!2#!"54"#"&54632&#"32U9H5?K1||1K?5I9xxxxAA3VX~_ Ua`UU`aL C4$jj$4F Lxxxyxxyۥ(j8pgm#/;F65'&'&547632&54 632'"3!254#%!2#!"5433 ##U9H5?K1||1K?5I9xxxxAAjixL C4$jj$4F LxxxyxxyazBmgm +!%%!%%"3!254#%!2#!"54decb`bMxxxxAAnξ;3o(T"(Uxxxyxxyjn ! ! tu u jn  !! ! ,=C`tu <=u jn ! !  ! ut~<A tu<=jn ! !  ! ut~<A tu<=jn ! ! ! ! tu <=CAu tC<=@jn ! ! % tu `Au {@Cjn ! ! 0 tu Au {@Cjn % ! ! +aC ut@C tujV#+3462"7"32654$"&462"32654462"6"&4622>7>54&'&'>54&#"&547&"'654.#"'72>32%%"&''%&/'%.547&54632B\BB\t- .B\BB\, -o    lN.  ;qsV6C70AIbbOSC**CSObbIA07C6Vsq;  .8L+ʏ]KY YK]+8ggg=>uggg=>"6''6']6''6'$9]W>:LktLJ73(#XQik\B?&STTS&?B\kiQX#(37JLtkL>W]9rlȡ~3D#@mm@#D3~lȬr Kj - "(,"&4632'2654#"3#"&46327'7#5%32767654'&'7>732>7>54'.#"&$ &/.#"3276%2654#"'747'&'#".'.54>7>32676 767>32+"&'&'&'&'& '326y2>=32>=-5nnI3=>23=>S39?*nB?94iEB9?B  R' 8%/6 1.4&++ #?Y==Y?# ++&4.1 6/%8 'R  -*?9 9 !;+57? B:),#3A32%A(77(A%23A3#,):B ?75+;! 9 "`  B6Ĉ6B  _# 4aa7k~nnnnAnnnn-ںMғv$DK;7" D;KD$v h5WM' 1 *L7.4(#"NemmeN"#(4.7L* 1 'MW5hNA*,PI  ,9 :kR4_5"2S''RNNR''S2"5_4Rk: 9,  IP,*0(sBD0H.&&.H0DBs($'&=j(2AN7327327#"'#""'72#"'&547632">7&>32#"'&327654'&#"654.'&'&#"&'%% 767%767%7&54>76?62>?''! '&'7&''\E[:TT:[E\lJDEI>zz>Z+Q (F65/AFeN$0+EdFB/56F( Qy 0( ,@ *tt* B g굤g  &<]ii];& l m nmuvmmm b@,,@bdx==x.L #$-G0,a&0$4%Ca,0G-$#  ;t.f>v::rAe.t+B,E@y>zwwz>y9L,B=|9jwAu: a a :uAwj9|BQW.>. .>.QWB j %1;Gh462"7"32654"547632"&462432#"'&"3265473267!"& 7654'>54'&#".#" 632+ '.'&'#"&'&'&54632676&#";67&%32767654&'&#"RtRRt78,%,% RtRRt%,%,78j`;j|TVZGqpGZVT|j7aij "Y:8mm8:Y" jia (+G&<5t5<&G+( tRRtRh11R $ $ tRRtR$ $k11TXLTr˚,0^1ioE55Eoi1^0,|,6[?Ki{pn7LL7np{iK?[6,|R:3/{W``W{/3:R jn ".49>BF4632#"&%462#"&! ! ! ! ! 67##%67#5#Q;:RR:;QBRtSS:;Qm<=CAtu XLL Hd e;QQ;:SS:;QQ;:SSC<=@u jj *%Z*Rdf jn #/5:?CG! ! ! ! 462#4&"!462#4&"! 67##%67#5#1<=CAtu Č=T=)Č=T=XLL Hd eC<=@u ŋSwwSŋSwwSj *%Z*Rdf _jn #/Y~5#67##67#%! 462#4&"!462#4&"! "'&/! &"&5476?&7! 4'#"'&/&'&! 6?"'462#4&"!462#4&"! ! ! ! ! %!7276Č=T=ɌČ=T=tu <=CABW!\ʎ\ŋSwwSŋSwwSu tC<=@j{u\\jn #/;AJ"&547462#4&"!462#4&"! ! ! ! ! %!7276AL6Č=T=ɌČ=T=tu <=CABW!\ʎ\,8"+6,#5 ŋSwwSŋSwwSu tC<=@j{u\\jn %+3%%''7'7! ! ! ! ! %!$76|'MM٠MMtu <=CABW!\\'nnUUnnu tC<=@j{u\\jc*5IW_7767 '&! /! '462#"&%4632#"&4$! ! 7&%654$! 6! &#"32s('s%22<=RtSS:;QQ;:RR:;Q ۼCAJKCݰG,&',H923QC<=99*;QQ;:SS:;QQvRS\\ t[\6JQrrQJ6nnj0-9J%462#"&%4632#"&'7 767 '&! ! 6 %! 7/M?RtSS:;QQ;:RR:;QMMs('s%22<=CA*go;QQ;:SS:;QQvRSno>G,&',H923QC<=@** t/0jn'297 767 '&! ! ! ! 4632#"&-%s('s%22<=CAtu Q;:RR:;Q'MMG,&',H923QC<=@u ;QQvRSdnnjn'3?7 767 '&462#4&"!462#4&"! ! ! ! s('s%22,Č=T=ɌČ=T=tu <=CAG,&',H923ŋSwwSŋSwwSu tC<=@jn 7CQ462#4&"!462#4&"!27&'# 3 536! ! ! 327674'&fČ=T=)Č=T= &?)   #I=#   )?& >2<tu <=CAs('s%22%?A>ϾDLD 1GG1 DLD>A?%*u tC<=@G,&',H923jn#3<! 4'#"&5"&=#! ! %7767 '&!&'&! 1<=Tn즦nUtu ss('s%22`C<àOddddOu [G,&',H923;jn)5!!5!2767!! ! ! ! lʎ's%2~tu <=CA',H929u tC<=@jn%15!4632#"&%462#"&! ! ! ! LvQ;:RR:;QBRtSS:;Qtu <=CAq;QQvRS:;QQ;:SSu tC<=@jn #5!!5!5!! ! ! ! tu <=CA凇yu tC<=@jn )7! ! ! ! 5!5!2#"&545!5!2#"&5451<=CAtu x:RR:;QVx:SS:;QC<=@u -Q;:SS:Q;:SS:jn *6B"&475!%7 '%4632#"&%462#"&! ! ! ! PA6L6= MXMXMQ;:RR:;QBRtSS:;Qtu <=CA5O66O5Eonno;QQvRS:;QQ;:SSu tC<=@jn'35!"&53265!"&53265! ! ! ! L6Č=T=7Č=T=?tu <=CAqŋSwwSŋSwwSbu tC<=@jn%1%'4632#"&%462#"&! ! ! ! 9g9Q;:RR:;QBRtSS:;Qtu <=CA{{;QQvRS:;QQ;:SSu tC<=@jnB'! ! ! ! 7"'&'&#"'67623276762&__Z<=CAtu _4) FF "58 yFF "54( FFy\__C<=@u _Wi"bc(!__9("bb("_i"bb"(9_jn #/;4632#"&%462#"&7'7'7'! ! ! ! Q;:RR:;QBRtSS:;QPA<NvX..XvN>#u tC<=@jn")6BN2#'&5&76334764632#"&-%7'7'7'! ! ! ! j<2> &@(   "VQ;:RR:;Q'MMنPA<ϾDLD 1GN;QQvRSdnn>NvX..XvN>#u tC<=@jn $0<462#4&"!462#4&"7'7'7'! ! ! ! Č=T=ɌČ=T=PA<NvX..XvN>#u tC<=@jn $0<7'7'7'"&53265!"&53265! ! ! ! PA<NvX..XvN>ŋSwwSŋSwwSbu tC<=@jn ".8>4632#"&%462#"&! ! ! ! %5!#"&5!#26Q;:RR:;QBRtSS:;Qm<=CAtu ČU=T=;QQ;:SS:;QQ;:SSC<=@u cŋSwwjn )394632#"&-%! ! ! ! %5!#"&5!#26Q;:RR:;Q'MM<=CAtu ČU=T=;QQvRSdnnC<=@u cŋSwwjn %/5! ! ! ! '7'7%%5!#"&5!#261<=CAtu 2MM'MMČU=T=C<=@u UnnnnŋSwwjn)5F%7 '%4632#"&%462#"&! ! ! ! 676 &'&#&MXMXMQ;:RR:;QBRtSS:;Qtu <=CA%23$s(ʎ'onno;QQvRS:;QQ;:SSu tC<=@%8338H,'',jn"-9EV%'6762&'&"%7 '%4632#"&%462#"&! ! ! ! 676 &'&#&yFFyT;MXMXMQ;:RR:;QBRtSS:;Qtu <=CA%23$s(ʎ'9("bb"(9<<donno;QQvRS:;QQ;:SSu tC<=@%8338H,'',jn)5F'4632#"&%462#"&%! ! ! ! 676 &'&#&MM+Q;:RR:;QBRtSS:;Q/M%tu <=CA%23$s(ʎ'gno;QQvRS:;QQ;:SSou tC<=@%8338H,'',jn(,7BFV676 &'&#&! ! ! ! %462#"&%4632#"&''6762&'&"%23$s(ʎ'<=CAtu (/M?RtSS:;QQ;:RR:;QMMyFFyTL8338H,'',C<=@u o;QQ;:SS:;QQvRSno 9("bb"(9<<jn #4O! ! ! ! "&53265676 &'&#&"&54?&'&532651<=CAtu HČ=T=%23$s(ʎ'F:M L6 M:F=T=C<=@u ŋSwwS8338H,'',bQ?7#+6,#5? RbSwwSjn*6676 &'&#&%%''7'7! ! ! ! %23$s(ʎ''MM٠MMtu <=CAL8338H,'',"nnUUnnu tC<=@jn!-9' 7 676 &'&#&"&47! ! ! ! ___/%23$s(ʎ'B6L6<=CAtu \___8338H,'',#5O66O5C<=@u jn ".4<4632#"&%462#"&! ! ! ! !4 !&'& Q;:RR:;QBRtSS:;Qtu <=CA''EkjE;QQ;:SS:;QQ;:SSu tC<=@Fa`LtuLjn)5;C%7 '%4632#"&%462#"&! ! ! ! !4 !&'& MXMXMQ;:RR:;QBRtSS:;Qtu <=CA''EkjEonno;QQvRS:;QQ;:SSu tC<=@Fa`LtuLjn NZflx32654&#"!&'& !4 ! 4'#&'#5"'#5&47&'##"&'##5! ! 4632#"&%6754&#"326'#"&546325&'&'67%&'%67%tJUioOLr7EkjE?'' <=5D%Dm8D++!"D"!++D8nD%D6tu q"!# ##  rLOoiUJt#!"$ g!"$!"+O# *"!$RluIOoo`LtuLF7C<;.)nY6G$@<<=j<<@$F7Y*.żu !!# OooOIulR #!!y (  &!--! jn '-5' 7 ' 7! ! ! ! !4 !&'& ______tu <=CA''EkjE___X___?u tC<=@Fa`LtuLjn)5AGO767632#"&53265!"&53265! ! ! ! !4 !&'& U% $  ';Č=T=7Č=T=?tu <=CA''EkjEt2  "$ŋSwwSŋSwwSbu tC<=@Fa`LtuLjn %+3%%''7'7! ! ! ! !4 !&'& |'MM٠MMtu <=CA''EkjE'nnUUnnu tC<=@Fa`LtuL_jn =b%!4 '7'7%%! "'&/! &"&5476?&7! 4'#"'&/&'&! 6?"'''MM'MM.tu ];6L *+ L6<\<=%&-)//)-LFtUnnnnu%&69 96&%C<9-ǚ -9jn*6462"4632#"&%462#"&! ! ! ! ,ԖQ;:RR:;QBRtSS:;Qtu <=CAZԖԖ;;QQ;:SS:;QQ;:SSu tC<=@jn%1=%7 '%4632#"&%462#"&462"! ! ! ! MXMXMQ;:RR:;QBRtSS:;QKjKKjtu <=CAonno;QQvRS:;QQ;:SSjKKjKu tC<=@jn#.:F"&47462"%7 '%4632#"&%462#"&! ! ! ! PA6L6ԖUMXMXMQ;:RR:;QBRtSS:;Qtu <=CA5O66O5.ԖԖRonno;QQvRS:;QQ;:SSu tC<=@#<5nk&462 &462%'%%7462"5.'46767 #5476764&"#5 '#54&/&'&'."% 7547676767>76767&'& QRtSSIQQuRRMXM~MXȖԖHVh=;;=hVH&z':%i)8^'ny'^8)i%:'z&Lw l08< 3233<80(SuQQuSSuQQvRnooԖԖx>[1'Sk ߰ kS1[>$n9(#mq,%@76?62>?''! '&&''7&''%4."#462!4."#462&'32?67#5#"'\>xcev>rt6;#y]M4f굡g.G *tt*  &<]ii];& ikhdtt^b e11B1Č1B1Č:$(2+$) `3H:G??4.x==x%B DG}KSB$bCJAi?l:1wv85j?g>LCR4.f>v::rAeME9jwAu: a a :uAwj9DMDVW$@"<<@6 @"O`DQ+ZEEY,Č+ZEEY,Č  c'F aa C `j?H]654.'&'& &'6?#"'% 76767%767#"&/27#"'7327 $'&54732"&546?4''7&54>76?62>?'#"&/ '&&'4."#462!4."#462&'32?67#5#"' *tt* P,3,04f鶢f.0,4.RȂ\>xbfv>rt6<"x|%7>4Zfd &<^ij]<& hjZ4<6$,4,tt*$0B2Č0B2Č:$(2,$( `4H:F>@4|(,.f>v::rAe.*',,3<%WLi?l:1wv85j?gD`(<3,.x==x%B DG}KSBA6%,4JDME9jwAu: a a :uAwj9DMD44,%6<3*hb]p-o+ZEEY,Č+ZEEY,Č  c'F aa C :j&*_27#"'7327 $'&54732#"'267#"%% 76767%767%7654.'&'&#"&'&54>76?62>?''! '&&''7&''%4632#"'&7">7&#"'&'67632327654'&#"\>xcev>rt6;#yprw??52H:u}M4f굡g.G *tt*  &<]ii];& ikhdtt^b ecC>]0L!(C8$0+E8C%$M/2-;Cc .* ,@.x==x%B DG}KSBh ELME N$bCJAi?l:1wv85j?g>LCR4.f>v::rAeME9jwAu: a a :uAwj9DMDVW$@"<<@6 @"O`DF_W;E !I+ /!-! CB*&_z 5=jJ7327327#"'#""'72%654.'&'&#"&'%% 767%767%7&54>76?62>?''! '&'7&''>32#'&'4632%2347632#'&546\E[:TT:[E\lJDEI>zz>Z *tt* B g굤g  &<]ii];& l m nmuvmmm  :-.>>1@/=# -:  #=/@1>>b@,,@bdx==x.8t.f>v::rAe.t+B,E@y>zwwz>y9L,B=|9jwAu: a a :uAwj9|BQW.>. .>.QWB0B=1JJ=B*HGrB0GH*B=ϾJJ1= =j(7Du-'"'72#"'&547632">7&>32#"'&327654'&#"654.'&'&#"&'%% 767%767%7&54>76?62>?''! '&'7&''2767!/MMMs>zz>Z+Q (F65/AFeN$0+EdFB/56F( Qy 0( ,@ *tt* B g굤g  &<]ii];& l m nmuvmmm ʎ(s"5gonno=x==x.L #$-G0,a&0$4%Ca,0G-$#  ;t.f>v::rAe.t+B,E@y>zwwz>y9L,B=|9jwAu: a a :uAwj9|BQW.>. .>.QWB(+H65=j'4i#"'&547632">7&>32#"'&327654'&#"654.'&'&#"&'%% 76?%767%72>?''! '&''7&''7&'54>76?7'7'7'27#"'7l+Q (F65/AFeN$0+EdFB/56F( Qy 0( ,@ *tt*  B f굡g i];&  l m nmttmmm l &<]PA<wdev>L #$-G0,a&0$4%Ca,0G-$#  ;s.f>v::rAe.$A+B,E@y>zwvz>y9L,B a :uAwj9WBQW.>..>.QWB`*9jwAu: a:=NwY.-YwN=.x==x =j'4e#"'&547632">7&>32#"'&327654'&#"654.'&'&#"&'%% 767%767%7&54>76?62>?''! '&'7&''62&"%6 &#"27#"'7'%%l+Q (F65/AFeN$0+EdFB/56F( Qy 0( ,@ *tt* B g굤g  &<]ii];& l m nmuvmmm GFF`T`Ȑ[>wdev>MM{/ML #$-G0,a&0$4%Ca,0G-$#  ;t.f>v::rAe.t+B,E@y>zwwz>y9L,B=|9jwAu: a a :uAwj9|BQW.>. .>.QWBcc_<<`.x==x=noo=j ,4d"&545"'7276?.53265!"&532656 & &6?6?62>?'' '7&''!2$7%767%7654/&'& &'%%BM 6L6nv>[[3 M=T=7Č=T=`n &<]ii];&+ l m nmudfvmmm ;Yg A+tt* B ?5O66,5=x..c?SwwSŋSwwSڵ`|jau: a a :ua̠|BQW.>.* .>.QWBz>y9L,B+Qb::Tnk+B,E@y>=j4s{654.'&'&#"&'%% 76?%767%72>?''! '&''7&''7&'54>76?!&'& !476327'* *tt*  B f굡g i];&  l m nmttmmm l &<]EkjE?(___ڃs.f>v::rAe.$A+B,E@y>zwvz>y9L,B a :uAwj9WBQW.>..>.QWB`*9jwAu: aGaKtuK礣~____jn'29676 &'& ! ! ! ! %4632#"&%5%%22%s'l(<=CAtu Q;:RR:;Q'MM8329H,'&,@=<Ct b:SRvQQUnn @03#u)@ dd1<20KTKT[X@878YKTK T[KT[X@878YKTKT[X@878YKTX@878Y@````pppp]3#%3#^ys@B10KSXY"K TX@878YKTX@878Y@ %%6FVjg //]]3#7Ju@!  VV 99991<2990K TX@878YKTX@878Y ]'.#"#4632326=3#"&9 $(}gV$=09" (}gT";9! 2-ev 3)dw @B10KSXY"K TX@878YKTX@878Y@*$$5CUU//]]#ę1w@ 91<90K TX@878YKTX@878YKTX@878Y@ //- ]3#'#Ӌ1@ 91290K TK T[K T[K T[X@878YKTX@878YKTX@878Y@ "  ]373Ӌ 9 #.#"#>32v cSRav 6978w{z9 j@ VV120K TX@878YKTX@878YKTKT[X@878Y332673#"&v cSRav 6978w{zfGd10KTKT[X@878YKTX@878Y3#@1<203#3#䙋N#!#ęę53#73#'3# 3#3#'3#}}d 3#3#'3#}}d3#3#d 3#3#3#3#dd&;#"'&'#"'$&733$767654'3F??7KXQ~XR\,>%!$'$&73!2%7&'&547676323!!"'654'&'&#"xhn}@AQ+"R:4RQP ioh4"(=)1$+<'g\^sM6,|y$K2S%jAzG' <8BN?0654'&323276767'&54767632#!V)B,4((7(*HTO<?aNbNLZB`.NJ|m+M;3*)3P& ]027EW4,E$2Hf3Џ,' !5;#"'+5327&'&54767632"67654'&'&f$'و'$A??8 D?$ 9P2*I1C299(M.L,0W 5+5DE2.4! k .@%&'&'&547676323!!#'$'&5473!2766'&'&#"B.y9()Wp8c20-=^E>><l/"'"3 9Ld/  #+m=E2X:zFNV}`kL:DbZzWK# :<,; ?7 8X4~X5 %4'&"2>"'&4762<R8R8z?@?@@?@(8)*8@@@@@?? '.'>3&4'.cR>P~&5'.cR>P'0 3&40cL~&50cL >&}8\K&}X>K&}X >&1?\F&1 >F&1  >&/\F&/>F&/ >&'-?0>\L&'08- >>L&'08- 3_+ 5__bV'J@!B  6991/<2990KSXY"]33+532765#ոRQi&&}``01}` 2@  F <<221/<20@  @ P ` p ]33###53ø`<ĤV.` 54!333##"3276!5R w{i&V`p?`3A0c3'q="Ua4'q"[^3'*Pq=cZ'#d"UcZ'%d"UaZ'#dqaZ'%dqvj 3'$\q=cZ''b"Uvj V'"}$\cW''u*|vj0Z'#@d$\c:'&u#(Dcm:'%D&uvc u'&u$vV Y')P$pVZ')P#dVZ')P%dV')Pc['&u{Pn&Z,,!!,,O=32653#"&[hq`=QQ, &&m &3;#"'!5 767654x I*e2D0# &pgM,>ꅗ:H~#'`'S'SF'8@'+ '.cQ~@'+ '.cR ~r'>9 9F KSKQZX8Y1/0@  @ P ` p ]3;#"&5Li a^q%qqu {&JOw`73#!!dž$Nd`Vw`#676#732767!5ʆ#5H2K1i0/N)deеT0Hd01``vg{'y{&3#3## +@     22221/220!#3!53#^ժ ?!5 ?8'tXz8 U'uXz8'zt8'wXz8 U'xXz8 'z,w$'w}$'x}N@ T1/0333N@T 1/20%3!533yոBy@ T1/0)533ysոBq8@ E EԶ0]991@  /0 6& #" 3 *NYh> éA@E E Զ0]91@    /<20 6& "'!53&54 3 *NNJhh> é!8@ E EԶ0]991@  /0 6& &54 #"'!5 hYNJ>z=x 4@   2291@  /290)33!x³j*]Qix 6@   2291@    /2290%!5!33xtj³瓓]Qi' 4@     2291@    /290#5!33j³]Q=q) #33mCq"q )5333!mm"q)533#mOq $@  1/2<0)3!33OkUq""Oq (@   1 /22<0)533!33OιUΓ""q $@  1/2<0)533!3kιU"Oq $@   <1/2035!!5!3ΓK"Oq $@   1/20#5!!5!3ΓK"q @ 1/0!5!!5kqKq:@!E E ܲ@]ܲ@ ]1@  /<0!&'.4> !2>4."RJr 惃sKR9[ZZ 1ũbbŨ1 p`88`p`88!>@#E E"ܲ@]ܲ@]1@  /2<0%!!5!&'.4> 2>4."RJr 惃sKRQ[ZZ{ 1ũbbŨ1 p`88`p`88O:@!EE ܲ@]ܲ@]1@  /<0#5!&'.4> 2>4."RJr 惃sKRQ[ZZ{ 1ũbbŨ1 p`88`p`88O &@    21 /03"3#!5!>k fO "  21 /03"3#!5!>c f $@   21 /03"3#!5!pk fq7@ E<21@  /<20!!##"&6 !354'&"3.Cf^v ]8mr^<Uf"qɃ]8ƃ;@! E <21@ /2<20%!##"&6 !3!554'&"3.Cf^v7]8mr^K<Uf"Ƀ]8ƃ7@ E<21@  /<20%!##"&6 !!554'&"3.Cf^v]8mr^K<UfɃ]8ƃ ,@   <<1@  /03!!!!!55Փ/ 0@   <<1@   /20#53!!!!!55B/D ,@    <<1@  /0)53!!!!ys55B/= ,@  <<1@  /0!!5!!5!355ߒѓ 0@  <<1@  /20#5!!5!!5!355ՓLѓ ,@    <<1@  /0)5!!5!!5!,55Lѓ *@  <1@   20!!27654'&3!23,R4,,=ٹUiXO]Oz}I_"_Ҥ.@  <1@  /220#533!23!!27654'&ιUiXO,R4,,=B_Ҥ]Oz}I_ *@  <1@   /20!!27654'&533!2#,R4,,= ιUiXXXl]Oz}I_"B_ҭ@@  ܲ_]9@  /999@  10!4'&'5!!5Mc4B_9V@9D@   ܲ_]9@  /2999@  10#5!&'&'&'5!! 5Mc4BX]9V@9$@@   ܲ_]9@  /999@  10#5!&'&'&'5! 5Mc4B X]9Vq=:@   91@ /̲]촍]0!533T9 >@  91@ /2̲]촍]0#5!533hՓL9 :@  91@ /̲]촍]0#5!53hL9+#1@%!$1@  #/2203432>3234&#"!4&#"!}x5%^qZHZlK--Xh|ŕnc%5@'#&1@  $/2220#53432>3234&#"!4&#"!}x5%^qZHZl[K--Xh|ŕnc#1@%!$1@  "/220#53432>324&#"!4&#"!}x5%^ZHZl[K--Xh&|ŕnc= -@   <<1@  /<<0!!5!3!!!KK?1@   <<1@  /2<<0#5!!5!3!!!KK? -@   <<1@  /<<0)5!!5!3!!@KK?=X>@ <<<<1@  /2<<<220%!!5!3!3!!!=KøL??XB@  <<<<1@  /22<<<220#5!!5!3!3!!!%!KøL=??>@  <<<<1@  /2<<<<<0)5!!5!3!3!!!0KøL=??Oq %@   1/203!3!$Uq"KOq *@    1@  /220#53!3!$U"Kq %@  1 /20)53!!kUޓK=C  1@ B/0KSX@Y!!!tFs0hB~ F  1@ B /20KSX@Y!5!!!tFlhhB~BC  1@ B/0KSX@Y!5!!tFlh0B~B+ 8@!  <<1@    /2<20327654'&+!!!2/!m]%i ; @ED\qQE=4."RJrCEoJRXErrJS9[ZZ 1SV/ { 2Ʀ1 "p_88_p`88*#5!5&'.4767675!5!!2>4."RJrCEoJRXErrJS9[ZZ 1SV/ { 2Ʀ1 "p_88_p`88O(#5!5&'.4767675!5!2>4."RJrCEoJRXErrJSQ[ZZ 1SV/ { 2Ʀ1 {"p_88_p`88Q %@   1/0!!#!3BQ *@  1@  /20#5!!#!3ԓ} %@   1/0#5!!#!+Q (@   <1 /0!!#3!3OQ -@   <1@   /20#5!!#3!3ԓ} (@    <1 /0#5!!#3!B /@   <<1@   /20!!!5!3z;  K"qѓB3@   <<1@  /220!53!!5!3z;7 K"ѓm /@    <<1@  /20!53!!5!z;7 K"ѓ+q &B@%(E# E'ܲ@ ]<<ܲ@]1@ # $ /<<02>4."&'.4767673! [ZZRJrCEoJRXErrJS"p_88_p`88~ 1SV/ { 2Ʀ1  (F@ *E#'E)ܲ@]<<ܲ@#]1@' (/2<<02>4."!5!5&'.4767673 [ZZlRJrCEoJRXErrJS"p_88_p`88 1SV/ { 2Ʀ1 O &B@(E# E&'ܲ@ ]<<ܲ@]1@ #  %/<<02>4."5&'.4767673!5 [ZZRJrCEoJRXErrJS0"p_88_p`88 1SV/ { 2Ʀ1 {q*!&'.4767675!5!!!2>4."RJrCEoJRNXErrJS9[ZZ 1SV/ 2Ʀ1 "p_88_p`88 ,%!5!5&'.4767675!5!!2>4."RJrCEoJRNXErrJSQ[ZZ 1SV/ 2Ʀ1 p_88_p`88O*)5!5&'.4767675!5!!2>4."0RJrCEoJRNXErrJSQ[ZZ 1SV/ 2Ʀ1 p_88_p`88 'b'bb 'b'bc 'b'cb 'b'cc 'c'bb 'c'bc 'c'cb 'c'cc  :@   @ ? o ]9999991 2<0#'##'##'d2222222dddddV!#!3!3#3jժVV8`!##333#{}`9VVX{ %5#"&5332653!"&'5326Cuȸ||aQQRjBfca{+,*}GR'>}GR'-}G'L'-}Gx'0}G'2 ~&'>X  ~&'-4H ~&'-('-4H ~'.  ~'2 G&'-_ - &'-R-7&'-R- G&0x  &0  7&0  G&'/ 0x  &'/ 0  7&'/ 0  &.x2&.X&.X &/~ 2&/ &/ &/R&/|R&/|Ru F&/,@&/,F&/,\&6-k?&7- &8-\L&6'--k?&7'-~- ~&8'--\&60?&70, &80,k &9-k?&:-~ ~&;-k &9/k?&:/,~ x&;/>7%2$6=4'%$=4767!;#"&'#!"'$4733k1yY `h_ /.Z\9 Sl ?AhXl k7>c`7# #5&E^209&b \^~B" #5!276'&'%$=4767!#. cY `h_ >_߸h,n7>ba7# #5&qe)#5!27654'%$=4767!;#"&'#9pY `h_ /.ZZ8 `h7?ba7# #5&E_/(W&(>F&>&>&-Fr&-r&-&0X&0&0+&,>F+&->+&.>+&,.X+&-.+&..4&,/4&-/K4&./K#&-j&-&-#4&/4&/4&/#&0& &0c &0+&8'-?&-'-R&4-~'-R&5-+&8'-?&>&'-R&4>P~'-R&5>P +&8/& X&40c ~X&50c5 b@ <2991/0K TX @ 878YKTKT[KT[X  @878Y P ]#53#3+e $@/  !# #%" " "!& %999919990KTKT[KT[X%%%@878Y@ ttttv]33267#"&546?>7>5#537ZZ:3mN`^gIYX0&DeWX5^1YnFC98ŸLVV/5<6XuX %#!5!276=3%Hc,1VV,1jٻ#!!!!# -#!!!R0S O -#f# !! !!5 N+)# m@>2&#"#"&'7327 ֳPd \ jkPd 켰}켰H&LL Q'L'LL@-6?67632&#"#"'&'7327&'&54767676'4'&' uphmdNPd ]z vphmdNPd fuV?bd4?V?fj4`\cM["jݜx9`\cM"gݢzBfd3vAif3HMQZc67632&#"!67632&#"#"'&'7327!#"'&'7327&'&54767!!676'4'& uphmdNPd  upimdNQd ^z upimdNQd  vphmdNPd fu"V?fj4Q V?bd4`\cMl`\cM["jݜx9`\cM@`\cM#hݢz[Aif3qiBfd3 Pmqu~67632&#"!67632&#"!67632&#"#"'&'7327!#"'&'7327!#"'&'7327&'&54767!)!67654'& uphmdNPd  upimdNQd  uphmdNQd ]z vphmeNPd  upimdNQd  vphmdNPd fu"""V?fj4YV>be4`\cMl`\cMl`\cM["jݜx9`\cM@`\cM@`\cM#hݢzA[Aif3qiBfd33&#"7#754'&'#"&'7327#4767>32nSb ~YfMfpH>> fpSb =[Bzz fp)"X*e e*mH鿬Bxۛz鿭>4'>7'7&#"7#"&'7327&'&54767>32mV?fj4Vs eSb ~kef%Uz lkSb fu lkAif3ETieX[mee_X9鿬"gݢz=.'>7'&#"'#"&'7327&'&54767>32mV?fj41M>V vfSb fj lkSb fu lkAif3]Di[wf&["Yfj9鿬"gݢz P# ! !Ass # % O P# ! ! O $# # #KuuO;>^8bC# 3 35.Ae8^?R u7!!  767654'&'  $'&'&7676R<¡¡?3dccddccdYTRzzSSSRRYR u3?  767654'&'  $'&'&7676!!#!5!<¡¡?3dccddccdɵeepTRzzSSSRRYɵjeeR u3?  767654'&'  $'&'&7676   ' <¡¡?3dccddccdɵsssspTRzzSSSRRYɵssssY&L'L'L L@327!5!>32&#"!!#"&'wSb -}# lkTb "r/ lk۸鿭?/鿬@'327!5!!5!>32&#"!!!!#"&'wSb n lkSb uc lk۸T鿭_O鿬@&#"#"&'73275>32nSb %,, lkSb %9, lk \鿬鿭@,5&#"&'67#"&'7327&'&54767>32nSb h9q-XLa lkSb fu lk-V?fj4[:\f.i3 9鿬"gݢzAif34&#"676='3'#"&'7327&'&53>32nSb =[Bzf L fx jkS` XqH?> jkgLBx+f f+ݘz鿬#XڡnHcJ@!!!!!>2&#"!!"&'732f* ֲPc  ΰPe  `켱b@,!!>2&#"#"&'7327326&f*֌:, ֲPd ]z jkPc 9L~켯["jUx9켱l2G1!!327326&# >32&#"632#"'#"&'+Sb &@Oàv"jkSb S`${g jk¸X(L(XKT/鿬@ 4!!6& 327&'&767>32&#"#"&'_*Sb fu lkSb ]z lk(͓l"gz["jTx9鿬@"&7!>2&#"!!#"&'73273!#3 ֲPd A fePd "C;켱RfwxKKB/&#"!2#567&'!#"&'7327!5!>32nSb $~ˇ y/ lkS` -c C& lkgL)鿬fcJ@%&#"7 '#"&'7327' 7>32nSb ީd# lkSb !g lkޫ鿬de鿭@,327#7>32&#"#'#"&'wSb @\-J۫ lkSb Xz@ lk۸)FqG% 鿭r"b/XG鿬@)&#"63#"&'7327&'&3>32nSb <~ lkSb Yz= lk 6s.7鿬!b/FE鿭N >2&#"#"&'7327!5 ֲPd \ kjPd 켱}켱 xL@>2&#"#"&'73275! ֲPd \ kjPd HV켱}켱^_<++LY mQY^f55q=3=dd?y}s)3s\\?uLsLsyD{={\{fqqq/q999qqJ+o#7=V;=3X55^R\sd5^5bs#5`b?yyyyyys\;\\\3 LsLsLsLsLsLf {{{{{{{fqqqqq9999qqqqqqH==y{y{y{sfqsfqsfqsfq)q3 qqqqqq3sq3sq3sq3sqTx\9\9\9\9\9r\9?u9u9uuFLsqLsqLsqs/qJJJ+o+o+o+o#7#7#7DV={\3X{\3X{\3X/ }}ssfq3 }qqLu3s~\ 9 =LsNgvsq7r+d#7#7N={\3XTT\h3qT]hX\] ` d <qKsday{\9Lsqqy{y{{3sq3sq?LsqLsqTX9 ` d <q3squy{{LfHy{y{qq\9\9LsqLsqJJ+o#7,Gqqq{\3Xy{qLsqLsqLsqLsq=79qqy f u +o3XPP}  yq\9@sq J qefqqqqq|SA4Pq9qq q``9t*KM:+#qqGpPPOJI>>t+o7#7#7q=V=f3X3XXmXXXXLsPqq;VVqXXqvqq77:7/<66JO<u1ufu]H^H 6&:uuuuuu  3s3soouuuuddLhuTzuuu%q7]yq$U $ zw(j#Lcxh!c+qc3x+x.pppp*pw<.::3efqe\sDy}uy{\Ls\?yLsLs{=LsN\FqSFq qSZkq=xJvkqJqqdGp;Gpq?qWWGpAOpLsq0q@GGrwxssFqU-~Od$s6sq,J7Opfq9Lsqs5UsssJs\\\T\J#y}}@e(!TLss#y{=6|<}o{p4kq5FA33L ;q;fq<=p;rR>Qdqtqq/4dq+o9998L07/3=;xs*` D3 GLsk7sS[2Lsq@R2@R2s<qsq pv9xssfq;XXX.j}!&4fG8=(5F!A!=2*ISsqsfq<=={=;yt|||\(5F?56].I6r|29y{y{{qLuqLuq(5F!ATX33LsqLsqLsqodq#=#=#=|4QfG8{=;{=;}q -qn6.3sGq/STL ZTL'AtLsqDVT>LLXv!]Z-]D`F d#iUxgZ%UdM|x2LsRnuu>ZCqNqCq,qj,< {n0oz)oqF~dDDcc/NDdc\\fcYXLX^X:.X:0LX;XXOoX.4X1XQXXXXBbS(?99l9lC91***}} ffuuXK5k1CCOOLLLRLLLLLUL<L<LdL\W5kVz*******}}}}}}}}}== fuKKKKKK5k5k)n)))))))*CCC1LLLRLLLRLjLL<L<Ld9qd==;;q;q=x==D=;==p==q==.qqB[B[{d{d]xmxs[")>WE_IIY"~h~h@sx2OsOsxMo`P{P@@@@`NzBza\d>N c c]ccY]dji::xnnond$P<Py<x<xd@2 PKnddZ nxyxd<<o<nPd$dZddd)hd$dZd.d.Jdd$d^d.<Z. ddJd$d$<.Z.d$)d.d$xdd$doddjZdPdndyyyy'''''w'w'ww'w Xc^c^%H Ewyyyywwwwwwywwwwwwwwwyy^^^l4wl4w4y4yywyFFFF*F*F*FAA8F3F3FFFF*F*F*Fzzwuuuwwwuu&w&w&wwFF wwwFFGwyFyFFwFFFwwwFFGwy=Fy=FGwGw=F=FwFFFFFV+V+FFV+V+VY]YFFF"F"F"F"FGFGFGF F F F F wwww?w?w?wYSSwSwSSSFFFFYwyyyyMMwwdwSSyy4yFwwFFww```````FwFw     FFwwFF%w%!%!%w%w%!%!Y )#su` z    s 4 s 3  E p 2 O 3wq= {>fq$S9( 3qfyqyqy3/qqq222</=V3X5x=2ZLr u//SH||NYHG p+"M"M>G/Mmu>GVGVGTR>GnzhuuEuOGGOGOGmu\#=nnuV&7yGSG%nzu=nV&7yKySG%tV29>GGGOGT_>G=nIzIIVz[quuIuEqOGOGFK\#^YGu@zV&7~77#7OG[[[[BBy{}}}sfq)q)q)q)q)qqqqqq/3sq\9\9???uMuMu9u9LsqLsqLsqLsqJJJJT+o+o+o+o+o#7#7#7#7y=y=DVDVDVDVDV{=;{=;={\3X{\3X{\3X#V={//&qy{y{y{y{y{y{y{y{y{y{y{y{qqqqqqqq\Z9D\9LsqLsqLsqLsqLsqLsqLsqNgvNgvNgvNgvNgv====' FqFqFqFqFqFqFqFqyy'iSSSSSS0l7hx qqqqqqoE.k_FqFqSc<qqFqFqFqFqFqFqFqFqyy'i7hxk_FqFqFqFqFqFqFqyyy<pr\\D~{aNsVddddd%%%%9933W q q(()((()( 33?nn=V`Jd=n=dn8N(ffadp5Wnz5?5f5\5l5Y5S999og0u5W55^5b5?5f5\5l5Y5S999og"MVGOGuVG<uhuTzu0umuu\#Vs`" .;F_q( ..D]1uj C===P=&C&Cs#&<<oI HZ;jDN hR6nLsbBSV,y('y\XNND?yJ\}WJT9hgd(V FhZ $<|3uuWZ[O=;6Q ^^b?fbfl\bya W{=w= =us)9~=}== ]=;;;9fqq y) ysesWdud    du,dudududvdvdd*ZZd-Opdduudwddxvxddddudud  dududuku7^H^^^@^^^uzz^uwududdud7u7y#_ZZ,dDX===,,ff+uPuuu+uPuuu+u+u+uyyy``>>**yyby*cc| a aXXJr;xxdxxd++* 8 8 P 8 x PFq 8#+7'Y,,,,,,,,,,<<<<<<xxxxxxxhh''''''''''''''''''''''q''''''''''llgg'''''''''''''''''pprppppppppp7p7Tpp''''3'''ppppp'''',h,d,,,,+,}}_}} ,,,B,d,,,,,,,,,,},,,dZd2E\,,,,,,,,,,,,,,,WWW,,,,,S,,,,,],,,,,m,,E,,,,A,,,U,,Q,0,,,U,,L,0,C,,X,,B,,X,,,x, ,,,,,,,,,,,,,,1,,,,,,,,,,,X,X,j,, T},y,},),,,,,,,d  f9  dT YxDVVVVVIVVx+5X3ppppR >pTVSTWW/V0/0002p@TTTTpnnTVaaTT,f,z,z,z,z,xNNx>NnX~#9Uwlf,,,,,,,,,,                    uuuuuuuuuuuuuu++<uusunOss[YOO Bu xd xu xd xd xu xd xd xu xd xu xu,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,duwOwO::: u+u+u+u+u+u+u+u+u+u+u++u+u+u+u+k  77^^  7^uuHH''''$$"pMMu 9 u H#?{\3X@sy@s= DVh<GpPqbfr {\{2PiIPlhPmPihhsshhfch{dPhhPVz]P<`FPPdz|"h5z,qssu@xC@~yyv{\{\ssg)?>8{\(oo:o\:o\csssss$d{=syNsNs6??,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,}F)3s??ss{\u;yy3D{=}yy\Lsu#sffffr+d pv9;<@>speKkT5L mLsqsq s&q:Bz<<| s&qffss7S+o { { #{{{{se? q#Sjxt  s&q 222Lsq  u 9553|M.U3?J+h'GRwF\ Dn`#Zn`nn`n#Z`n3nnnnon9nnqnn YnnnnnOnn2nn)nnn1pnn!2nxnnnnnszwddddddddddd$d$d$dKdKdKdKdKd_<_<_<_<_<_<q/ / ///}/o } <VJN1X?,XXuX`XNXXYYYXLX^X:Y?Y0YLX;XXoX2X1XXXB.X;XX:j:j:j:j:j:jKH KH ************}3}}3}}3}}3}jj))k))k))k))k:j8k""""C:j:jC:jp*XXXiXXXXXXXXXXX9p9lpl"9lplC:j9p:j1J:j:j*********}3}}3}jj 3# 3#  f^f^uBuuBu/KH 5kk kpSI:j1J8"CC:j..TT4dddd:D_ j d:}d}xzd8vvddDd}d,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,c3s$f"=3LrDrlK{fqo/q5 "qqq+o7=HVhL=Xy}s)3s\?uLsLsyD{={\{fqqq/q999qqJ+o#7=V;=3XkZqAjd9*}*fC uK 5k *} fuK5k4VV4j4/44V//@bb@?@ $6C ;C $@@b ;6@C ///////////////////////////@///////////////////////////////WWWWWWWWX xKW= @WW Y_WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW Y_WWWW#WW WWWW: Y`W:W=W=W=W=W=W=Ws N:jH k :j:j:j********_9xxxxxxxxvxxvxxvxxxvxvxxxx,p:jj9Jqq9O99:::qd=dd=;;;;;;q;;;q=xxx==D=DD;;;====p==q======...qq,,,,,,,,.j3}3}3}3}3}jjjjj 3# 3# 3#  ^BuH H H k k n)kkk)k)k)kppp:j:j:j55??4#   G G G G PR PR PR? 0 $%*K-r294K7D9:;< R&Y\99999 &&&&&K&D: $$$$$9$&$*$2$4$7a$9}$:$>?@@@@@ABXCpDDF$FHHIIJ KXKLLM,MN8NOHOtOP,PQQQQRRS S8ShSTUUUUVV@VXVpVVWWWWXXDXtY<ZtZZZ[ [$[\\]]<]h]]____``,`D`\``bTblbbbbccpddee4ede|ffLfxfffggg4gLgpgggggh h4hDiii<i`iiiiijj4jXj|jjjjkkkHkllllmm8m\mmmmn,nPntnnnnoop,pDphppppqlqr rDr\rtrrrsttHthttttuvvvvw w0wXwpwwwwxx,xTxlxxxyXyz zDztzzz{{${<{T{l{{{{||4|L|d|||||}<~~~ Xxt8p<P |H`x d$ @l(`,44 (@Xp0Pp $<Td|\t,D\t$4Ld| $<Tl,D\t<T ,D\t(xX<D@L(ŒL\p Ũh$L(ɈʬD̔μlP$4PԤՈ@@ڄ\ܐߴLxXX@T`th T $Ht|<LP8(l@`d$| d      ( `  0  D l   l $Td (Lx T8`0@tLPL4H\ | 4Xtt 8TLp , h   !!!(!<!!!!""\"""#$######$$$8$`$$%$%%%&&&P&h&|&&'4'H'\'x'(((D(T(d((()$)4)D)x)))))*(*X*p*****++ +8+H+X++, ,,,,,,-`-p---...../l/|00011 181P1h123l3456D6778X889|:8:H:;X<<<=>D?P?@x@@@@@A0BBC`C|CD\E EFpGGH\HlIIxIJ`JKLM@MNOhOPPQQRHRSPSTXUU U0U@UVV,V<VLWWX(X8XPXhXY YYZpZZZZ[H[\H\`\x\\\]x]]^L^\_P```aaabbb`bpbbcccd0ddeTeef`g ghhhxi@ijjjkl`lmmnnnno8oHoXoopppqxqr@rrsDstuu4uvxvw4wDwTwdwtxxy4yLydy|yzz{\{||}4}t}~(px4Ldp|`,T|(PTp4Ld8X4((\ lh84$`<Lx Dhx(8Ph,D\t4t$th8|8| 8DTdd0t ,<LHt<`ŒÀ8\ļpż(ǨȘt0ʌ8ːˠ̠ͤLτϼDXlѸ4|Xӄp(HP֤֔hװ`ج$٬ټLۄL8ݰ4D8\߄H| |T@|dLp0plD `t dd@`\,D`x@X @X 8T`p@\dx@\`L4 Dx     , D d |       $ < T l        4 L d |       , L d      8P| $<TlLd\|,<L\ll(p@4@L0|t L !!!""#4##$L$%h%&&d&&'T'((,(l(()4))*\+ +,-@-./0(012p334P5 567X789X:8:;>>?4??@,@AAAB\BC8CDhDEEFFFGHHhH|HI,IJ$JK4KLMN NxO`PpPQ$QtQR0RRSLStSTHTUUUV0VVW0WX8XxXXY`YZZtZ[<[[\\X\]]X]^<^___`d`aHaabbcPcddpde<effggghlhi8ijjxjk<kl$llm`mnXno oop,pxppq q$qqqqrHr`rxrrrrrss s8sPshsssssst$tDtdttuu\uuuv v8v\vtvvvvvvw wpwwwxx4xLxdx|xxxxxy y$y<yTylyyzz0z@zXzz{D{\{t{{| |$|<|T|l||||||}},}D}\}t}}~~~0~~@X0H`x4h 8Ph(@Xp8P0H`x 8Ph4Th(0H`Dp $<Tl,D\$<00H`xXhTl4Ld| $<T 8 8 $<Tlt,xx\t4Phx $8Ph\@X4DTl 8Ph<h,h(dh0H`x 4d4\ \@PPxPxhD` DÀÐ`ĸH<tƸ(ǘǨ(Ȅ(xɘɨ<LʀH˘h̴̘8p͠08ϤHPѸҐ\x ՜0Tֈ$׸<ؤ|ټTXېܔ(<Pdhx޸D߀H\0tHpx | \< <P$Dp0H`x 8Ph(@`(@Xp0H`x(@Xp0H`x @Xp0H`x @`x 8Ph(@Xp0H`x 8Ph(@Xp(@Xp0H`x 8Ph(@Xx 8Ph0H`x 8Ph(@Xp0\(@Xp $<Tl   < X p        4 P l       $ < T l        < X t        8 P l     4Ld|0H`x,Hd 8H`p(@Xp0H`x 8Ph(@Xp0H`x0@P0H`|4Ldt4Ld|4P`|,HXtDT0l`  L !(!h!!!"@"X"X"X"X"X"X"X"X"X#$%%,%L%h%%%&4&&&'p''(D(p((()()X)p))**H*x***++ +D++, ,@,h,,--T--.$.$.$.$.$.$.$.$.$.$.$.$.$.x./0,0012 2T2p223 33333334 4 444H4\4p444444455$585L5`5t5556$67d78 899:;t;;==>D?@lAABBC4ChCDLDDDEE@ExEEFGHHIDIJxJJJKLM4MMNO0PPQ QPSTUTUVdW|XxY(Z@Z[[\t\]]t]]^^^^_`a abdcDcpdeef<fg@gghiiiijPjjk0k\klm0mhmnhnnno o@o`oooopp p@p`pppppqqq(q@q`qqqqqqrrr(r8rPrprrrrrss s8sXshsxsstuvxvvww$wxy4y{|(|X|||}4}~(~hH  `l0h X@Tl,4Xt<lTT L LD<X\TT80x,Td0XPHPTT Xl| \|Ll@t4L0 ThpD,(@`@8xlń<ƨ8 Lȴ`ɤ,ʘ@ˬ̘xμϐTЌ0LҼ,pӴ Ԍ0լ,֘d|`ؔڔd ݼޠ`d<x <l\ xP(xL0\ T d@ h8XpXTTDTx ,PtP4tP 0\((Px4XxhX p  P ` p     P     0 h    (\@<h (,,"%'t''''((P((()<)))**<*`****++4+X+|+++,,(,P,|,,--,-T-|---.$.T..../(/P/x///00H0t00011D1t1122D2x2233T3334(4\4445 585`55566@6l6667,7X77788`889989h99: :L::;4;d;;;<<0>4>P>l>>>>>??0?L?h???@B<CCC(CDCXClCCCCDD DDD`DDDEPEFGtGIIJJ JLJhJJJKK,KHKpKKKLLLHLdLLLLM M<MhMMMNNHNO,OPQ@QR`RSSlST0TTTU UUV,VXVVVW$WhWWWWXXDXpXXXY@YlYZZ@ZZ[[\\]](]P]x]]]^^0_ _`Di(l lDllm\mnoopsuv,vwwpxz|zz{|}X}~|@,4<(p<$Dd\x TDl D$h hp`|ld(@(Dt 8  Dxh<HȀx̜͐DҠ`TpԘ԰xLDXpxt$l4x8Dt \`h  l  h  T @ 8hH,D\x8D 0  !P!"l"##X#$&0($)P+,/X458489:<=>@XACC|CDPDEE8ETEpEEFF,GGH\HI@J`KHKL$L`LLLMMDMlMMMNTNO4OQ QRTTTWXZZ[]]^_`acd8dehf0fglh4hijLkk@klkkklllm$mxmmnn npnnoodoppPppqDqrrssLst0tu<v vwxy|yz<zxzz{{0{H{`{| |}~< L$`|| 0D`|0D`|0Lp<p Dp$P|@d@l4p Dp$P|@d@l4p$P|,`0T @tD,p Dp$P|@d@l4p$P|,`0T @tD,p$P|,`0T @tD,p LP0lD,p`$pP`( 8t@th(pX<dxDTl0H`x`,|ŐLư ǐȠ0ɌXʼ4Ld˨8̈|Ψ,tϸ|$ќԠpռL֔$׀ר$P|بLلټHtڜ,\,hܬ<`ݜ$ޔ,8HLhL|LH\4hx( \<(8XhLPD\L$4D0D4 p8l4D`,l|X l P    \ @Ddt$<(h<\|4Ld| $<Tl,D\t4Ld| $<Tl,D\t8HX 0@ 0@P 0@P<X|  8 ` p   !|","D"\"#<#$$t$%%P%%%&&x&' ',())x))*h*x****++8+`+++,,$,D,h,,,,-(-T---..L.|.//0,0<0011l2H2334p45456 677,7<7778 8P8|99:@:;@;<$<=H=> >t>?8?X?x?@$@4@\@A AA|ABBCHCDDpDEtEFdFG@GH$H4H`HHIILIxIIIJJ$J8JLJ`JtJJJJJK K K4KHK\KKKKKLNQ\TVW WtWXXY\YZ`Z[[\4\]]l]^P^_ _`8`a,abbpbcDcd0ddelef\fg0ghHhiijXjk kklHlmmmn<noop(pq(qr$rs,st8tu\vvw`x xyzd{ {|X} }8}l}}~<~d~4|@Xp(@Xt 8Ph(@X $<Tl,D\t4Ld| $<Tl,D\t4Ld|8HXp0H`x0H`p$4Ld| $<Tl$4D\ttttttttttttttttt Th4H`t(@Xp,D\t4Ld|l@Xpx0H`xdxpXp0H`xTHXDTd $<Td(8(@Xp,,,,,,l(X\xH4|tDx(p(D`|0X$LtDl8`,T$Lt@l 4`,T| Lt@h 4\,T|¤À,h0|LDŽǼ0l(pʰ(<̤ltθ4\Dl| dtՄՔդմ$4DTdtքִ֤֔$4DTdtׄהפ״$4DTdt؄ؔؤPt0ۼ`p݀ݐݠݰ 0@P`pހސޠް 0@P`p߀ߐߠ߰ D\t,D(@Xhlpd(@XX8,\LlLH   l < "p$p&)(+.0247p:4=<@l@@ALBCD@EGpGHIJL`NPPQSTV<X8Zl[h\^$_adfgi4jlo(qtHuw|yl{~$l0 PhD@tTT<P(8$<|L @,xhphĸŴƈ,h(Ȍ\ɸ$ 4TTTجٰڄݤބߐpl` |Pd<|dtXX$d`  ,l,L `! !@!"@"|"""##D#h#$$,$<$%&8''' '0'@''''(( (8(P(h(((((())0)P)p)* ***++$+@+\+x++++,, ,<,X,t,,,,---4-l---.. .8.X.x..///$/</h///00l0000011101H11123(34 445(5X556,6x677X889T9::`:;<8<<=T=>>p>?t?@AAB BxBCD,DE4EFFG$GHH`HI,IJ$JK|L$LLMXMNO,O|OP$PQQRRSSdST TdTUUUVhW,WXYDYZXZlZZZZ[ [D[h[[\4\p\\] ],]D]\]t]]]]]^^<^T^l^^^^^__,_D_\_t_____```4`L`l`````a a$a<aTalab0bc$c<cTclccccccdd,dDd\dtdddddeee4eLede|eeeef f,fLflfffgDh<hPhhhii`ixijhkmLmnooop<pqLr$ss8st(tuPuvpwwx\xyzz{${{mT+h >2   : `   (Z4;b ;; 0    " F m " : %: h: ; ;Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. DejaVu changes are in public domain Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. DejaVu changes are in public domain DejaVu SansDejaVu SansBookBookDejaVu SansDejaVu SansDejaVu SansDejaVu SansVersion 2.37Version 2.37DejaVuSansDejaVuSansDejaVu fonts teamDejaVu fonts teamhttp://dejavu.sourceforge.nethttp://dejavu.sourceforge.netFonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjung Bah (see below) Bitstream Vera Fonts Copyright ------------------------------ Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. Arev Fonts Copyright ------------------------------ Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjung Bah (see below) Bitstream Vera Fonts Copyright ------------------------------ Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. Arev Fonts Copyright ------------------------------ Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.http://dejavu.sourceforge.net/wiki/index.php/Licensehttp://dejavu.sourceforge.net/wiki/index.php/LicenseDejaVu SansDejaVu SansBookBook~Zm  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghjikmlnoqprsutvwxzy{}|~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~                           ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~        !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm sfthyphenAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflex Cdotaccent cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflex Gdotaccent gdotaccent Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccent kgreenlandicLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheEngengOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflex Tcommaaccent tcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentlongsuni0180uni0181uni0182uni0183uni0184uni0185uni0186uni0187uni0188uni0189uni018Auni018Buni018Cuni018Duni018Euni018Funi0190uni0191uni0193uni0194uni0195uni0196uni0197uni0198uni0199uni019Auni019Buni019Cuni019Duni019Euni019FOhornohornuni01A2uni01A3uni01A4uni01A5uni01A6uni01A7uni01A8uni01A9uni01AAuni01ABuni01ACuni01ADuni01AEUhornuhornuni01B1uni01B2uni01B3uni01B4uni01B5uni01B6uni01B7uni01B8uni01B9uni01BAuni01BBuni01BCuni01BDuni01BEuni01BFuni01C0uni01C1uni01C2uni01C3uni01C4uni01C5uni01C6uni01C7uni01C8uni01C9uni01CAuni01CBuni01CCuni01CDuni01CEuni01CFuni01D0uni01D1uni01D2uni01D3uni01D4uni01D5uni01D6uni01D7uni01D8uni01D9uni01DAuni01DBuni01DCuni01DDuni01DEuni01DFuni01E0uni01E1uni01E2uni01E3uni01E4uni01E5Gcarongcaronuni01E8uni01E9uni01EAuni01EBuni01ECuni01EDuni01EEuni01EFuni01F0uni01F1uni01F2uni01F3uni01F4uni01F5uni01F6uni01F7uni01F8uni01F9 Aringacute aringacuteAEacuteaeacute Oslashacute oslashacuteuni0200uni0201uni0202uni0203uni0204uni0205uni0206uni0207uni0208uni0209uni020Auni020Buni020Cuni020Duni020Euni020Funi0210uni0211uni0212uni0213uni0214uni0215uni0216uni0217 Scommaaccent scommaaccentuni021Auni021Buni021Cuni021Duni021Euni021Funi0220uni0221uni0222uni0223uni0224uni0225uni0226uni0227uni0228uni0229uni022Auni022Buni022Cuni022Duni022Euni022Funi0230uni0231uni0232uni0233uni0234uni0235uni0236dotlessjuni0238uni0239uni023Auni023Buni023Cuni023Duni023Euni023Funi0240uni0241uni0242uni0243uni0244uni0245uni0246uni0247uni0248uni0249uni024Auni024Buni024Cuni024Duni024Euni024Funi0250uni0251uni0252uni0253uni0254uni0255uni0256uni0257uni0258uni0259uni025Auni025Buni025Cuni025Duni025Euni025Funi0260uni0261uni0262uni0263uni0264uni0265uni0266uni0267uni0268uni0269uni026Auni026Buni026Cuni026Duni026Euni026Funi0270uni0271uni0272uni0273uni0274uni0275uni0276uni0277uni0278uni0279uni027Auni027Buni027Cuni027Duni027Euni027Funi0280uni0281uni0282uni0283uni0284uni0285uni0286uni0287uni0288uni0289uni028Auni028Buni028Cuni028Duni028Euni028Funi0290uni0291uni0292uni0293uni0294uni0295uni0296uni0297uni0298uni0299uni029Auni029Buni029Cuni029Duni029Euni029Funi02A0uni02A1uni02A2uni02A3uni02A4uni02A5uni02A6uni02A7uni02A8uni02A9uni02AAuni02ABuni02ACuni02ADuni02AEuni02AFuni02B0uni02B1uni02B2uni02B3uni02B4uni02B5uni02B6uni02B7uni02B8uni02B9uni02BAuni02BBuni02BCuni02BDuni02BEuni02BFuni02C0uni02C1uni02C2uni02C3uni02C4uni02C5uni02C8uni02C9uni02CAuni02CBuni02CCuni02CDuni02CEuni02CFuni02D0uni02D1uni02D2uni02D3uni02D4uni02D5uni02D6uni02D7uni02DEuni02DFuni02E0uni02E1uni02E2uni02E3uni02E4uni02E5uni02E6uni02E7uni02E8uni02E9uni02ECuni02EDuni02EEuni02F3uni02F7 gravecomb acutecombuni0302 tildecombuni0304uni0305uni0306uni0307uni0308 hookabovecombuni030Auni030Buni030Cuni030Duni030Euni030Funi0310uni0311uni0312uni0313uni0314uni0315uni0316uni0317uni0318uni0319uni031Auni031Buni031Cuni031Duni031Euni031Funi0320uni0321uni0322 dotbelowcombuni0324uni0325uni0326uni0327uni0328uni0329uni032Auni032Buni032Cuni032Duni032Euni032Funi0330uni0331uni0332uni0333uni0334uni0335uni0336uni0337uni0338uni0339uni033Auni033Buni033Cuni033Duni033Euni033Funi0340uni0341uni0342uni0343uni0344uni0345uni0346uni0347uni0348uni0349uni034Auni034Buni034Cuni034Duni034Euni034Funi0351uni0352uni0353uni0357uni0358uni035Auni035Cuni035Duni035Euni035Funi0360uni0361uni0362uni0370uni0371uni0372uni0373uni0374uni0375uni0376uni0377uni037Auni037Buni037Cuni037Duni037Euni037Ftonos dieresistonos Alphatonos anoteleia EpsilontonosEtatonos Iotatonos Omicrontonos Upsilontonos OmegatonosiotadieresistonosAlphaBetaGammauni0394EpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsi IotadieresisUpsilondieresis alphatonos epsilontonosetatonos iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronrhosigma1sigmatauupsilonphichipsiomega iotadieresisupsilondieresis omicrontonos upsilontonos omegatonosuni03CFuni03D0theta1Upsilon1uni03D3uni03D4phi1omega1uni03D7uni03D8uni03D9uni03DAuni03DBuni03DCuni03DDuni03DEuni03DFuni03E0uni03E1uni03E2uni03E3uni03E4uni03E5uni03E6uni03E7uni03E8uni03E9uni03EAuni03EBuni03ECuni03EDuni03EEuni03EFuni03F0uni03F1uni03F2uni03F3uni03F4uni03F5uni03F6uni03F7uni03F8uni03F9uni03FAuni03FBuni03FCuni03FDuni03FEuni03FFuni0400uni0401uni0402uni0403uni0404uni0405uni0406uni0407uni0408uni0409uni040Auni040Buni040Cuni040Duni040Euni040Funi0410uni0411uni0412uni0413uni0414uni0415uni0416uni0417uni0418uni0419uni041Auni041Buni041Cuni041Duni041Euni041Funi0420uni0421uni0422uni0423uni0424uni0425uni0426uni0427uni0428uni0429uni042Auni042Buni042Cuni042Duni042Euni042Funi0430uni0431uni0432uni0433uni0434uni0435uni0436uni0437uni0438uni0439uni043Auni043Buni043Cuni043Duni043Euni043Funi0440uni0441uni0442uni0443uni0444uni0445uni0446uni0447uni0448uni0449uni044Auni044Buni044Cuni044Duni044Euni044Funi0450uni0451uni0452uni0453uni0454uni0455uni0456uni0457uni0458uni0459uni045Auni045Buni045Cuni045Duni045Euni045Funi0460uni0461uni0462uni0463uni0464uni0465uni0466uni0467uni0468uni0469uni046Auni046Buni046Cuni046Duni046Euni046Funi0470uni0471uni0472uni0473uni0474uni0475uni0476uni0477uni0478uni0479uni047Auni047Buni047Cuni047Duni047Euni047Funi0480uni0481uni0482uni0483uni0484uni0485uni0486uni0487uni0488uni0489uni048Auni048Buni048Cuni048Duni048Euni048Funi0490uni0491uni0492uni0493uni0494uni0495uni0496uni0497uni0498uni0499uni049Auni049Buni049Cuni049Duni049Euni049Funi04A0uni04A1uni04A2uni04A3uni04A4uni04A5uni04A6uni04A7uni04A8uni04A9uni04AAuni04ABuni04ACuni04ADuni04AEuni04AFuni04B0uni04B1uni04B2uni04B3uni04B4uni04B5uni04B6uni04B7uni04B8uni04B9uni04BAuni04BBuni04BCuni04BDuni04BEuni04BFuni04C0uni04C1uni04C2uni04C3uni04C4uni04C5uni04C6uni04C7uni04C8uni04C9uni04CAuni04CBuni04CCuni04CDuni04CEuni04CFuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04D8uni04D9uni04DAuni04DBuni04DCuni04DDuni04DEuni04DFuni04E0uni04E1uni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04F6uni04F7uni04F8uni04F9uni04FAuni04FBuni04FCuni04FDuni04FEuni04FFuni0500uni0501uni0502uni0503uni0504uni0505uni0506uni0507uni0508uni0509uni050Auni050Buni050Cuni050Duni050Euni050Funi0510uni0511uni0512uni0513uni0514uni0515uni0516uni0517uni0518uni0519uni051Auni051Buni051Cuni051Duni051Euni051Funi0520uni0521uni0522uni0523uni0524uni0525uni0531uni0532uni0533uni0534uni0535uni0536uni0537uni0538uni0539uni053Auni053Buni053Cuni053Duni053Euni053Funi0540uni0541uni0542uni0543uni0544uni0545uni0546uni0547uni0548uni0549uni054Auni054Buni054Cuni054Duni054Euni054Funi0550uni0551uni0552uni0553uni0554uni0555uni0556uni0559uni055Auni055Buni055Cuni055Duni055Euni055Funi0561uni0562uni0563uni0564uni0565uni0566uni0567uni0568uni0569uni056Auni056Buni056Cuni056Duni056Euni056Funi0570uni0571uni0572uni0573uni0574uni0575uni0576uni0577uni0578uni0579uni057Auni057Buni057Cuni057Duni057Euni057Funi0580uni0581uni0582uni0583uni0584uni0585uni0586uni0587uni0589uni058Auni05B0uni05B1uni05B2uni05B3uni05B4uni05B5uni05B6uni05B7uni05B8uni05B9uni05BAuni05BBuni05BCuni05BDuni05BEuni05BFuni05C0uni05C1uni05C2uni05C3uni05C6uni05C7uni05D0uni05D1uni05D2uni05D3uni05D4uni05D5uni05D6uni05D7uni05D8uni05D9uni05DAuni05DBuni05DCuni05DDuni05DEuni05DFuni05E0uni05E1uni05E2uni05E3uni05E4uni05E5uni05E6uni05E7uni05E8uni05E9uni05EAuni05F0uni05F1uni05F2uni05F3uni05F4uni0606uni0607uni0609uni060Auni060Cuni0615uni061Buni061Funi0621uni0622uni0623uni0624uni0625uni0626uni0627uni0628uni0629uni062Auni062Buni062Cuni062Duni062Euni062Funi0630uni0631uni0632uni0633uni0634uni0635uni0636uni0637uni0638uni0639uni063Auni0640uni0641uni0642uni0643uni0644uni0645uni0646uni0647uni0648uni0649uni064Auni064Buni064Cuni064Duni064Euni064Funi0650uni0651uni0652uni0653uni0654uni0655uni0657uni065Auni0660uni0661uni0662uni0663uni0664uni0665uni0666uni0667uni0668uni0669uni066Auni066Buni066Cuni066Duni066Euni066Funi0670uni0674uni0679uni067Auni067Buni067Cuni067Duni067Euni067Funi0680uni0681uni0682uni0683uni0684uni0685uni0686uni0687uni0688uni0689uni068Auni068Buni068Cuni068Duni068Euni068Funi0690uni0691uni0692uni0693uni0694uni0695uni0696uni0697uni0698uni0699uni069Auni069Buni069Cuni069Duni069Euni069Funi06A0uni06A1uni06A2uni06A3uni06A4uni06A5uni06A6uni06A7uni06A8uni06A9uni06AAuni06ABuni06ACuni06ADuni06AEuni06AFuni06B0uni06B1uni06B2uni06B3uni06B4uni06B5uni06B6uni06B7uni06B8uni06B9uni06BAuni06BBuni06BCuni06BDuni06BEuni06BFuni06C6uni06C7uni06C8uni06CBuni06CCuni06CEuni06D0uni06D5uni06F0uni06F1uni06F2uni06F3uni06F4uni06F5uni06F6uni06F7uni06F8uni06F9uni07C0uni07C1uni07C2uni07C3uni07C4uni07C5uni07C6uni07C7uni07C8uni07C9uni07CAuni07CBuni07CCuni07CDuni07CEuni07CFuni07D0uni07D1uni07D2uni07D3uni07D4uni07D5uni07D6uni07D7uni07D8uni07D9uni07DAuni07DBuni07DCuni07DDuni07DEuni07DFuni07E0uni07E1uni07E2uni07E3uni07E4uni07E5uni07E6uni07E7uni07EBuni07ECuni07EDuni07EEuni07EFuni07F0uni07F1uni07F2uni07F3uni07F4uni07F5uni07F8uni07F9uni07FAuni0E3Funi0E81uni0E82uni0E84uni0E87uni0E88uni0E8Auni0E8Duni0E94uni0E95uni0E96uni0E97uni0E99uni0E9Auni0E9Buni0E9Cuni0E9Duni0E9Euni0E9Funi0EA1uni0EA2uni0EA3uni0EA5uni0EA7uni0EAAuni0EABuni0EADuni0EAEuni0EAFuni0EB0uni0EB1uni0EB2uni0EB3uni0EB4uni0EB5uni0EB6uni0EB7uni0EB8uni0EB9uni0EBBuni0EBCuni0EBDuni0EC0uni0EC1uni0EC2uni0EC3uni0EC4uni0EC6uni0EC8uni0EC9uni0ECAuni0ECBuni0ECCuni0ECDuni0ED0uni0ED1uni0ED2uni0ED3uni0ED4uni0ED5uni0ED6uni0ED7uni0ED8uni0ED9uni0EDCuni0EDDuni10A0uni10A1uni10A2uni10A3uni10A4uni10A5uni10A6uni10A7uni10A8uni10A9uni10AAuni10ABuni10ACuni10ADuni10AEuni10AFuni10B0uni10B1uni10B2uni10B3uni10B4uni10B5uni10B6uni10B7uni10B8uni10B9uni10BAuni10BBuni10BCuni10BDuni10BEuni10BFuni10C0uni10C1uni10C2uni10C3uni10C4uni10C5uni10D0uni10D1uni10D2uni10D3uni10D4uni10D5uni10D6uni10D7uni10D8uni10D9uni10DAuni10DBuni10DCuni10DDuni10DEuni10DFuni10E0uni10E1uni10E2uni10E3uni10E4uni10E5uni10E6uni10E7uni10E8uni10E9uni10EAuni10EBuni10ECuni10EDuni10EEuni10EFuni10F0uni10F1uni10F2uni10F3uni10F4uni10F5uni10F6uni10F7uni10F8uni10F9uni10FAuni10FBuni10FCuni1401uni1402uni1403uni1404uni1405uni1406uni1407uni1409uni140Auni140Buni140Cuni140Duni140Euni140Funi1410uni1411uni1412uni1413uni1414uni1415uni1416uni1417uni1418uni1419uni141Auni141Buni141Duni141Euni141Funi1420uni1421uni1422uni1423uni1424uni1425uni1426uni1427uni1428uni1429uni142Auni142Buni142Cuni142Duni142Euni142Funi1430uni1431uni1432uni1433uni1434uni1435uni1437uni1438uni1439uni143Auni143Buni143Cuni143Duni143Euni143Funi1440uni1441uni1442uni1443uni1444uni1445uni1446uni1447uni1448uni1449uni144Auni144Cuni144Duni144Euni144Funi1450uni1451uni1452uni1454uni1455uni1456uni1457uni1458uni1459uni145Auni145Buni145Cuni145Duni145Euni145Funi1460uni1461uni1462uni1463uni1464uni1465uni1466uni1467uni1468uni1469uni146Auni146Buni146Cuni146Duni146Euni146Funi1470uni1471uni1472uni1473uni1474uni1475uni1476uni1477uni1478uni1479uni147Auni147Buni147Cuni147Duni147Euni147Funi1480uni1481uni1482uni1483uni1484uni1485uni1486uni1487uni1488uni1489uni148Auni148Buni148Cuni148Duni148Euni148Funi1490uni1491uni1492uni1493uni1494uni1495uni1496uni1497uni1498uni1499uni149Auni149Buni149Cuni149Duni149Euni149Funi14A0uni14A1uni14A2uni14A3uni14A4uni14A5uni14A6uni14A7uni14A8uni14A9uni14AAuni14ABuni14ACuni14ADuni14AEuni14AFuni14B0uni14B1uni14B2uni14B3uni14B4uni14B5uni14B6uni14B7uni14B8uni14B9uni14BAuni14BBuni14BCuni14BDuni14C0uni14C1uni14C2uni14C3uni14C4uni14C5uni14C6uni14C7uni14C8uni14C9uni14CAuni14CBuni14CCuni14CDuni14CEuni14CFuni14D0uni14D1uni14D2uni14D3uni14D4uni14D5uni14D6uni14D7uni14D8uni14D9uni14DAuni14DBuni14DCuni14DDuni14DEuni14DFuni14E0uni14E1uni14E2uni14E3uni14E4uni14E5uni14E6uni14E7uni14E8uni14E9uni14EAuni14ECuni14EDuni14EEuni14EFuni14F0uni14F1uni14F2uni14F3uni14F4uni14F5uni14F6uni14F7uni14F8uni14F9uni14FAuni14FBuni14FCuni14FDuni14FEuni14FFuni1500uni1501uni1502uni1503uni1504uni1505uni1506uni1507uni1510uni1511uni1512uni1513uni1514uni1515uni1516uni1517uni1518uni1519uni151Auni151Buni151Cuni151Duni151Euni151Funi1520uni1521uni1522uni1523uni1524uni1525uni1526uni1527uni1528uni1529uni152Auni152Buni152Cuni152Duni152Euni152Funi1530uni1531uni1532uni1533uni1534uni1535uni1536uni1537uni1538uni1539uni153Auni153Buni153Cuni153Duni153Euni1540uni1541uni1542uni1543uni1544uni1545uni1546uni1547uni1548uni1549uni154Auni154Buni154Cuni154Duni154Euni154Funi1550uni1552uni1553uni1554uni1555uni1556uni1557uni1558uni1559uni155Auni155Buni155Cuni155Duni155Euni155Funi1560uni1561uni1562uni1563uni1564uni1565uni1566uni1567uni1568uni1569uni156Auni1574uni1575uni1576uni1577uni1578uni1579uni157Auni157Buni157Cuni157Duni157Euni157Funi1580uni1581uni1582uni1583uni1584uni1585uni158Auni158Buni158Cuni158Duni158Euni158Funi1590uni1591uni1592uni1593uni1594uni1595uni1596uni15A0uni15A1uni15A2uni15A3uni15A4uni15A5uni15A6uni15A7uni15A8uni15A9uni15AAuni15ABuni15ACuni15ADuni15AEuni15AFuni15DEuni15E1uni1646uni1647uni166Euni166Funi1670uni1671uni1672uni1673uni1674uni1675uni1676uni1680uni1681uni1682uni1683uni1684uni1685uni1686uni1687uni1688uni1689uni168Auni168Buni168Cuni168Duni168Euni168Funi1690uni1691uni1692uni1693uni1694uni1695uni1696uni1697uni1698uni1699uni169Auni169Buni169Cuni1D00uni1D01uni1D02uni1D03uni1D04uni1D05uni1D06uni1D07uni1D08uni1D09uni1D0Auni1D0Buni1D0Cuni1D0Duni1D0Euni1D0Funi1D10uni1D11uni1D12uni1D13uni1D14uni1D16uni1D17uni1D18uni1D19uni1D1Auni1D1Buni1D1Cuni1D1Duni1D1Euni1D1Funi1D20uni1D21uni1D22uni1D23uni1D26uni1D27uni1D28uni1D29uni1D2Auni1D2Buni1D2Cuni1D2Duni1D2Euni1D30uni1D31uni1D32uni1D33uni1D34uni1D35uni1D36uni1D37uni1D38uni1D39uni1D3Auni1D3Buni1D3Cuni1D3Duni1D3Euni1D3Funi1D40uni1D41uni1D42uni1D43uni1D44uni1D45uni1D46uni1D47uni1D48uni1D49uni1D4Auni1D4Buni1D4Cuni1D4Duni1D4Euni1D4Funi1D50uni1D51uni1D52uni1D53uni1D54uni1D55uni1D56uni1D57uni1D58uni1D59uni1D5Auni1D5Buni1D5Duni1D5Euni1D5Funi1D60uni1D61uni1D62uni1D63uni1D64uni1D65uni1D66uni1D67uni1D68uni1D69uni1D6Auni1D77uni1D78uni1D7Buni1D7Duni1D85uni1D9Buni1D9Cuni1D9Duni1D9Euni1D9Funi1DA0uni1DA1uni1DA2uni1DA3uni1DA4uni1DA5uni1DA6uni1DA7uni1DA8uni1DA9uni1DAAuni1DABuni1DACuni1DADuni1DAEuni1DAFuni1DB0uni1DB1uni1DB2uni1DB3uni1DB4uni1DB5uni1DB6uni1DB7uni1DB8uni1DB9uni1DBAuni1DBBuni1DBCuni1DBDuni1DBEuni1DBFuni1DC4uni1DC5uni1DC6uni1DC7uni1DC8uni1DC9uni1E00uni1E01uni1E02uni1E03uni1E04uni1E05uni1E06uni1E07uni1E08uni1E09uni1E0Auni1E0Buni1E0Cuni1E0Duni1E0Euni1E0Funi1E10uni1E11uni1E12uni1E13uni1E14uni1E15uni1E16uni1E17uni1E18uni1E19uni1E1Auni1E1Buni1E1Cuni1E1Duni1E1Euni1E1Funi1E20uni1E21uni1E22uni1E23uni1E24uni1E25uni1E26uni1E27uni1E28uni1E29uni1E2Auni1E2Buni1E2Cuni1E2Duni1E2Euni1E2Funi1E30uni1E31uni1E32uni1E33uni1E34uni1E35uni1E36uni1E37uni1E38uni1E39uni1E3Auni1E3Buni1E3Cuni1E3Duni1E3Euni1E3Funi1E40uni1E41uni1E42uni1E43uni1E44uni1E45uni1E46uni1E47uni1E48uni1E49uni1E4Auni1E4Buni1E4Cuni1E4Duni1E4Euni1E4Funi1E50uni1E51uni1E52uni1E53uni1E54uni1E55uni1E56uni1E57uni1E58uni1E59uni1E5Auni1E5Buni1E5Cuni1E5Duni1E5Euni1E5Funi1E60uni1E61uni1E62uni1E63uni1E64uni1E65uni1E66uni1E67uni1E68uni1E69uni1E6Auni1E6Buni1E6Cuni1E6Duni1E6Euni1E6Funi1E70uni1E71uni1E72uni1E73uni1E74uni1E75uni1E76uni1E77uni1E78uni1E79uni1E7Auni1E7Buni1E7Cuni1E7Duni1E7Euni1E7FWgravewgraveWacutewacute Wdieresis wdieresisuni1E86uni1E87uni1E88uni1E89uni1E8Auni1E8Buni1E8Cuni1E8Duni1E8Euni1E8Funi1E90uni1E91uni1E92uni1E93uni1E94uni1E95uni1E96uni1E97uni1E98uni1E99uni1E9Auni1E9Buni1E9Cuni1E9Duni1E9Euni1E9Funi1EA0uni1EA1uni1EA2uni1EA3uni1EA4uni1EA5uni1EA6uni1EA7uni1EA8uni1EA9uni1EAAuni1EABuni1EACuni1EADuni1EAEuni1EAFuni1EB0uni1EB1uni1EB2uni1EB3uni1EB4uni1EB5uni1EB6uni1EB7uni1EB8uni1EB9uni1EBAuni1EBBuni1EBCuni1EBDuni1EBEuni1EBFuni1EC0uni1EC1uni1EC2uni1EC3uni1EC4uni1EC5uni1EC6uni1EC7uni1EC8uni1EC9uni1ECAuni1ECBuni1ECCuni1ECDuni1ECEuni1ECFuni1ED0uni1ED1uni1ED2uni1ED3uni1ED4uni1ED5uni1ED6uni1ED7uni1ED8uni1ED9uni1EDAuni1EDBuni1EDCuni1EDDuni1EDEuni1EDFuni1EE0uni1EE1uni1EE2uni1EE3uni1EE4uni1EE5uni1EE6uni1EE7uni1EE8uni1EE9uni1EEAuni1EEBuni1EECuni1EEDuni1EEEuni1EEFuni1EF0uni1EF1Ygraveygraveuni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9uni1EFAuni1EFBuni1F00uni1F01uni1F02uni1F03uni1F04uni1F05uni1F06uni1F07uni1F08uni1F09uni1F0Auni1F0Buni1F0Cuni1F0Duni1F0Euni1F0Funi1F10uni1F11uni1F12uni1F13uni1F14uni1F15uni1F18uni1F19uni1F1Auni1F1Buni1F1Cuni1F1Duni1F20uni1F21uni1F22uni1F23uni1F24uni1F25uni1F26uni1F27uni1F28uni1F29uni1F2Auni1F2Buni1F2Cuni1F2Duni1F2Euni1F2Funi1F30uni1F31uni1F32uni1F33uni1F34uni1F35uni1F36uni1F37uni1F38uni1F39uni1F3Auni1F3Buni1F3Cuni1F3Duni1F3Euni1F3Funi1F40uni1F41uni1F42uni1F43uni1F44uni1F45uni1F48uni1F49uni1F4Auni1F4Buni1F4Cuni1F4Duni1F50uni1F51uni1F52uni1F53uni1F54uni1F55uni1F56uni1F57uni1F59uni1F5Buni1F5Duni1F5Funi1F60uni1F61uni1F62uni1F63uni1F64uni1F65uni1F66uni1F67uni1F68uni1F69uni1F6Auni1F6Buni1F6Cuni1F6Duni1F6Euni1F6Funi1F70uni1F71uni1F72uni1F73uni1F74uni1F75uni1F76uni1F77uni1F78uni1F79uni1F7Auni1F7Buni1F7Cuni1F7Duni1F80uni1F81uni1F82uni1F83uni1F84uni1F85uni1F86uni1F87uni1F88uni1F89uni1F8Auni1F8Buni1F8Cuni1F8Duni1F8Euni1F8Funi1F90uni1F91uni1F92uni1F93uni1F94uni1F95uni1F96uni1F97uni1F98uni1F99uni1F9Auni1F9Buni1F9Cuni1F9Duni1F9Euni1F9Funi1FA0uni1FA1uni1FA2uni1FA3uni1FA4uni1FA5uni1FA6uni1FA7uni1FA8uni1FA9uni1FAAuni1FABuni1FACuni1FADuni1FAEuni1FAFuni1FB0uni1FB1uni1FB2uni1FB3uni1FB4uni1FB6uni1FB7uni1FB8uni1FB9uni1FBAuni1FBBuni1FBCuni1FBDuni1FBEuni1FBFuni1FC0uni1FC1uni1FC2uni1FC3uni1FC4uni1FC6uni1FC7uni1FC8uni1FC9uni1FCAuni1FCBuni1FCCuni1FCDuni1FCEuni1FCFuni1FD0uni1FD1uni1FD2uni1FD3uni1FD6uni1FD7uni1FD8uni1FD9uni1FDAuni1FDBuni1FDDuni1FDEuni1FDFuni1FE0uni1FE1uni1FE2uni1FE3uni1FE4uni1FE5uni1FE6uni1FE7uni1FE8uni1FE9uni1FEAuni1FEBuni1FECuni1FEDuni1FEEuni1FEFuni1FF2uni1FF3uni1FF4uni1FF6uni1FF7uni1FF8uni1FF9uni1FFAuni1FFBuni1FFCuni1FFDuni1FFEuni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200Buni200Cuni200Duni200Euni200Funi2010uni2011 figuredashuni2015uni2016 underscoredbl quotereverseduni201Funi2023onedotenleadertwodotenleaderuni2027uni2028uni2029uni202Auni202Buni202Cuni202Duni202Euni202Funi2031minuteseconduni2034uni2035uni2036uni2037uni2038uni203B exclamdbluni203Duni203Euni203Funi2040uni2041uni2042uni2043uni2045uni2046uni2047uni2048uni2049uni204Auni204Buni204Cuni204Duni204Euni204Funi2050uni2051uni2052uni2053uni2054uni2055uni2056uni2057uni2058uni2059uni205Auni205Buni205Cuni205Duni205Euni205Funi2060uni2061uni2062uni2063uni2064uni206Auni206Buni206Cuni206Duni206Euni206Funi2070uni2071uni2074uni2075uni2076uni2077uni2078uni2079uni207Auni207Buni207Cuni207Duni207Euni207Funi2080uni2081uni2082uni2083uni2084uni2085uni2086uni2087uni2088uni2089uni208Auni208Buni208Cuni208Duni208Euni2090uni2091uni2092uni2093uni2094uni2095uni2096uni2097uni2098uni2099uni209Auni209Buni209Cuni20A0 colonmonetaryuni20A2lirauni20A5uni20A6pesetauni20A8uni20A9uni20AAdongEurouni20ADuni20AEuni20AFuni20B0uni20B1uni20B2uni20B3uni20B4uni20B5uni20B8uni20B9uni20BAuni20BDuni20D0uni20D1uni20D6uni20D7uni20DBuni20DCuni20E1uni2100uni2101uni2102uni2103uni2104uni2105uni2106uni2107uni2108uni2109uni210Buni210Cuni210Duni210Euni210Funi2110Ifrakturuni2112uni2113uni2114uni2115uni2116uni2117 weierstrassuni2119uni211Auni211BRfrakturuni211D prescriptionuni211Funi2120uni2121uni2123uni2124uni2125uni2126uni2127uni2128uni2129uni212Auni212Buni212Cuni212D estimateduni212Funi2130uni2131uni2132uni2133uni2134alephuni2136uni2137uni2138uni2139uni213Auni213Buni213Cuni213Duni213Euni213Funi2140uni2141uni2142uni2143uni2144uni2145uni2146uni2147uni2148uni2149uni214Buni214Euni2150uni2151uni2152onethird twothirdsuni2155uni2156uni2157uni2158uni2159uni215A oneeighth threeeighths fiveeighths seveneighthsuni215Funi2160uni2161uni2162uni2163uni2164uni2165uni2166uni2167uni2168uni2169uni216Auni216Buni216Cuni216Duni216Euni216Funi2170uni2171uni2172uni2173uni2174uni2175uni2176uni2177uni2178uni2179uni217Auni217Buni217Cuni217Duni217Euni217Funi2180uni2181uni2182uni2183uni2184uni2185uni2189 arrowleftarrowup arrowright arrowdown arrowboth arrowupdnuni2196uni2197uni2198uni2199uni219Auni219Buni219Cuni219Duni219Euni219Funi21A0uni21A1uni21A2uni21A3uni21A4uni21A5uni21A6uni21A7 arrowupdnbseuni21A9uni21AAuni21ABuni21ACuni21ADuni21AEuni21AFuni21B0uni21B1uni21B2uni21B3uni21B4carriagereturnuni21B6uni21B7uni21B8uni21B9uni21BAuni21BBuni21BCuni21BDuni21BEuni21BFuni21C0uni21C1uni21C2uni21C3uni21C4uni21C5uni21C6uni21C7uni21C8uni21C9uni21CAuni21CBuni21CCuni21CDuni21CEuni21CF arrowdblleft arrowdblup arrowdblright arrowdbldown arrowdblbothuni21D5uni21D6uni21D7uni21D8uni21D9uni21DAuni21DBuni21DCuni21DDuni21DEuni21DFuni21E0uni21E1uni21E2uni21E3uni21E4uni21E5uni21E6uni21E7uni21E8uni21E9uni21EAuni21EBuni21ECuni21EDuni21EEuni21EFuni21F0uni21F1uni21F2uni21F3uni21F4uni21F5uni21F6uni21F7uni21F8uni21F9uni21FAuni21FBuni21FCuni21FDuni21FEuni21FF universaluni2201 existentialuni2204emptysetgradientelement notelementuni220Asuchthatuni220Cuni220Duni220Euni2210uni2213uni2214uni2215uni2216 asteriskmathuni2218uni2219uni221Buni221C proportional orthogonalangleuni2221uni2222uni2223uni2224uni2225uni2226 logicaland logicalor intersectionunionuni222Cuni222Duni222Euni222Funi2230uni2231uni2232uni2233 thereforeuni2235uni2236uni2237uni2238uni2239uni223Auni223Bsimilaruni223Duni223Euni223Funi2240uni2241uni2242uni2243uni2244 congruentuni2246uni2247uni2249uni224Auni224Buni224Cuni224Duni224Euni224Funi2250uni2251uni2252uni2253uni2254uni2255uni2256uni2257uni2258uni2259uni225Auni225Buni225Cuni225Duni225Euni225F equivalenceuni2262uni2263uni2266uni2267uni2268uni2269uni226Auni226Buni226Cuni226Duni226Euni226Funi2270uni2271uni2272uni2273uni2274uni2275uni2276uni2277uni2278uni2279uni227Auni227Buni227Cuni227Duni227Euni227Funi2280uni2281 propersubsetpropersuperset notsubsetuni2285 reflexsubsetreflexsupersetuni2288uni2289uni228Auni228Buni228Cuni228Duni228Euni228Funi2290uni2291uni2292uni2293uni2294 circleplusuni2296circlemultiplyuni2298uni2299uni229Auni229Buni229Cuni229Duni229Euni229Funi22A0uni22A1uni22A2uni22A3uni22A4 perpendicularuni22A6uni22A7uni22A8uni22A9uni22AAuni22ABuni22ACuni22ADuni22AEuni22AFuni22B0uni22B1uni22B2uni22B3uni22B4uni22B5uni22B6uni22B7uni22B8uni22B9uni22BAuni22BBuni22BCuni22BDuni22BEuni22BFuni22C0uni22C1uni22C2uni22C3uni22C4dotmathuni22C6uni22C7uni22C8uni22C9uni22CAuni22CBuni22CCuni22CDuni22CEuni22CFuni22D0uni22D1uni22D2uni22D3uni22D4uni22D5uni22D6uni22D7uni22D8uni22D9uni22DAuni22DBuni22DCuni22DDuni22DEuni22DFuni22E0uni22E1uni22E2uni22E3uni22E4uni22E5uni22E6uni22E7uni22E8uni22E9uni22EAuni22EBuni22ECuni22EDuni22EEuni22EFuni22F0uni22F1uni22F2uni22F3uni22F4uni22F5uni22F6uni22F7uni22F8uni22F9uni22FAuni22FBuni22FCuni22FDuni22FEuni22FFuni2300uni2301houseuni2303uni2304uni2305uni2306uni2307uni2308uni2309uni230Auni230Buni230Cuni230Duni230Euni230F revlogicalnotuni2311uni2318uni2319uni231Cuni231Duni231Euni231F integraltp integralbtuni2324uni2325uni2326uni2327uni2328uni232Buni232Cuni2373uni2374uni2375uni237Auni237Duni2387uni2394uni239Buni239Cuni239Duni239Euni239Funi23A0uni23A1uni23A2uni23A3uni23A4uni23A5uni23A6uni23A7uni23A8uni23A9uni23AAuni23ABuni23ACuni23ADuni23AEuni23CEuni23CFuni23E3uni23E5uni23E8uni2422uni2423uni2460uni2461uni2462uni2463uni2464uni2465uni2466uni2467uni2468uni2469SF100000uni2501SF110000uni2503uni2504uni2505uni2506uni2507uni2508uni2509uni250Auni250BSF010000uni250Duni250Euni250FSF030000uni2511uni2512uni2513SF020000uni2515uni2516uni2517SF040000uni2519uni251Auni251BSF080000uni251Duni251Euni251Funi2520uni2521uni2522uni2523SF090000uni2525uni2526uni2527uni2528uni2529uni252Auni252BSF060000uni252Duni252Euni252Funi2530uni2531uni2532uni2533SF070000uni2535uni2536uni2537uni2538uni2539uni253Auni253BSF050000uni253Duni253Euni253Funi2540uni2541uni2542uni2543uni2544uni2545uni2546uni2547uni2548uni2549uni254Auni254Buni254Cuni254Duni254Euni254FSF430000SF240000SF510000SF520000SF390000SF220000SF210000SF250000SF500000SF490000SF380000SF280000SF270000SF260000SF360000SF370000SF420000SF190000SF200000SF230000SF470000SF480000SF410000SF450000SF460000SF400000SF540000SF530000SF440000uni256Duni256Euni256Funi2570uni2571uni2572uni2573uni2574uni2575uni2576uni2577uni2578uni2579uni257Auni257Buni257Cuni257Duni257Euni257Fupblockuni2581uni2582uni2583dnblockuni2585uni2586uni2587blockuni2589uni258Auni258Blfblockuni258Duni258Euni258Frtblockltshadeshadedkshadeuni2594uni2595uni2596uni2597uni2598uni2599uni259Auni259Buni259Cuni259Duni259Euni259F filledboxH22073uni25A2uni25A3uni25A4uni25A5uni25A6uni25A7uni25A8uni25A9H18543H18551 filledrectuni25ADuni25AEuni25AFuni25B0uni25B1triagupuni25B3uni25B4uni25B5uni25B6uni25B7uni25B8uni25B9triagrtuni25BBtriagdnuni25BDuni25BEuni25BFuni25C0uni25C1uni25C2uni25C3triaglfuni25C5uni25C6uni25C7uni25C8uni25C9circleuni25CCuni25CDuni25CEH18533uni25D0uni25D1uni25D2uni25D3uni25D4uni25D5uni25D6uni25D7 invbullet invcircleuni25DAuni25DBuni25DCuni25DDuni25DEuni25DFuni25E0uni25E1uni25E2uni25E3uni25E4uni25E5 openbulletuni25E7uni25E8uni25E9uni25EAuni25EBuni25ECuni25EDuni25EEuni25EFuni25F0uni25F1uni25F2uni25F3uni25F4uni25F5uni25F6uni25F7uni25F8uni25F9uni25FAuni25FBuni25FCuni25FDuni25FEuni25FFuni2600uni2601uni2602uni2603uni2604uni2605uni2606uni2607uni2608uni2609uni260Auni260Buni260Cuni260Duni260Euni260Funi2610uni2611uni2612uni2613uni2614uni2615uni2616uni2617uni2618uni2619uni261Auni261Buni261Cuni261Duni261Euni261Funi2620uni2621uni2622uni2623uni2624uni2625uni2626uni2627uni2628uni2629uni262Auni262Buni262Cuni262Duni262Euni262Funi2630uni2631uni2632uni2633uni2634uni2635uni2636uni2637uni2638uni2639 smileface invsmilefacesununi263Duni263Euni263Ffemaleuni2641maleuni2643uni2644uni2645uni2646uni2647uni2648uni2649uni264Auni264Buni264Cuni264Duni264Euni264Funi2650uni2651uni2652uni2653uni2654uni2655uni2656uni2657uni2658uni2659uni265Auni265Buni265Cuni265Duni265Euni265Fspadeuni2661uni2662clubuni2664heartdiamonduni2667uni2668uni2669 musicalnotemusicalnotedbluni266Cuni266Duni266Euni266Funi2670uni2671uni2672uni2673uni2674uni2675uni2676uni2677uni2678uni2679uni267Auni267Buni267Cuni267Duni267Euni267Funi2680uni2681uni2682uni2683uni2684uni2685uni2686uni2687uni2688uni2689uni268Auni268Buni268Cuni268Duni268Euni268Funi2690uni2691uni2692uni2693uni2694uni2695uni2696uni2697uni2698uni2699uni269Auni269Buni269Cuni269Euni269Funi26A0uni26A1uni26A2uni26A3uni26A4uni26A5uni26A6uni26A7uni26A8uni26A9uni26AAuni26ABuni26ACuni26ADuni26AEuni26AFuni26B0uni26B1uni26B2uni26B3uni26B4uni26B5uni26B6uni26B7uni26B8uni26C0uni26C1uni26C2uni26C3uni26E2uni2701uni2702uni2703uni2704uni2706uni2707uni2708uni2709uni270Cuni270Duni270Euni270Funi2710uni2711uni2712uni2713uni2714uni2715uni2716uni2717uni2718uni2719uni271Auni271Buni271Cuni271Duni271Euni271Funi2720uni2721uni2722uni2723uni2724uni2725uni2726uni2727uni2729uni272Auni272Buni272Cuni272Duni272Euni272Funi2730uni2731uni2732uni2733uni2734uni2735uni2736uni2737uni2738uni2739uni273Auni273Buni273Cuni273Duni273Euni273Funi2740uni2741uni2742uni2743uni2744uni2745uni2746uni2747uni2748uni2749uni274Auni274Buni274Duni274Funi2750uni2751uni2752uni2756uni2758uni2759uni275Auni275Buni275Cuni275Duni275Euni2761uni2762uni2763uni2764uni2765uni2766uni2767uni2768uni2769uni276Auni276Buni276Cuni276Duni276Euni276Funi2770uni2771uni2772uni2773uni2774uni2775uni2776uni2777uni2778uni2779uni277Auni277Buni277Cuni277Duni277Euni277Funi2780uni2781uni2782uni2783uni2784uni2785uni2786uni2787uni2788uni2789uni278Auni278Buni278Cuni278Duni278Euni278Funi2790uni2791uni2792uni2793uni2794uni2798uni2799uni279Auni279Buni279Cuni279Duni279Euni279Funi27A0uni27A1uni27A2uni27A3uni27A4uni27A5uni27A6uni27A7uni27A8uni27A9uni27AAuni27ABuni27ACuni27ADuni27AEuni27AFuni27B1uni27B2uni27B3uni27B4uni27B5uni27B6uni27B7uni27B8uni27B9uni27BAuni27BBuni27BCuni27BDuni27BEuni27C5uni27C6uni27E0uni27E6uni27E7uni27E8uni27E9uni27EAuni27EBuni27F0uni27F1uni27F2uni27F3uni27F4uni27F5uni27F6uni27F7uni27F8uni27F9uni27FAuni27FBuni27FCuni27FDuni27FEuni27FFuni2800uni2801uni2802uni2803uni2804uni2805uni2806uni2807uni2808uni2809uni280Auni280Buni280Cuni280Duni280Euni280Funi2810uni2811uni2812uni2813uni2814uni2815uni2816uni2817uni2818uni2819uni281Auni281Buni281Cuni281Duni281Euni281Funi2820uni2821uni2822uni2823uni2824uni2825uni2826uni2827uni2828uni2829uni282Auni282Buni282Cuni282Duni282Euni282Funi2830uni2831uni2832uni2833uni2834uni2835uni2836uni2837uni2838uni2839uni283Auni283Buni283Cuni283Duni283Euni283Funi2840uni2841uni2842uni2843uni2844uni2845uni2846uni2847uni2848uni2849uni284Auni284Buni284Cuni284Duni284Euni284Funi2850uni2851uni2852uni2853uni2854uni2855uni2856uni2857uni2858uni2859uni285Auni285Buni285Cuni285Duni285Euni285Funi2860uni2861uni2862uni2863uni2864uni2865uni2866uni2867uni2868uni2869uni286Auni286Buni286Cuni286Duni286Euni286Funi2870uni2871uni2872uni2873uni2874uni2875uni2876uni2877uni2878uni2879uni287Auni287Buni287Cuni287Duni287Euni287Funi2880uni2881uni2882uni2883uni2884uni2885uni2886uni2887uni2888uni2889uni288Auni288Buni288Cuni288Duni288Euni288Funi2890uni2891uni2892uni2893uni2894uni2895uni2896uni2897uni2898uni2899uni289Auni289Buni289Cuni289Duni289Euni289Funi28A0uni28A1uni28A2uni28A3uni28A4uni28A5uni28A6uni28A7uni28A8uni28A9uni28AAuni28ABuni28ACuni28ADuni28AEuni28AFuni28B0uni28B1uni28B2uni28B3uni28B4uni28B5uni28B6uni28B7uni28B8uni28B9uni28BAuni28BBuni28BCuni28BDuni28BEuni28BFuni28C0uni28C1uni28C2uni28C3uni28C4uni28C5uni28C6uni28C7uni28C8uni28C9uni28CAuni28CBuni28CCuni28CDuni28CEuni28CFuni28D0uni28D1uni28D2uni28D3uni28D4uni28D5uni28D6uni28D7uni28D8uni28D9uni28DAuni28DBuni28DCuni28DDuni28DEuni28DFuni28E0uni28E1uni28E2uni28E3uni28E4uni28E5uni28E6uni28E7uni28E8uni28E9uni28EAuni28EBuni28ECuni28EDuni28EEuni28EFuni28F0uni28F1uni28F2uni28F3uni28F4uni28F5uni28F6uni28F7uni28F8uni28F9uni28FAuni28FBuni28FCuni28FDuni28FEuni28FFuni2906uni2907uni290Auni290Buni2940uni2941uni2983uni2984uni29CEuni29CFuni29D0uni29D1uni29D2uni29D3uni29D4uni29D5uni29EBuni29FAuni29FBuni2A00uni2A01uni2A02uni2A0Cuni2A0Duni2A0Euni2A0Funi2A10uni2A11uni2A12uni2A13uni2A14uni2A15uni2A16uni2A17uni2A18uni2A19uni2A1Auni2A1Buni2A1Cuni2A2Funi2A6Auni2A6Buni2A7Duni2A7Euni2A7Funi2A80uni2A81uni2A82uni2A83uni2A84uni2A85uni2A86uni2A87uni2A88uni2A89uni2A8Auni2A8Buni2A8Cuni2A8Duni2A8Euni2A8Funi2A90uni2A91uni2A92uni2A93uni2A94uni2A95uni2A96uni2A97uni2A98uni2A99uni2A9Auni2A9Buni2A9Cuni2A9Duni2A9Euni2A9Funi2AA0uni2AAEuni2AAFuni2AB0uni2AB1uni2AB2uni2AB3uni2AB4uni2AB5uni2AB6uni2AB7uni2AB8uni2AB9uni2ABAuni2AF9uni2AFAuni2B00uni2B01uni2B02uni2B03uni2B04uni2B05uni2B06uni2B07uni2B08uni2B09uni2B0Auni2B0Buni2B0Cuni2B0Duni2B0Euni2B0Funi2B10uni2B11uni2B12uni2B13uni2B14uni2B15uni2B16uni2B17uni2B18uni2B19uni2B1Auni2B1Funi2B20uni2B21uni2B22uni2B23uni2B24uni2B53uni2B54uni2C60uni2C61uni2C62uni2C63uni2C64uni2C65uni2C66uni2C67uni2C68uni2C69uni2C6Auni2C6Buni2C6Cuni2C6Duni2C6Euni2C6Funi2C70uni2C71uni2C72uni2C73uni2C74uni2C75uni2C76uni2C77uni2C79uni2C7Auni2C7Buni2C7Cuni2C7Duni2C7Euni2C7Funi2D00uni2D01uni2D02uni2D03uni2D04uni2D05uni2D06uni2D07uni2D08uni2D09uni2D0Auni2D0Buni2D0Cuni2D0Duni2D0Euni2D0Funi2D10uni2D11uni2D12uni2D13uni2D14uni2D15uni2D16uni2D17uni2D18uni2D19uni2D1Auni2D1Buni2D1Cuni2D1Duni2D1Euni2D1Funi2D20uni2D21uni2D22uni2D23uni2D24uni2D25uni2D30uni2D31uni2D32uni2D33uni2D34uni2D35uni2D36uni2D37uni2D38uni2D39uni2D3Auni2D3Buni2D3Cuni2D3Duni2D3Euni2D3Funi2D40uni2D41uni2D42uni2D43uni2D44uni2D45uni2D46uni2D47uni2D48uni2D49uni2D4Auni2D4Buni2D4Cuni2D4Duni2D4Euni2D4Funi2D50uni2D51uni2D52uni2D53uni2D54uni2D55uni2D56uni2D57uni2D58uni2D59uni2D5Auni2D5Buni2D5Cuni2D5Duni2D5Euni2D5Funi2D60uni2D61uni2D62uni2D63uni2D64uni2D65uni2D6Funi2E18uni2E1Funi2E22uni2E23uni2E24uni2E25uni2E2Euni4DC0uni4DC1uni4DC2uni4DC3uni4DC4uni4DC5uni4DC6uni4DC7uni4DC8uni4DC9uni4DCAuni4DCBuni4DCCuni4DCDuni4DCEuni4DCFuni4DD0uni4DD1uni4DD2uni4DD3uni4DD4uni4DD5uni4DD6uni4DD7uni4DD8uni4DD9uni4DDAuni4DDBuni4DDCuni4DDDuni4DDEuni4DDFuni4DE0uni4DE1uni4DE2uni4DE3uni4DE4uni4DE5uni4DE6uni4DE7uni4DE8uni4DE9uni4DEAuni4DEBuni4DECuni4DEDuni4DEEuni4DEFuni4DF0uni4DF1uni4DF2uni4DF3uni4DF4uni4DF5uni4DF6uni4DF7uni4DF8uni4DF9uni4DFAuni4DFBuni4DFCuni4DFDuni4DFEuni4DFFuniA4D0uniA4D1uniA4D2uniA4D3uniA4D4uniA4D5uniA4D6uniA4D7uniA4D8uniA4D9uniA4DAuniA4DBuniA4DCuniA4DDuniA4DEuniA4DFuniA4E0uniA4E1uniA4E2uniA4E3uniA4E4uniA4E5uniA4E6uniA4E7uniA4E8uniA4E9uniA4EAuniA4EBuniA4ECuniA4EDuniA4EEuniA4EFuniA4F0uniA4F1uniA4F2uniA4F3uniA4F4uniA4F5uniA4F6uniA4F7uniA4F8uniA4F9uniA4FAuniA4FBuniA4FCuniA4FDuniA4FEuniA4FFuniA644uniA645uniA646uniA647uniA64CuniA64DuniA650uniA651uniA654uniA655uniA656uniA657uniA662uniA663uniA664uniA665uniA666uniA667uniA668uniA669uniA66AuniA66BuniA66CuniA66DuniA66EuniA68AuniA68BuniA68CuniA68DuniA694uniA695uniA698uniA699uniA708uniA709uniA70AuniA70BuniA70CuniA70DuniA70EuniA70FuniA710uniA711uniA712uniA713uniA714uniA715uniA716uniA71BuniA71CuniA71DuniA71EuniA71FuniA722uniA723uniA724uniA725uniA726uniA727uniA728uniA729uniA72AuniA72BuniA730uniA731uniA732uniA733uniA734uniA735uniA736uniA737uniA738uniA739uniA73AuniA73BuniA73CuniA73DuniA73EuniA73FuniA740uniA741uniA746uniA747uniA748uniA749uniA74AuniA74BuniA74EuniA74FuniA750uniA751uniA752uniA753uniA756uniA757uniA764uniA765uniA766uniA767uniA780uniA781uniA782uniA783uniA789uniA78AuniA78BuniA78CuniA78DuniA78EuniA790uniA791uniA7A0uniA7A1uniA7A2uniA7A3uniA7A4uniA7A5uniA7A6uniA7A7uniA7A8uniA7A9uniA7AAuniA7F8uniA7F9uniA7FAuniA7FBuniA7FCuniA7FDuniA7FEuniA7FF uni02E5.5 uni02E6.5 uni02E7.5 uni02E8.5 uni02E9.5 uni02E5.4 uni02E6.4 uni02E7.4 uni02E8.4 uni02E9.4 uni02E5.3 uni02E6.3 uni02E7.3 uni02E8.3 uni02E9.3 uni02E5.2 uni02E6.2 uni02E7.2 uni02E8.2 uni02E9.2 uni02E5.1 uni02E6.1 uni02E7.1 uni02E8.1 uni02E9.1stemuniF000uniF001uniF002uniF003uniF400uniF401uniF402uniF403uniF404uniF405uniF406uniF407uniF408uniF409uniF40AuniF40BuniF40CuniF40DuniF40EuniF40FuniF410uniF411uniF412uniF413uniF414uniF415uniF416uniF417uniF418uniF419uniF41AuniF41BuniF41CuniF41DuniF41EuniF41FuniF420uniF421uniF422uniF423uniF424uniF425uniF426uniF428uniF429uniF42AuniF42BuniF42CuniF42DuniF42EuniF42FuniF430uniF431uniF432uniF433uniF434uniF435uniF436uniF437uniF438uniF439uniF43AuniF43BuniF43CuniF43DuniF43EuniF43FuniF440uniF441uniF6C5uniFB00uniFB03uniFB04uniFB05uniFB06uniFB13uniFB14uniFB15uniFB16uniFB17uniFB1DuniFB1EuniFB1FuniFB20uniFB21uniFB22uniFB23uniFB24uniFB25uniFB26uniFB27uniFB28uniFB29uniFB2AuniFB2BuniFB2CuniFB2DuniFB2EuniFB2FuniFB30uniFB31uniFB32uniFB33uniFB34uniFB35uniFB36uniFB38uniFB39uniFB3AuniFB3BuniFB3CuniFB3EuniFB40uniFB41uniFB43uniFB44uniFB46uniFB47uniFB48uniFB49uniFB4AuniFB4BuniFB4CuniFB4DuniFB4EuniFB4FuniFB52uniFB53uniFB54uniFB55uniFB56uniFB57uniFB58uniFB59uniFB5AuniFB5BuniFB5CuniFB5DuniFB5EuniFB5FuniFB60uniFB61uniFB62uniFB63uniFB64uniFB65uniFB66uniFB67uniFB68uniFB69uniFB6AuniFB6BuniFB6CuniFB6DuniFB6EuniFB6FuniFB70uniFB71uniFB72uniFB73uniFB74uniFB75uniFB76uniFB77uniFB78uniFB79uniFB7AuniFB7BuniFB7CuniFB7DuniFB7EuniFB7FuniFB80uniFB81uniFB82uniFB83uniFB84uniFB85uniFB86uniFB87uniFB88uniFB89uniFB8AuniFB8BuniFB8CuniFB8DuniFB8EuniFB8FuniFB90uniFB91uniFB92uniFB93uniFB94uniFB95uniFB96uniFB97uniFB98uniFB99uniFB9AuniFB9BuniFB9CuniFB9DuniFB9EuniFB9FuniFBA0uniFBA1uniFBA2uniFBA3uniFBAAuniFBABuniFBACuniFBADuniFBD3uniFBD4uniFBD5uniFBD6uniFBD7uniFBD8uniFBD9uniFBDAuniFBDBuniFBDCuniFBDEuniFBDFuniFBE4uniFBE5uniFBE6uniFBE7uniFBE8uniFBE9uniFBFCuniFBFDuniFBFEuniFBFFuniFE00uniFE01uniFE02uniFE03uniFE04uniFE05uniFE06uniFE07uniFE08uniFE09uniFE0AuniFE0BuniFE0CuniFE0DuniFE0EuniFE0FuniFE20uniFE21uniFE22uniFE23uniFE70uniFE71uniFE72uniFE73uniFE74uniFE76uniFE77uniFE78uniFE79uniFE7AuniFE7BuniFE7CuniFE7DuniFE7EuniFE7FuniFE80uniFE81uniFE82uniFE83uniFE84uniFE85uniFE86uniFE87uniFE88uniFE89uniFE8AuniFE8BuniFE8CuniFE8DuniFE8EuniFE8FuniFE90uniFE91uniFE92uniFE93uniFE94uniFE95uniFE96uniFE97uniFE98uniFE99uniFE9AuniFE9BuniFE9CuniFE9DuniFE9EuniFE9FuniFEA0uniFEA1uniFEA2uniFEA3uniFEA4uniFEA5uniFEA6uniFEA7uniFEA8uniFEA9uniFEAAuniFEABuniFEACuniFEADuniFEAEuniFEAFuniFEB0uniFEB1uniFEB2uniFEB3uniFEB4uniFEB5uniFEB6uniFEB7uniFEB8uniFEB9uniFEBAuniFEBBuniFEBCuniFEBDuniFEBEuniFEBFuniFEC0uniFEC1uniFEC2uniFEC3uniFEC4uniFEC5uniFEC6uniFEC7uniFEC8uniFEC9uniFECAuniFECBuniFECCuniFECDuniFECEuniFECFuniFED0uniFED1uniFED2uniFED3uniFED4uniFED5uniFED6uniFED7uniFED8uniFED9uniFEDAuniFEDBuniFEDCuniFEDDuniFEDEuniFEDFuniFEE0uniFEE1uniFEE2uniFEE3uniFEE4uniFEE5uniFEE6uniFEE7uniFEE8uniFEE9uniFEEAuniFEEBuniFEECuniFEEDuniFEEEuniFEEFuniFEF0uniFEF1uniFEF2uniFEF3uniFEF4uniFEF5uniFEF6uniFEF7uniFEF8uniFEF9uniFEFAuniFEFBuniFEFCuniFEFFuniFFF9uniFFFAuniFFFBuniFFFCuniFFFDu10300u10301u10302u10303u10304u10305u10306u10307u10308u10309u1030Au1030Bu1030Cu1030Du1030Eu1030Fu10310u10311u10312u10313u10314u10315u10316u10317u10318u10319u1031Au1031Bu1031Cu1031Du1031Eu10320u10321u10322u10323u1D300u1D301u1D302u1D303u1D304u1D305u1D306u1D307u1D308u1D309u1D30Au1D30Bu1D30Cu1D30Du1D30Eu1D30Fu1D310u1D311u1D312u1D313u1D314u1D315u1D316u1D317u1D318u1D319u1D31Au1D31Bu1D31Cu1D31Du1D31Eu1D31Fu1D320u1D321u1D322u1D323u1D324u1D325u1D326u1D327u1D328u1D329u1D32Au1D32Bu1D32Cu1D32Du1D32Eu1D32Fu1D330u1D331u1D332u1D333u1D334u1D335u1D336u1D337u1D338u1D339u1D33Au1D33Bu1D33Cu1D33Du1D33Eu1D33Fu1D340u1D341u1D342u1D343u1D344u1D345u1D346u1D347u1D348u1D349u1D34Au1D34Bu1D34Cu1D34Du1D34Eu1D34Fu1D350u1D351u1D352u1D353u1D354u1D355u1D356u1D538u1D539u1D53Bu1D53Cu1D53Du1D53Eu1D540u1D541u1D542u1D543u1D544u1D546u1D54Au1D54Bu1D54Cu1D54Du1D54Eu1D54Fu1D550u1D552u1D553u1D554u1D555u1D556u1D557u1D558u1D559u1D55Au1D55Bu1D55Cu1D55Du1D55Eu1D55Fu1D560u1D561u1D562u1D563u1D564u1D565u1D566u1D567u1D568u1D569u1D56Au1D56Bu1D5A0u1D5A1u1D5A2u1D5A3u1D5A4u1D5A5u1D5A6u1D5A7u1D5A8u1D5A9u1D5AAu1D5ABu1D5ACu1D5ADu1D5AEu1D5AFu1D5B0u1D5B1u1D5B2u1D5B3u1D5B4u1D5B5u1D5B6u1D5B7u1D5B8u1D5B9u1D5BAu1D5BBu1D5BCu1D5BDu1D5BEu1D5BFu1D5C0u1D5C1u1D5C2u1D5C3u1D5C4u1D5C5u1D5C6u1D5C7u1D5C8u1D5C9u1D5CAu1D5CBu1D5CCu1D5CDu1D5CEu1D5CFu1D5D0u1D5D1u1D5D2u1D5D3u1D7D8u1D7D9u1D7DAu1D7DBu1D7DCu1D7DDu1D7DEu1D7DFu1D7E0u1D7E1u1D7E2u1D7E3u1D7E4u1D7E5u1D7E6u1D7E7u1D7E8u1D7E9u1D7EAu1D7EBu1EE00u1EE01u1EE02u1EE03u1EE05u1EE06u1EE07u1EE08u1EE09u1EE0Au1EE0Bu1EE0Cu1EE0Du1EE0Eu1EE0Fu1EE10u1EE11u1EE12u1EE13u1EE14u1EE15u1EE16u1EE17u1EE18u1EE19u1EE1Au1EE1Bu1EE1Cu1EE1Du1EE1Eu1EE1Fu1EE21u1EE22u1EE24u1EE27u1EE29u1EE2Au1EE2Bu1EE2Cu1EE2Du1EE2Eu1EE2Fu1EE30u1EE31u1EE32u1EE34u1EE35u1EE36u1EE37u1EE39u1EE3Bu1EE61u1EE62u1EE64u1EE67u1EE68u1EE69u1EE6Au1EE6Cu1EE6Du1EE6Eu1EE6Fu1EE70u1EE71u1EE72u1EE74u1EE75u1EE76u1EE77u1EE79u1EE7Au1EE7Bu1EE7Cu1EE7Eu1F030u1F031u1F032u1F033u1F034u1F035u1F036u1F037u1F038u1F039u1F03Au1F03Bu1F03Cu1F03Du1F03Eu1F03Fu1F040u1F041u1F042u1F043u1F044u1F045u1F046u1F047u1F048u1F049u1F04Au1F04Bu1F04Cu1F04Du1F04Eu1F04Fu1F050u1F051u1F052u1F053u1F054u1F055u1F056u1F057u1F058u1F059u1F05Au1F05Bu1F05Cu1F05Du1F05Eu1F05Fu1F060u1F061u1F062u1F063u1F064u1F065u1F066u1F067u1F068u1F069u1F06Au1F06Bu1F06Cu1F06Du1F06Eu1F06Fu1F070u1F071u1F072u1F073u1F074u1F075u1F076u1F077u1F078u1F079u1F07Au1F07Bu1F07Cu1F07Du1F07Eu1F07Fu1F080u1F081u1F082u1F083u1F084u1F085u1F086u1F087u1F088u1F089u1F08Au1F08Bu1F08Cu1F08Du1F08Eu1F08Fu1F090u1F091u1F092u1F093u1F0A0u1F0A1u1F0A2u1F0A3u1F0A4u1F0A5u1F0A6u1F0A7u1F0A8u1F0A9u1F0AAu1F0ABu1F0ACu1F0ADu1F0AEu1F0B1u1F0B2u1F0B3u1F0B4u1F0B5u1F0B6u1F0B7u1F0B8u1F0B9u1F0BAu1F0BBu1F0BCu1F0BDu1F0BEu1F0C1u1F0C2u1F0C3u1F0C4u1F0C5u1F0C6u1F0C7u1F0C8u1F0C9u1F0CAu1F0CBu1F0CCu1F0CDu1F0CEu1F0CFu1F0D1u1F0D2u1F0D3u1F0D4u1F0D5u1F0D6u1F0D7u1F0D8u1F0D9u1F0DAu1F0DBu1F0DCu1F0DDu1F0DEu1F0DFu1F311u1F312u1F313u1F314u1F315u1F316u1F317u1F318u1F42Du1F42Eu1F431u1F435u1F600u1F601u1F602u1F603u1F604u1F605u1F606u1F607u1F608u1F609u1F60Au1F60Bu1F60Cu1F60Du1F60Eu1F60Fu1F610u1F611u1F612u1F613u1F614u1F615u1F616u1F617u1F618u1F619u1F61Au1F61Bu1F61Cu1F61Du1F61Eu1F61Fu1F620u1F621u1F622u1F623u1F625u1F626u1F627u1F628u1F629u1F62Au1F62Bu1F62Du1F62Eu1F62Fu1F630u1F631u1F632u1F633u1F634u1F635u1F636u1F637u1F638u1F639u1F63Au1F63Bu1F63Cu1F63Du1F63Eu1F63Fu1F640u1F643 dlLtcaronDieresisAcuteTildeGrave CircumflexCaron uni0311.caseBreve Dotaccent Hungarumlaut Doublegrave arabic_dot arabic_2dots arabic_3dotsarabic_3dots_aarabic_2dots_a arabic_4dots uni066E.fina uni066E.init uni066E.medi uni06A1.fina uni06A1.init uni06A1.medi uni066F.fina uni066F.init uni066F.medi uni06BA.init uni06BA.medi arabic_ring uni067C.fina uni067C.init uni067C.medi uni067D.fina uni067D.init uni067D.medi uni0681.fina uni0681.init uni0681.medi uni0682.fina uni0682.init uni0682.medi uni0685.fina uni0685.init uni0685.medi uni06BF.fina uni06BF.init uni06BF.mediarabic_gaf_barEng.altuni0268.dotlessuni029D.dotless uni03080304 uni03040308 uni03070304 uni03080301 uni03080300 uni03040301 uni03040300 uni03030304 uni0308030C uni03030308 uni030C0307 uni03030301 uni03020301 uni03020300 uni03020303 uni03060303 uni03060301 uni03060300 uni03060309 uni03020309 uni03010307 brailledotJ.alt uni0695.finauniFEAE.fina.longstart uni06B5.fina uni06B5.init uni06B5.medi uni06CE.fina uni06CE.init uni06CE.medi uni0692.final.alt uni06D5.finauni0478.monographuni0479.monographiogonek.dotlessuni2148.dotlessuni2149.dotlessuni1E2D.dotlessuni1ECB.dotlessdcoI.alt arrow.base uni0651064B uni0651064C uni064B0651 uni0651064E uni0651064F uni064E0651 uni0654064E uni0654064F uni07CA.fina uni07CA.medi uni07CA.init uni07CB.fina uni07CB.medi uni07CB.init uni07CC.fina uni07CC.medi uni07CC.init uni07CD.fina uni07CD.medi uni07CD.init uni07CE.fina uni07CE.medi uni07CE.init uni07CF.fina uni07CF.medi uni07CF.init uni07D0.fina uni07D0.medi uni07D0.init uni07D1.fina uni07D1.medi uni07D1.init uni07D2.fina uni07D2.medi uni07D2.init uni07D3.fina uni07D3.medi uni07D3.init uni07D4.fina uni07D4.medi uni07D4.init uni07D5.fina uni07D5.medi uni07D5.init uni07D6.fina uni07D6.medi uni07D6.init uni07D7.fina uni07D7.medi uni07D7.init uni07D8.fina uni07D8.medi uni07D8.init uni07D9.fina uni07D9.medi uni07D9.init uni07DA.fina uni07DA.medi uni07DA.init uni07DB.fina uni07DB.medi uni07DB.init uni07DC.fina uni07DC.medi uni07DC.init uni07DD.fina uni07DD.medi uni07DD.init uni07DE.fina uni07DE.medi uni07DE.init uni07DF.fina uni07DF.medi uni07DF.init uni07E0.fina uni07E0.medi uni07E0.init uni07E1.fina uni07E1.medi uni07E1.init uni07E2.fina uni07E2.medi uni07E2.init uni07E3.fina uni07E3.medi uni07E3.init uni07E4.fina uni07E4.medi uni07E4.init uni07E5.fina uni07E5.medi uni07E5.init uni07E6.fina uni07E6.medi uni07E6.init uni07E7.fina uni07E7.medi uni07E7.init Ringabove uni2630.alt uni2631.alt uni2632.alt uni2633.alt uni2634.alt uni2635.alt uni2636.alt uni2637.alt uni047E.diacuni048A.brevelessuni048B.brevelessy.alt uni0689.fina uni068A.fina uni068B.fina uni068F.fina uni0690.fina uni0693.fina uni0694.fina uni0696.fina uni0697.fina uni0699.fina uni069A.fina uni069A.init uni069A.medi uni069B.fina uni069B.init uni069B.medi uni069C.fina uni069C.init uni069C.medi uni069D.fina uni069D.init uni069D.medi uni069E.fina uni069E.init uni069E.medi uni069F.fina uni069F.init uni069F.medi uni06A0.fina uni06A0.init uni06A0.medi uni06A2.fina uni06A2.init uni06A2.medi uni06A3.fina uni06A3.init uni06A3.medi uni06A5.fina uni06A5.init uni06A5.medi uni06A7.fina uni06A7.init uni06A7.medi uni06A8.fina uni06A8.init uni06A8.medi uni06AA.fina uni06AA.init uni06AA.medi uni06AB.fina uni06AB.init uni06AB.medi uni06AC.fina uni06AC.init uni06AC.medi uni06AE.fina uni06AE.init uni06AE.medi uni06B0.fina uni06B0.init uni06B0.medi uni06B2.fina uni06B2.init uni06B2.medi uni06B4.fina uni06B4.init uni06B4.medi uni06B6.fina uni06B6.init uni06B6.medi uni06B7.fina uni06B7.init uni06B7.medi uni06B8.fina uni06B8.init uni06B8.medi uni06B9.fina uni06B9.init uni06B9.medi uni06BC.fina uni06BC.init uni06BC.medi uni06BD.fina uni06BD.init uni06BD.mediexclamdown.casequestiondown.case uni2E18.caseuni066E.init.mathproduct.displayuni2210.displaysummation.displayintegral.displayuni222C.displayuni222D.displayuni222E.displayuni222F.displayuni2230.displayuni2231.displayuni2232.displayuni2233.displayuni22C0.displayuni22C1.displayuni22C2.displayuni22C3.displayuni2A00.displayuni2A01.displayuni2A02.displayuni2A0C.displayuni2A0D.displayuni2A0E.displayuni2A0F.displayuni2A10.displayuni2A11.displayuni2A12.displayuni2A13.displayuni2A14.displayuni2A15.displayuni2A16.displayuni2A17.displayuni2A18.displayuni2A19.displayuni2A1A.displayuni2A1B.displayuni2A1C.display@%2%%A:B2SAS//2ݖ}ٻ֊A}G}G͖2ƅ%]%]@@%d%d%A2dA  d   A(]%]@%..%A  %d%@~}}~}}|d{T{%zyxw v utsrqponl!kjBjSih}gBfedcba:`^ ][ZYX YX WW2VUTUBTSSRQJQP ONMNMLKJKJIJI IH GFEDC-CBAK@?>=>=<=<; <@; :987876765 65 43 21 21 0/ 0 / .- .- ,2+*%+d*)*%)('%(A'%&% &% $#"!! d d BBBdB-B}d       -d@--d++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++qTox/res/font/LICENSE000066400000000000000000000211601415623743500145670ustar00rootroot00000000000000Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) Bitstream Vera Fonts Copyright ------------------------------ Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. Arev Fonts Copyright ------------------------------ Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. TeX Gyre DJV Math ----------------- Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski (on behalf of TeX users groups) are in public domain. Letters imported from Euler Fraktur from AMSfonts are (c) American Mathematical Society (see below). Bitstream Vera Fonts Copyright Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (“Fonts”) and associated documentation files (the “Font Software”), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words “Bitstream” or the word “Vera”. This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the “Bitstream Vera” names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of GNOME, the GNOME Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the GNOME Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. AMSFonts (v. 2.2) copyright The PostScript Type 1 implementation of the AMSFonts produced by and previously distributed by Blue Sky Research and Y&Y, Inc. are now freely available for general use. This has been accomplished through the cooperation of a consortium of scientific publishers with Blue Sky Research and Y&Y. Members of this consortium include: Elsevier Science IBM Corporation Society for Industrial and Applied Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) In order to assure the authenticity of these fonts, copyright will be held by the American Mathematical Society. This is not meant to restrict in any way the legitimate use of the fonts, such as (but not limited to) electronic distribution of documents containing these fonts, inclusion of these fonts into other public domain or commercial font collections or computer applications, use of the outline data to create derivative fonts and/or faces, etc. However, the AMS does require that the AMS copyright notice be removed from any derivative versions of the fonts which have been altered in any way. In addition, to ensure the fidelity of TeX documents using Computer Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, has requested that any alterations which yield different font metrics be given a different name. $Id$ qTox/res/io.github.qtox.qTox.appdata.xml000066400000000000000000000057431415623743500205740ustar00rootroot00000000000000 io.github.qtox.qTox io.github.qtox.qTox.desktop CC-BY-SA-3.0 GPL-3.0 qTox Powerful Tox chat client that follows the Tox design guidelines.

A New Kind of Instant Messaging

With the rise of government monitoring programs, qTox provides an easy to use application that allows you to connect with friends and family without anyone else listening in. While other big-name services require you to pay for features, qTox is totally free, and comes without advertising.

Nowadays, every government seems to be interested in what we're saying online. qTox is built on a "privacy goes first" agenda, and we make no compromises. Your safety is our top priority, and there isn't anything in the world that will change that.

Instant messaging, video conferencing, and more.

  • Messages: At your fingertips. You're always in the loop with instant encrypted messaging.
  • Calls: Stay in touch. Make free and secure qTox to qTox calls.
  • Video: Seeing is believing. Catch up face to face with a secure video call.

qTox is a free and open source software, built by and for the users!

  • Security: qTox takes your privacy seriously. With leading-class encryption, you can rest assured knowing that the only people reading your messages are the ones you send them to.
  • Ease of Use: Unlike other secure messaging solutions, qTox does not require you to be a computer programmer to use it. qTox comes out-of-the-box with an easy-to-use interface that allows you to focus on your conversations.
  • Freedom: qTox is both free for you to use, and free for you to change. You are completely free to both use and modify qTox. Furthermore, qTox will never harass you with ads, or require you to pay for features.
https://i.imgur.com/olb89CN.png A sample conversation taking place on qTox https://i.imgur.com/tmX8z9s.png A sample groupchat discussion taking place on qTox https://qtox.github.io barrdetwix@gmail.com qTox intense intense
qTox/res/nodes.json000066400000000000000000000260351415623743500146250ustar00rootroot00000000000000{"last_scan":1606049991,"last_refresh":1606049995,"nodes":[{"ipv4":"85.172.30.117","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832","maintainer":"ray65536","location":"RU","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"Ray's Tox Node. TOX ID:3C3D6DB24D24754393679E59F198EF45EE26835AEF7EA3E3ECEA40E204F2B828BE86DF012ABF","last_ping":1606049993},{"ipv4":"85.143.221.42","ipv6":"2a04:ac00:1:9f00:5054:ff:fe01:becd","port":33445,"tcp_ports":[3389,33445],"public_key":"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43","maintainer":"MAH69K","location":"RU","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"Saluton! Mia Tox ID: B229B7BD68FC66C2716EAB8671A461906321C764782D7B3EDBB650A315F6C458EF744CE89F07. Scribu! ;)","last_ping":1606049992},{"ipv4":"tox.verdict.gg","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976","maintainer":"Deliran","location":"DE","status_udp":true,"status_tcp":true,"version":"1000002008","motd":"Praise The Sun!","last_ping":1606049992},{"ipv4":"78.46.73.141","ipv6":"2a01:4f8:120:4091::3","port":33445,"tcp_ports":[33445,3389],"public_key":"02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46","maintainer":"Sorunome","location":"DE","status_udp":true,"status_tcp":true,"version":"1000002008","motd":"Keep calm and pony on!","last_ping":1606049992},{"ipv4":"tox.initramfs.io","ipv6":"tox.initramfs.io","port":33445,"tcp_ports":[3389,33445],"public_key":"3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25","maintainer":"initramfs","location":"TW","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"initramfs' Tox DHT Node","last_ping":1606049992},{"ipv4":"46.229.52.198","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"813C8F4187833EF0655B10F7752141A352248462A567529A38B6BBF73E979307","maintainer":"Stranger","location":"UA","status_udp":true,"status_tcp":false,"version":"1000002008","motd":"Freedom to parrots!","last_ping":1606049992},{"ipv4":"144.217.167.73","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C","maintainer":"velusip","location":"CA","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"Jera","last_ping":1606049991},{"ipv4":"tox.abilinski.com","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"10C00EB250C3233E343E2AEBA07115A5C28920E9C8D29492F6D00B29049EDC7E","maintainer":"AnthonyBilinski","location":"CA","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"Running https://github.com/toktok/c-toxcore v0.2.12. qTox best Tox! Contact: AC18841E56CCDEE16E93E10E6AB2765BE54277D67F1372921B5B418A6B330D3D3FAFA60B0931","last_ping":1606049991},{"ipv4":"tox.novg.net","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"D527E5847F8330D628DAB1814F0A422F6DC9D0A300E6C357634EE2DA88C35463","maintainer":"blind_oracle","location":"NL","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"tox-bootstrapd","last_ping":1606049993},{"ipv4":"95.31.18.227","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"257744DBF57BE3E117FE05D145B5F806089428D4DCE4E3D0D50616AA16D9417E","maintainer":"ky0uraku","location":"RU","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"Vive le TOX","last_ping":1606049993},{"ipv4":"198.199.98.108","ipv6":"2604:a880:1:20::32f:1001","port":33445,"tcp_ports":[3389,33445],"public_key":"BEF0CFB37AF874BD17B9A8F9FE64C75521DB95A37D33C5BDB00E9CF58659C04F","maintainer":"Cody","location":"US","status_udp":true,"status_tcp":true,"version":"1000002008","motd":"tox-bootstrapd","last_ping":1606049991},{"ipv4":"tox.kurnevsky.net","ipv6":"tox.kurnevsky.net","port":33445,"tcp_ports":[33445],"public_key":"82EF82BA33445A1F91A7DB27189ECFC0C013E06E3DA71F588ED692BED625EC23","maintainer":"kurnevsky","location":"NL","status_udp":true,"status_tcp":true,"version":"3000001000","motd":"Hi from tox-rs! I'm up 02 days 02 hours 59 minutes.","last_ping":1606049993},{"ipv4":"205.185.115.131","ipv6":"-","port":53,"tcp_ports":[53,33445,443,3389],"public_key":"3091C6BEB2A993F1C6300C16549FABA67098FF3D62C6D253828B531470B53D68","maintainer":"GDR!","location":"US","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"https://gdr.name/tuntox/","last_ping":1606049991},{"ipv4":"tox2.abilinski.com","ipv6":"tox2.abilinski.com","port":33445,"tcp_ports":[33445],"public_key":"7A6098B590BDC73F9723FC59F82B3F9085A64D1B213AAF8E610FD351930D052D","maintainer":"AnthonyBilinski","location":"US","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"Running https://github.com/toktok/c-toxcore v0.2.12. qTox best Tox! Contact: AC18841E56CCDEE16E93E10E6AB2765BE54277D67F1372921B5B418A6B330D3D3FAFA60B0931","last_ping":1606049991},{"ipv4":"floki.blog","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"6C6AF2236F478F8305969CCFC7A7B67C6383558FF87716D38D55906E08E72667","maintainer":"Floki","location":"GB","status_udp":true,"status_tcp":false,"version":"1000002010","motd":"tox-bootstrapd","last_ping":1606049991},{"ipv4":"51.158.146.76","ipv6":"2001:bc8:6010:213:208:a2ff:fe0c:7fee","port":33445,"tcp_ports":[3389,33445],"public_key":"E940D8FA9B07C1D13EA4ECF9F06B66F565F1CF61F094F60C67FDC8ADD3F4BA59","maintainer":"CyberSquirrel","location":"NL","status_udp":true,"status_tcp":true,"version":"1000002009","motd":"CyberSquirrel TOX node. Contacts - toxnode@cock.li","last_ping":1606049993},{"ipv4":"46.101.197.175","ipv6":"2a03:b0c0:3:d0::ac:5001","port":33445,"tcp_ports":[3389,33445],"public_key":"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707","maintainer":"kotelnik","location":"DE","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"Batcave lockdown lifted","last_ping":1606049992},{"ipv4":"tox1.mf-net.eu","ipv6":"tox1.mf-net.eu","port":33445,"tcp_ports":[33445,3389],"public_key":"B3E5FA80DC8EBD1149AD2AB35ED8B85BD546DEDE261CA593234C619249419506","maintainer":"2mf","location":"DE","status_udp":true,"status_tcp":true,"version":"1000002010","motd":"tox-bootstrapd","last_ping":1606049992},{"ipv4":"tox2.mf-net.eu","ipv6":"tox2.mf-net.eu","port":33445,"tcp_ports":[33445,3389],"public_key":"70EA214FDE161E7432530605213F18F7427DC773E276B3E317A07531F548545F","maintainer":"2mf","location":"DE","status_udp":true,"status_tcp":true,"version":"1000002009","motd":"tox-bootstrapd","last_ping":1606049992},{"ipv4":"195.201.7.101","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"B84E865125B4EC4C368CD047C72BCE447644A2DC31EF75BD2CDA345BFD310107","maintainer":"tux1973","location":"DE","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"tox-bootstrapd","last_ping":1606049992},{"ipv4":"168.138.203.178","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"6D04D8248E553F6F0BFDDB66FBFB03977E3EE54C432D416BC2444986EF02CC17","maintainer":"SOT-TECH","location":"JP","status_udp":true,"status_tcp":true,"version":"1000002012","motd":"SOT-TECH NPO","last_ping":1606049993},{"ipv4":"5.19.249.240","ipv6":"-","port":38296,"tcp_ports":[38296,3389],"public_key":"DA98A4C0CD7473A133E115FEA2EBDAEEA2EF4F79FD69325FC070DA4DE4BA3238","maintainer":"Toxdaemon","location":"RU","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1606049992},{"ipv4":"209.59.144.175","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"214B7FEA63227CAEC5BCBA87F7ABEEDB1A2FF6D18377DD86BF551B8E094D5F1E","maintainer":"LasersAreGreat","location":"US","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1606049991},{"ipv4":"tox.neuland.technology","ipv6":"tox.neuland.technology","port":33445,"tcp_ports":[],"public_key":"15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E","maintainer":"Nolz","location":"DE","status_udp":false,"status_tcp":false,"version":"1000002008","motd":"Unlike Others","last_ping":1580033828},{"ipv4":"37.48.122.22","ipv6":"2001:1af8:4700:a115:6::b","port":33445,"tcp_ports":[],"public_key":"1B5A8AB25FFFB66620A531C4646B47F0F32B74C547B30AF8BD8266CA50A3AB59","maintainer":"Pokemon","location":"NL","status_udp":false,"status_tcp":false,"version":"1000002009","motd":"Those who would give up essential Liberty, to purchase a little temporary Safety, deserve neither Liberty nor Safety","last_ping":1582034682},{"ipv4":"185.14.30.213","ipv6":"2a00:1ca8:a7::e8b","port":443,"tcp_ports":[],"public_key":"2555763C8C460495B14157D234DD56B86300A2395554BCAE4621AC345B8C1B1B","maintainer":"dvor","location":"NL","status_udp":false,"status_tcp":false,"version":"1000002008","motd":"Just another tox node.","last_ping":1579652108},{"ipv4":"87.118.126.207","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"0D303B1778CA102035DA01334E7B1855A45C3EFBC9A83B9D916FFDEBC6DD3B2E","maintainer":"quux","location":"DE","status_udp":false,"status_tcp":false,"version":"1000002010","motd":"Make Orwell Fiction Again","last_ping":1604352232},{"ipv4":"81.169.136.229","ipv6":"2a01:238:4254:2a00:7aca:fe8c:68e0:27ec","port":33445,"tcp_ports":[],"public_key":"E0DB78116AC6500398DDBA2AEEF3220BB116384CAB714C5D1FCD61EA2B69D75E","maintainer":"9ofSpades","location":"DE","status_udp":false,"status_tcp":false,"version":"1000002012","motd":"🂩 wishes happy toxing. 📡","last_ping":1603210002},{"ipv4":"109.111.178.181","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"7B9BF5C2FF43F60592381596D4CBDC88B287AA3FD34C7536924CB1395CAF7E0C","maintainer":"LivingstoneI2P","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":1597828243},{"ipv4":"218.28.170.22","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"DBACB7D3F53693498398E6B46EF0C063A4656EB02FEFA11D72A60BAFA8DF7B59","maintainer":"OnionBulb","location":"CN","status_udp":false,"status_tcp":false,"version":"1000002010","motd":"tox-bootstrapd","last_ping":1591926097},{"ipv4":"194.36.190.71","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"B62F1878BD08EDD34E4D7B0D66F9E74CC7BDE4BEA2C95E130DAADCFF9BCB4F6D","maintainer":"Shilov","location":"NL","status_udp":false,"status_tcp":false,"version":"1000002010","motd":"tox-bootstrapd","last_ping":1583185122},{"ipv4":"94.45.70.19","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"CE049A748EB31F0377F94427E8E3D219FC96509D4F9D16E181E956BC5B1C4564","maintainer":"Shilov","location":"UA","status_udp":false,"status_tcp":false,"version":"3000000008","motd":"{{Welcome to Ukraine!}} 33 days 02 hours 17 minutes Tcp: incoming 217.5M, outgoing 177.2M, Udp: incoming 307.9M, outgoing 332.5M","last_ping":1583184822},{"ipv4":"185.66.13.169","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"A44A024DA1299A85B91E3A64B9D19C7F331D0073DD2FAAF1361C127B5D909E3D","maintainer":"Shilov","location":"RU","status_udp":false,"status_tcp":false,"version":"3000000008","motd":"{Elektrostal{start_date}} 10 days 02 hours 47 minutes Tcp: incoming 50.1M, outgoing 38.6M, Udp: incoming 96.3M, outgoing 102.5M","last_ping":1583185662},{"ipv4":"46.146.229.184","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"94750E94013586CCD989233A621747E2646F08F31102339452CADCF6DC2A760A","maintainer":"GS","location":"RU","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1602080262}]}qTox/security/000077500000000000000000000000001415623743500136725ustar00rootroot00000000000000qTox/security/apparmor/000077500000000000000000000000001415623743500155135ustar00rootroot00000000000000qTox/security/apparmor/2.12.1/000077500000000000000000000000001415623743500162345ustar00rootroot00000000000000qTox/security/apparmor/2.12.1/install.sh000077500000000000000000000025721415623743500202470ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see set -e -u pipefail readonly SCRIPT_DIR=$(dirname $(readlink -f $0)) if [[ $(id -u) != 0 ]] then >&2 echo "Please run as root." exit 1 fi if [[ -z $(which apparmor_parser) ]] then >&2 echo "AppArmor not found." exit 1 fi echo "Copying AppArmor files..." [[ ! -d "/etc/apparmor.d/tunables/usr.bin.qtox.d/" ]] && mkdir -v "/etc/apparmor.d/tunables/usr.bin.qtox.d/" cp -v "${SCRIPT_DIR}/tunables/usr.bin.qtox" "/etc/apparmor.d/tunables/" cp -v "${SCRIPT_DIR}/usr.bin.qtox" "/etc/apparmor.d/" touch "/etc/apparmor.d/local/usr.bin.qtox" echo "Restarting AppArmor..." systemctl restart apparmor echo "Done." qTox/security/apparmor/2.12.1/tunables/000077500000000000000000000000001415623743500200515ustar00rootroot00000000000000qTox/security/apparmor/2.12.1/tunables/usr.bin.qtox000066400000000000000000000022011415623743500223410ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see @{qtox_prefix} = /usr /usr/local # Allow to read & write into mounted media, etc. # for convenient sending & receiving of files. @{qtox_additional_rw_dirs} = /mnt /media # Create /etc/apparmor.d/tunables/usr.bin.qtox.d/local file to append values as # needed, such as: # @{qtox_prefix} += @{HOME}/opt/qtox # @{qtox_additional_rw_dirs} = /data/nfs_storage #include qTox/security/apparmor/2.12.1/usr.bin.qtox000066400000000000000000000334001415623743500205310ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see #include #include # using variables in profile name is not yet recommended due to issues with # AppArmor tools # TODO: use this alternative in the future when available #profile qtox @{qtox_prefix}/bin/qtox { profile qtox /usr{,/local}/bin/qtox { #include #include #include #include #include #include #include #include #include #include # Site-specific additions and overrides. See local/README for details. #include # Main executable @{qtox_prefix}/bin/qtox mr, # Other executables #TODO: use xdg-open abstraction when it's available /usr/bin/xdg-open PUx, #TODO: use named profile or abstraction when it's available /usr/lib/@{multiarch}/libexec/kf5/kioslave PUx, # Additional libraries # Allow /usr/local/lib/libtoxcore.so... @{qtox_prefix}/lib/*.so* mr, # Networking network inet udp, network inet6 udp, network inet tcp, network inet6 tcp, # DBus dbus send bus=session path=/org/a11y/bus interface=org.freedesktop.DBus.Properties member=Get peer=(label=unconfined), dbus receive bus=session path=/ interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=session path=/StatusNotifierWatcher interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus (send,receive) bus=session path=/StatusNotifierWatcher interface=org.freedesktop.DBus.Properties member=Get peer=(label=unconfined), dbus receive bus=session path=/StatusNotifierItem interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.NetworkManager member=GetDevices peer=(label=unconfined), dbus receive bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.NetworkManager member=PropertiesChanged peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Settings interface=org.freedesktop.NetworkManager.Settings member=ListConnections peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Settings/[0-9]* interface=org.freedesktop.NetworkManager.Settings.Connection member=GetSettings peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/ActiveConnection/[0-9]* interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus receive bus=system path=/org/freedesktop/NetworkManager/ActiveConnection/[0-9]* interface=org.freedesktop.NetworkManager.Connection.Active member=PropertiesChanged peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Devices/[0-9]* interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=session path=/StatusNotifierWatcher interface=org.kde.StatusNotifierWatcher member=RegisterStatusNotifierItem peer=(label=unconfined), dbus receive bus=session path=/StatusNotifierItem interface=org.kde.StatusNotifierItem member=Activate peer=(label=unconfined), dbus (send,receive) bus=session path=/MenuBar interface=com.canonical.dbusmenu member=GetLayout peer=(label=unconfined), dbus (send,receive) bus=session path=/MenuBar interface=com.canonical.dbusmenu member={AboutToShow,Event} peer=(label=unconfined), dbus send bus=session path=/StatusNotifierItem interface=org.kde.StatusNotifierItem member={NewIcon,NewToolTip} peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UPower interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UDisks2/{block_devices,block_devices/*,drives,drives/*} interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UDisks2/{block_devices,drives}/* interface=org.freedesktop.DBus.Properties member={Get,GetAll} peer=(label=unconfined), dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=GetConnectionUnixUser peer=(label=unconfined), dbus send bus=session path=/ interface=org.kde.KDirNotify member={enteredDirectory,leftDirectory} peer=(label=unconfined), dbus receive bus=session path=/ interface=org.kde.KDirNotify member=FilesAdded peer=(label=unconfined), dbus send bus=session path=/KLauncher interface=org.kde.KSlaveLauncher member=requestSlave peer=(label=unconfined), # Denied files # libpcre2 on openSUSE tries to mmap() shared memory on directory. # see: https://lists.ubuntu.com/archives/apparmor/2019-January/011925.html # AppArmor does not allow to distinguish "real" file vs shared memory one, # so we deny this path to protect from loading exploits from /tmp. deny /tmp/#[0-9][0-9][0-9][0-9][0-9] m, # libfontconfig bug? Should not write to root-owned dirs. deny /usr/share/fonts/** w, deny /var/cache/fontconfig/ w, # System files /usr/share/hunspell/* r, @{qtox_additional_rw_dirs}/ r, @{qtox_additional_rw_dirs}/** rw, # Sensitive directory access!!! # Allow navigating directories with file dialog, to access directory you # can write (read) file to, for most convenience (though against maximum # security). Note: this allows reading only directory contents (list), # not the files itself. /{,**/} r, /dev/ r, /dev/dri/ r, /dev/video[0-9]* rw, # webcam /etc/fstab r, # file dialog /etc/xdg/menus/ r, # file dialog /proc/sys/kernel/core_pattern r, # for KCrash::initialize() /proc/sys/kernel/random/boot_id r, # for QSysInfo::bootUniqueId(), mvoe to qt5 abstraction? /run/udev/data/*:* r, # libKF5KIOFileWidgets.so -> libudev.so (KDE file dialog) /sys/bus/ r, # file dialog /sys/bus/usb/devices/ r, # file dialog /sys/class/ r, # file dialog /sys/devices/**/uevent r, # file dialog /sys/devices/system/node/ r, # for ld-linux-x86-64.so -> libnuma1.so /sys/devices/system/node/node[0-9]*/meminfo r, # for ld-linux-x86-64.so -> libnuma1.so /usr/share/emoticons/{,**} r, /usr/share/hspell/* r, # for spellcheking /usr/share/hwdata/pnp.ids r, # For OpenSUSE only? /usr/share/icu/[0-9]*.[0-9]*/icudt[0-9]*.dat r, # For OpenSUSE only? /usr/share/kf5/sonnet/* r, # for spellcheking /usr/share/kservices5/{,**} r, # file dialog /usr/share/mime/ r, # file dialog /usr/share/plasma/look-and-feel/*/contents/defaults r, # TODO: move to kde abstraction? /usr/share/sounds/ r, # file dialog (alert) /var/lib/aspell/* r, # for spellcheking /{,var/}run/udev/data/* r, # file dialog # User files # Sensitive file access!!! # Allow reading & writing into $HOME, EXCEPT for dot files and directories, # for most convenience (though against maximum security). owner @{HOME}/ r, owner @{HOME}/[^.]* rw, owner @{HOME}/[^.]*/{,**} rw, # QSaveFile security measures? While saving log file owner @{HOME}/[^.]* l -> @{HOME}/#[0-9]*[0-9], owner @{HOME}/[^.]*/** l -> @{HOME}/#[0-9]*[0-9], owner /{,var/}run/user/[0-9]*[0-9]/#[0-9]*[0-9] rw, # file dialog owner /{,var/}run/user/[0-9]*[0-9]/qTox*.slave-socket rwl -> /{,var/}run/user/[0-9]*[0-9]/#[0-9]*[0-9], # file dialog owner @{HOME}/.aspell.??.{pws,prepl} rk, # for spellchecking owner @{HOME}/.cache/Tox/ w, owner @{HOME}/.cache/Tox/qTox/{,**} rw, owner @{HOME}/.cache/fontconfig/** rwk, owner @{HOME}/.cache/qTox/{,**} rw, owner @{HOME}/.cache/thumbnails/** rw, # receiving image file produces thumbnail? owner @{HOME}/.config/menus/ r, # file dialog owner @{HOME}/.config/menus/applications-merged/ r, # file dialog owner @{HOME}/.config/qToxrc rw, owner @{HOME}/.config/qToxrc.?????? rwl -> @{HOME}/.config/#[0-9]*[0-9], # QSaveFile? owner @{HOME}/.config/qToxrc.lock rwk, owner @{HOME}/.config/tox/** l -> @{HOME}/.config/tox/**, # QSaveFile? owner @{HOME}/.config/tox/{,**} rwk, owner @{HOME}/.fonts/.uuid* rw, owner @{HOME}/.fonts/.uuid.* l -> @{HOME}/.fonts/.uuid.*, owner @{HOME}/.fonts/.uuid.*/ rw, owner @{HOME}/.local/share/Tox/{,**} rw, owner @{HOME}/.local/share/qTox/{,**} rw, owner @{HOME}/.local/share/user-places.xbel r, # file dialog owner @{PROC}/@{pid}/cmdline r, # Backport from more recent qt5-compose-cache-write abstraction # commit 1250402471d9d83134b0faa90239a733a37f23f0 owner @{HOME}/.cache/qt_compose_cache_{little,big}_endian_* rwl -> @{HOME}/.cache/#[0-9]*[0-9], owner @{HOME}/.cache/#[0-9]*[0-9] rw, # QSaveFile (anonymous shared memory) # Backport kde-globals-write abstraction # commit fae93f1b6c7a28bb77ad186ab1de41372630272b owner @{HOME}/.config/#[0-9]* rw, owner @{HOME}/.config/kdeglobals rw, owner @{HOME}/.config/kdeglobals.?????? rwl -> @{HOME}/.config/#[0-9]*, owner @{HOME}/.config/kdeglobals.lock rwk, # Backport kde-icon-cache-write abstraction # commit 94014c09f09fc63229bb10fea3f0727113fe5bae owner @{HOME}/.cache/icon-cache.kcache rw, # for KIconLoader # Backport mesa abstraction # commit b5be5964609b4e0927af7c9e4f0276e50ccdc3e3 # System files /dev/dri/ r, # libGLX_mesa.so calls drmGetDevice2() /usr/share/drirc.d/{,*.conf} r, # User files owner @{HOME}/.cache/ w, # if user clears all caches owner @{HOME}/.cache/mesa_shader_cache/ w, owner @{HOME}/.cache/mesa_shader_cache/index rw, owner @{HOME}/.cache/mesa_shader_cache/??/ w, owner @{HOME}/.cache/mesa_shader_cache/??/* rwk, # End of backported mesa abstraction # Backport qt5 abstraction # commit 67816c42cfbadd85aa5cbb086284076c4c289881 # Additional libraries /usr/lib{,64,/@{multiarch}}/qt5/plugins/**.so mr, /usr/lib{,64,/@{multiarch}}/qt5/qml/**.so mr, /usr/lib{,64,/@{multiarch}}/qt5/qml/**.{qmlc,jsc} mr, # Precompiled QML/JavaScript modules # System files /etc/xdg/QtProject/qtlogging.ini r, /usr/share/qt5/translations/*.qm r, /usr/lib{,64,/@{multiarch}}/qt5/plugins/** r, /usr/lib{,64,/@{multiarch}}/qt5/qml/** r, # User files owner @{HOME}/.config/QtProject.conf r, # common settings for QFileDialog, etc (application might need write access) owner @{HOME}/.cache/qt_compose_cache_{little,big}_endian_* r, # for "platforminputcontexts" plugins # End of backported qt5 abstractions # Backport qt5-compose-cache-write abstraction # commit 1250402471d9d83134b0faa90239a733a37f23f0 owner @{HOME}/.cache/qt_compose_cache_{little,big}_endian_* rwl -> @{HOME}/.cache/#[0-9]*[0-9], owner @{HOME}/.cache/#[0-9]*[0-9] rw, # QSaveFile (anonymous shared memory) # Backport qt5-settings-write abstraction # commit 8f6a8fb1942122705af4c45168922c4afd696c8a owner @{HOME}/.config/#[0-9]*[0-9] rw, owner @{HOME}/.config/QtProject.conf rwl -> @{HOME}/.config/#[0-9]*[0-9], # for temporary files like QtProject.conf.Aqrgeb owner @{HOME}/.config/QtProject.conf.?????? rwl -> @{HOME}/.config/#[0-9]*[0-9], owner @{HOME}/.config/QtProject.conf.lock rwk, # Backport recent-documents-write # commit 4fe8ae97c43d72d7f5a948c7149f5ea35339832a owner @{HOME}/.local/share/RecentDocuments/ rw, owner @{HOME}/.local/share/RecentDocuments/#[0-9]* rw, owner @{HOME}/.local/share/RecentDocuments/*.desktop rwl -> @{HOME}/.local/share/RecentDocuments/#[0-9]*, owner @{HOME}/.local/share/RecentDocuments/*.lock rwk, # Backport dri-enumerate abstraction # commit b0456adbd86aab73e4a19013fdfed22da98ed455 /sys/devices/pci[0-9]*/**/{device,subsystem_device,subsystem_vendor,uevent,vendor} r, # Backport kde abstraction # commit aae838faca57905d2dbc27db7bffd595c09d26f0 # commit dc3b73daf9f648336a6f9ab90103acc962c0bf40 /etc/xdg/kdeglobals r, /usr/share/knotifications5/*.notifyrc r, # KNotification::sendEvent() /usr/share/kubuntu-default-settings/kf5-settings/* r, owner @{HOME}/.cache/ksycoca5_??_* r, # KDE System Configuration Cache owner @{HOME}/.config/baloofilerc r, # indexing options (excludes, etc), used by KFileWidget owner @{HOME}/.config/dolphinrc r, # settings used by KFileWidget owner @{HOME}/.config/kde.org/libphonon.conf r, # for KNotifications::sendEvent() owner @{HOME}/.config/kdeglobals r, # global settings, used by Breeze style, etc. owner @{HOME}/.config/klanguageoverridesrc r, # per-application languages, for KDEPrivate::initializeLanguages() from libKF5XmlGui.so owner @{HOME}/.config/trashrc r, # Used by KFileWidget # Backport dri-common abstraction # commit 2d8d2f06d5697d9692330686bb5ddb0095621144 /usr/share/drirc.d/{,*.conf} r, } qTox/security/apparmor/2.13.2/000077500000000000000000000000001415623743500162365ustar00rootroot00000000000000qTox/security/apparmor/2.13.2/install.sh000077500000000000000000000026071415623743500202500ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see set -e -u pipefail readonly SCRIPT_DIR=$(dirname $(readlink -f $0)) if [[ $(id -u) != 0 ]] then >&2 echo "Please run as root." exit 1 fi if [[ -z $(which apparmor_parser) ]] then >&2 echo "AppArmor not found." exit 1 fi #NOTE: we do not need to create /etc/apparmor.d/tunables/usr.bin.qtox.d/ or #/etc/apparmor.d/local/usr.bin.qtox because AppArmor >2.13 support #include if #exists echo "Copying AppArmor files..." cp -v "${SCRIPT_DIR}/tunables/usr.bin.qtox" "/etc/apparmor.d/tunables/" cp -v "${SCRIPT_DIR}/usr.bin.qtox" "/etc/apparmor.d/" echo "Restarting AppArmor..." systemctl restart apparmor echo "Done." qTox/security/apparmor/2.13.2/tunables/000077500000000000000000000000001415623743500200535ustar00rootroot00000000000000qTox/security/apparmor/2.13.2/tunables/usr.bin.qtox000066400000000000000000000022131415623743500223460ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see @{qtox_prefix} = /usr /usr/local # Allow to read & write into mounted media, etc. # for convenient sending & receiving of files. @{qtox_additional_rw_dirs} = /mnt /media # Create /etc/apparmor.d/tunables/usr.bin.qtox.d/local file to append values as # needed, such as: # @{qtox_prefix} += @{HOME}/opt/qtox # @{qtox_additional_rw_dirs} = /data/nfs_storage #include if exists qTox/security/apparmor/2.13.2/usr.bin.qtox000066400000000000000000000262771415623743500205510ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see #include #include # using variables in profile name is not yet recommended due to issues with # AppArmor tools # TODO: use this alternative in the future when available #profile qtox @{qtox_prefix}/bin/qtox { profile qtox /usr{,/local}/bin/qtox { #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include # Site-specific additions and overrides. See local/README for details. #include if exists # Main executable @{qtox_prefix}/bin/qtox mr, # Other executables #TODO: use xdg-open abstraction when it's available /usr/bin/xdg-open PUx, #TODO: use named profile or abstraction when it's available /usr/lib/@{multiarch}/libexec/kf5/kioslave PUx, # Additional libraries # Allow /usr/local/lib/libtoxcore.so... @{qtox_prefix}/lib/*.so* mr, # Networking network inet udp, network inet6 udp, network inet tcp, network inet6 tcp, # DBus dbus send bus=session path=/org/a11y/bus interface=org.freedesktop.DBus.Properties member=Get peer=(label=unconfined), dbus receive bus=session path=/ interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=session path=/StatusNotifierWatcher interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus (send,receive) bus=session path=/StatusNotifierWatcher interface=org.freedesktop.DBus.Properties member=Get peer=(label=unconfined), dbus receive bus=session path=/StatusNotifierItem interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.NetworkManager member=GetDevices peer=(label=unconfined), dbus receive bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.NetworkManager member=PropertiesChanged peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Settings interface=org.freedesktop.NetworkManager.Settings member=ListConnections peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Settings/[0-9]* interface=org.freedesktop.NetworkManager.Settings.Connection member=GetSettings peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/ActiveConnection/[0-9]* interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus receive bus=system path=/org/freedesktop/NetworkManager/ActiveConnection/[0-9]* interface=org.freedesktop.NetworkManager.Connection.Active member=PropertiesChanged peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Devices/[0-9]* interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=session path=/StatusNotifierWatcher interface=org.kde.StatusNotifierWatcher member=RegisterStatusNotifierItem peer=(label=unconfined), dbus receive bus=session path=/StatusNotifierItem interface=org.kde.StatusNotifierItem member=Activate peer=(label=unconfined), dbus (send,receive) bus=session path=/MenuBar interface=com.canonical.dbusmenu member=GetLayout peer=(label=unconfined), dbus (send,receive) bus=session path=/MenuBar interface=com.canonical.dbusmenu member={AboutToShow,Event} peer=(label=unconfined), dbus send bus=session path=/StatusNotifierItem interface=org.kde.StatusNotifierItem member={NewIcon,NewToolTip} peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UPower interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UDisks2/{block_devices,block_devices/*,drives,drives/*} interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UDisks2/{block_devices,drives}/* interface=org.freedesktop.DBus.Properties member={Get,GetAll} peer=(label=unconfined), dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=GetConnectionUnixUser peer=(label=unconfined), dbus send bus=session path=/ interface=org.kde.KDirNotify member={enteredDirectory,leftDirectory} peer=(label=unconfined), dbus receive bus=session path=/ interface=org.kde.KDirNotify member=FilesAdded peer=(label=unconfined), dbus send bus=session path=/KLauncher interface=org.kde.KSlaveLauncher member=requestSlave peer=(label=unconfined), # Denied files # libpcre2 on openSUSE tries to mmap() shared memory on directory. # see: https://lists.ubuntu.com/archives/apparmor/2019-January/011925.html # AppArmor does not allow to distinguish "real" file vs shared memory one, # so we deny this path to protect from loading exploits from /tmp. deny /tmp/#[0-9][0-9][0-9][0-9][0-9] m, # libfontconfig bug? Should not write to root-owned dirs. deny /usr/share/fonts/** w, deny /var/cache/fontconfig/ w, # System files /usr/share/hunspell/* r, @{qtox_additional_rw_dirs}/ r, @{qtox_additional_rw_dirs}/** rw, # Sensitive directory access!!! # Allow navigating directories with file dialog, to access directory you # can write (read) file to, for most convenience (though against maximum # security). Note: this allows reading only directory contents (list), # not the files itself. /{,**/} r, /dev/ r, /dev/dri/ r, /dev/video[0-9]* rw, # webcam /etc/fstab r, # file dialog /etc/xdg/menus/ r, # file dialog /proc/sys/kernel/core_pattern r, # for KCrash::initialize() /proc/sys/kernel/random/boot_id r, # for QSysInfo::bootUniqueId(), mvoe to qt5 abstraction? /run/udev/data/*:* r, # libKF5KIOFileWidgets.so -> libudev.so (KDE file dialog) /sys/bus/ r, # file dialog /sys/bus/usb/devices/ r, # file dialog /sys/class/ r, # file dialog /sys/devices/**/uevent r, # file dialog /sys/devices/system/node/ r, # for ld-linux-x86-64.so -> libnuma1.so /sys/devices/system/node/node[0-9]*/meminfo r, # for ld-linux-x86-64.so -> libnuma1.so /usr/share/emoticons/{,**} r, /usr/share/hspell/* r, # for spellchecking /usr/share/hwdata/pnp.ids r, # For OpenSUSE only? /usr/share/icu/[0-9]*.[0-9]*/icudt[0-9]*.dat r, # For OpenSUSE only? /usr/share/kf5/sonnet/* r, # for spellchecking /usr/share/kservices5/{,**} r, # file dialog /usr/share/mime/ r, # file dialog /usr/share/plasma/look-and-feel/*/contents/defaults r, # TODO: move to kde abstraction? /usr/share/sounds/ r, # file dialog (alert) /var/lib/aspell/* r, # for spellchecking /{,var/}run/udev/data/* r, # file dialog # User files # Sensitive file access!!! # Allow reading & writing into $HOME, EXCEPT for dot files and directories, # for most convenience (though against maximum security). owner @{HOME}/ r, owner @{HOME}/[^.]* rw, owner @{HOME}/[^.]*/{,**} rw, # QSaveFile security measures? While saving log file owner @{HOME}/[^.]* l -> @{HOME}/#[0-9]*[0-9], owner @{HOME}/[^.]*/** l -> @{HOME}/#[0-9]*[0-9], owner /{,var/}run/user/@{uid}/#[0-9]*[0-9] rw, # file dialog owner /{,var/}run/user/@{uid}/qTox*.slave-socket rwl -> /{,var/}run/user/@{uid}/#[0-9]*[0-9], # file dialog owner @{HOME}/.aspell.??.{pws,prepl} rk, # for spellchecking owner @{HOME}/.cache/Tox/ w, owner @{HOME}/.cache/Tox/qTox/{,**} rw, owner @{HOME}/.cache/fontconfig/** rwk, owner @{HOME}/.cache/qTox/{,**} rw, owner @{HOME}/.cache/thumbnails/** rw, # receiving image file produces thumbnail? owner @{HOME}/.config/menus/ r, # file dialog owner @{HOME}/.config/menus/applications-merged/ r, # file dialog owner @{HOME}/.config/qToxrc rw, owner @{HOME}/.config/qToxrc.?????? rwl -> @{HOME}/.config/#[0-9]*[0-9], # QSaveFile? owner @{HOME}/.config/qToxrc.lock rwk, owner @{HOME}/.config/tox/** l -> @{HOME}/.config/tox/**, # QSaveFile? owner @{HOME}/.config/tox/{,**} rwk, owner @{HOME}/.fonts/.uuid* rw, owner @{HOME}/.fonts/.uuid.* l -> @{HOME}/.fonts/.uuid.*, owner @{HOME}/.fonts/.uuid.*/ rw, owner @{HOME}/.local/share/Tox/{,**} rw, owner @{HOME}/.local/share/qTox/{,**} rw, owner @{HOME}/.local/share/user-places.xbel r, # file dialog owner @{PROC}/@{pid}/cmdline r, # Backport from more recent qt5-compose-cache-write abstraction # commit 1250402471d9d83134b0faa90239a733a37f23f0 owner @{HOME}/.cache/qt_compose_cache_{little,big}_endian_* rwl -> @{HOME}/.cache/#[0-9]*[0-9], owner @{HOME}/.cache/#[0-9]*[0-9] rw, # QSaveFile (anonymous shared memory) # Backport kde abstraction # commit aae838faca57905d2dbc27db7bffd595c09d26f0 # commit dc3b73daf9f648336a6f9ab90103acc962c0bf40 /etc/xdg/kdeglobals r, /usr/share/knotifications5/*.notifyrc r, # KNotification::sendEvent() /usr/share/kubuntu-default-settings/kf5-settings/* r, owner @{HOME}/.cache/ksycoca5_??_* r, # KDE System Configuration Cache owner @{HOME}/.config/baloofilerc r, # indexing options (excludes, etc), used by KFileWidget owner @{HOME}/.config/dolphinrc r, # settings used by KFileWidget owner @{HOME}/.config/kde.org/libphonon.conf r, # for KNotifications::sendEvent() owner @{HOME}/.config/kdeglobals r, # global settings, used by Breeze style, etc. owner @{HOME}/.config/klanguageoverridesrc r, # per-application languages, for KDEPrivate::initializeLanguages() from libKF5XmlGui.so owner @{HOME}/.config/trashrc r, # Used by KFileWidget # Backport dri-common abstraction # commit 2d8d2f06d5697d9692330686bb5ddb0095621144 /usr/share/drirc.d/{,*.conf} r, } qTox/security/apparmor/2.13.3/000077500000000000000000000000001415623743500162375ustar00rootroot00000000000000qTox/security/apparmor/2.13.3/install.sh000077500000000000000000000026071415623743500202510ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see set -e -u pipefail readonly SCRIPT_DIR=$(dirname $(readlink -f $0)) if [[ $(id -u) != 0 ]] then >&2 echo "Please run as root." exit 1 fi if [[ -z $(which apparmor_parser) ]] then >&2 echo "AppArmor not found." exit 1 fi #NOTE: we do not need to create /etc/apparmor.d/tunables/usr.bin.qtox.d/ or #/etc/apparmor.d/local/usr.bin.qtox because AppArmor >2.13 support #include if #exists echo "Copying AppArmor files..." cp -v "${SCRIPT_DIR}/tunables/usr.bin.qtox" "/etc/apparmor.d/tunables/" cp -v "${SCRIPT_DIR}/usr.bin.qtox" "/etc/apparmor.d/" echo "Restarting AppArmor..." systemctl restart apparmor echo "Done." qTox/security/apparmor/2.13.3/tunables/000077500000000000000000000000001415623743500200545ustar00rootroot00000000000000qTox/security/apparmor/2.13.3/tunables/usr.bin.qtox000066400000000000000000000022131415623743500223470ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see @{qtox_prefix} = /usr /usr/local # Allow to read & write into mounted media, etc. # for convenient sending & receiving of files. @{qtox_additional_rw_dirs} = /mnt /media # Create /etc/apparmor.d/tunables/usr.bin.qtox.d/local file to append values as # needed, such as: # @{qtox_prefix} += @{HOME}/opt/qtox # @{qtox_additional_rw_dirs} = /data/nfs_storage #include if exists qTox/security/apparmor/2.13.3/usr.bin.qtox000066400000000000000000000236321415623743500205420ustar00rootroot00000000000000# Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see #include #include # using variables in profile name is not yet recommended due to issues with # AppArmor tools # TODO: use this alternative in the future when available #profile qtox @{qtox_prefix}/bin/qtox { profile qtox /usr{,/local}/bin/qtox { #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include # Site-specific additions and overrides. See local/README for details. #include if exists # Main executable @{qtox_prefix}/bin/qtox mr, # Other executables #TODO: use xdg-open abstraction when it's available /usr/bin/xdg-open PUx, #TODO: use named profile or abstraction when it's available /usr/lib/@{multiarch}/libexec/kf5/kioslave PUx, # Additional libraries # Allow /usr/local/lib/libtoxcore.so... @{qtox_prefix}/lib/*.so* mr, # Networking network inet udp, network inet6 udp, network inet tcp, network inet6 tcp, # DBus dbus send bus=session path=/org/a11y/bus interface=org.freedesktop.DBus.Properties member=Get peer=(label=unconfined), dbus receive bus=session path=/ interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=session path=/StatusNotifierWatcher interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus (send,receive) bus=session path=/StatusNotifierWatcher interface=org.freedesktop.DBus.Properties member=Get peer=(label=unconfined), dbus receive bus=session path=/StatusNotifierItem interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.NetworkManager member=GetDevices peer=(label=unconfined), dbus receive bus=system path=/org/freedesktop/NetworkManager interface=org.freedesktop.NetworkManager member=PropertiesChanged peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Settings interface=org.freedesktop.NetworkManager.Settings member=ListConnections peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Settings/[0-9]* interface=org.freedesktop.NetworkManager.Settings.Connection member=GetSettings peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/ActiveConnection/[0-9]* interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus receive bus=system path=/org/freedesktop/NetworkManager/ActiveConnection/[0-9]* interface=org.freedesktop.NetworkManager.Connection.Active member=PropertiesChanged peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/NetworkManager/Devices/[0-9]* interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), dbus send bus=session path=/StatusNotifierWatcher interface=org.kde.StatusNotifierWatcher member=RegisterStatusNotifierItem peer=(label=unconfined), dbus receive bus=session path=/StatusNotifierItem interface=org.kde.StatusNotifierItem member=Activate peer=(label=unconfined), dbus (send,receive) bus=session path=/MenuBar interface=com.canonical.dbusmenu member=GetLayout peer=(label=unconfined), dbus (send,receive) bus=session path=/MenuBar interface=com.canonical.dbusmenu member={AboutToShow,Event} peer=(label=unconfined), dbus send bus=session path=/StatusNotifierItem interface=org.kde.StatusNotifierItem member={NewIcon,NewToolTip} peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UPower interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UDisks2/{block_devices,block_devices/*,drives,drives/*} interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), dbus send bus=system path=/org/freedesktop/UDisks2/{block_devices,drives}/* interface=org.freedesktop.DBus.Properties member={Get,GetAll} peer=(label=unconfined), dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=GetConnectionUnixUser peer=(label=unconfined), dbus send bus=session path=/ interface=org.kde.KDirNotify member={enteredDirectory,leftDirectory} peer=(label=unconfined), dbus receive bus=session path=/ interface=org.kde.KDirNotify member=FilesAdded peer=(label=unconfined), dbus send bus=session path=/KLauncher interface=org.kde.KSlaveLauncher member=requestSlave peer=(label=unconfined), # Denied files # libpcre2 on openSUSE tries to mmap() shared memory on directory. # see: https://lists.ubuntu.com/archives/apparmor/2019-January/011925.html # AppArmor does not allow to distinguish "real" file vs shared memory one, # so we deny this path to protect from loading exploits from /tmp. deny /tmp/#[0-9][0-9][0-9][0-9][0-9] m, # libfontconfig bug? Should not write to root-owned dirs. deny /usr/share/fonts/** w, deny /var/cache/fontconfig/ w, # System files /usr/share/hunspell/* r, @{qtox_additional_rw_dirs}/ r, @{qtox_additional_rw_dirs}/** rw, # Sensitive directory access!!! # Allow navigating directories with file dialog, to access directory you # can write (read) file to, for most convenience (though against maximum # security). Note: this allows reading only directory contents (list), # not the files itself. /{,**/} r, /dev/ r, /dev/video[0-9]* rw, # webcam /etc/fstab r, # file dialog /etc/xdg/menus/ r, # file dialog /proc/sys/kernel/core_pattern r, # for KCrash::initialize() /proc/sys/kernel/random/boot_id r, # for QSysInfo::bootUniqueId(), mvoe to qt5 abstraction? /run/udev/data/*:* r, # libKF5KIOFileWidgets.so -> libudev.so (KDE file dialog) /sys/bus/ r, # file dialog /sys/bus/usb/devices/ r, # file dialog /sys/class/ r, # file dialog /sys/devices/**/uevent r, # file dialog /sys/devices/system/node/ r, # for ld-linux-x86-64.so -> libnuma1.so /sys/devices/system/node/node[0-9]*/meminfo r, # for ld-linux-x86-64.so -> libnuma1.so /usr/share/emoticons/{,**} r, /usr/share/hspell/* r, # for spellchecking /usr/share/hwdata/pnp.ids r, # For OpenSUSE only? /usr/share/icu/[0-9]*.[0-9]*/icudt[0-9]*.dat r, # For OpenSUSE only? /usr/share/kf5/sonnet/* r, # for spellchecking /usr/share/kservices5/{,**} r, # file dialog /usr/share/mime/ r, # file dialog /usr/share/plasma/look-and-feel/*/contents/defaults r, # TODO: move to kde abstraction? /usr/share/sounds/ r, # file dialog (alert) /var/lib/aspell/* r, # for spellchecking /{,var/}run/udev/data/* r, # file dialog # User files # Sensitive file access!!! # Allow reading & writing into $HOME, EXCEPT for dot files and directories, # for most convenience (though against maximum security). owner @{HOME}/ r, owner @{HOME}/[^.]* rw, owner @{HOME}/[^.]*/{,**} rw, # QSaveFile security measures? While saving log file owner @{HOME}/[^.]* l -> @{HOME}/#[0-9]*[0-9], owner @{HOME}/[^.]*/** l -> @{HOME}/#[0-9]*[0-9], owner /{,var/}run/user/@{uid}/#[0-9]*[0-9] rw, # file dialog owner /{,var/}run/user/@{uid}/qTox*.slave-socket rwl -> /{,var/}run/user/@{uid}/#[0-9]*[0-9], # file dialog owner @{HOME}/.aspell.??.{pws,prepl} rk, # for spellchecking owner @{HOME}/.cache/Tox/ w, owner @{HOME}/.cache/Tox/qTox/{,**} rw, owner @{HOME}/.cache/fontconfig/** rwk, owner @{HOME}/.cache/qTox/{,**} rw, owner @{HOME}/.cache/thumbnails/** rw, # receiving image file produces thumbnail? owner @{HOME}/.config/menus/ r, # file dialog owner @{HOME}/.config/menus/applications-merged/ r, # file dialog owner @{HOME}/.config/qToxrc rw, owner @{HOME}/.config/qToxrc.?????? rwl -> @{HOME}/.config/#[0-9]*[0-9], # QSaveFile? owner @{HOME}/.config/qToxrc.lock rwk, owner @{HOME}/.config/tox/** l -> @{HOME}/.config/tox/**, # QSaveFile? owner @{HOME}/.config/tox/{,**} rwk, owner @{HOME}/.fonts/.uuid* rw, owner @{HOME}/.fonts/.uuid.* l -> @{HOME}/.fonts/.uuid.*, owner @{HOME}/.fonts/.uuid.*/ rw, owner @{HOME}/.local/share/Tox/{,**} rw, owner @{HOME}/.local/share/qTox/{,**} rw, owner @{HOME}/.local/share/user-places.xbel r, # file dialog owner @{PROC}/@{pid}/cmdline r, } qTox/security/apparmor/README.md000066400000000000000000000122301415623743500167700ustar00rootroot00000000000000# Hardening qTox with AppArmor qTox can be confined with AppArmor on Linux to reduce attack vectors in case remote code execution exploit is being used. Please note that [MAC's] (of course) does not guarantee perfect security, but it will: - Deny access to your `~/.bashrc`, `~/.ssh/*` `~/.config/path/to/your/password/manager/file`, etc. - Deny creating autostart entries (in `~/.config/autostart`, etc). - Deny launching random executables (like `sudo`, `su`, etc...). - And more. Consider using additional security measures like [Firejail] to improve security even more. Please also note that not all distributions have full AppArmor feature set available. For example, Debian (at least up to Debian 10 (buster)) does not have network, DBus mediation available. Also, X Server, shared user configuration files (like `~/.config/QtProject.conf`, caches, etc), opening web links via unconfined browsers introduces additional attack vectors, too. So please be cautious even with number of security measures applied. **AppArmor profile attaches only to `/usr/bin/qtox` and `/usr/local/bin/qtox` executables by default**. See [Tuning permissions](#tuning-permissions) for custom setups. ## Installing profile Select AppArmor profile from appropriate `security/apparmor/X` subdirectory depending on what AppArmor version is available for your Linux distribution release: - 2.13.3 - Debian 11 (bullseye) (or newer) - Ubuntu 19.10 (or newer) - openSUSE Tumbleweed - 2.13.2 - Debian 10 (buster) - Ubuntu 19.04 (Disco Dingo) - 2.12.1 - Debian 9 (stretch) or older - Ubuntu 18.10 or older To enable AppArmor profile on your system, run prepared install script: ``` sudo security/apparmor/x.y.z/install.sh ``` Restart `qTox` if it was already running before enabling AppArmor profile. ## Checking if qTox is actually confined Run `aa-status` command line utility and check if `qTox` is listed within `X processes are in enforced mode.` list: ``` sudo aa-status ... 21 processes are in enforce mode. /usr/lib/ipsec/charon (2421) /usr/sbin/cups-browsed (839) ... /usr/bin/qtox (16315) qtox ... ``` Alternatively, use `ps` and `grep`: ``` ps auxZ | fgrep qtox qtox (enforce) vincas 16315 2.0 1.1 1502292 180220 ? SLl 12:21 0:38 /usr/bin/qtox ``` If OK it's marked as `(enforce)`. `unconfined` means AppArmor profile is not attached to the process, no confinement is being applied. ## Troubleshooting If you believe that some feature is unavailable, or some files you need access to are inaccessible due to enforced AppArmor profile, check system logs for the hints. On Debian/Ubuntu: ``` sudo fgrep DENIED /var/log/syslog ``` On openSUSE, OR if you have `auditd` daemon installed: ``` sudo fgrep DENIED /var/log/audit/audit.log ``` You will see messages like this: ``` type=AVC msg=audit(1549793273.269:149): apparmor="DENIED" operation="open" profile="qtox" name="/home/vincas/.config/klanguageove rridesrc" pid=3037 comm="qtox" requested_mask="r" denied_mask="r" fsuid=1000 ouid=1000 ``` This means that `r`ead access was denied to the file `/home/vincas/.config/klanguageoverridesrc`, owned by you (ouid 1000), by AppArmor profile `qtox` (available in `/etc/apparmor.d/usr.bin.qtox`). Please create issue if you detect new AppArmor `DENIED` messages and you believe that these denials are relevant for other users too. Meanwhile, workaround by adding manual rule. DO NOT modify `/etc/apparmor.d/usr.bin.qtox` directly! See [Tuning permissions](#tuning-permissions) for fixing access issues. ## Tuning permissions If you need access to files (for file sharing) other than from your `$HOME` or mounted media, create/modify `/etc/apparmor.d/tunables/usr.bin.qtox.d/local` file and append writable path variable: ``` @{qtox_additional_rw_dirs} += /path/to/some/directory ``` Alternatively, if you need more custom/advanced rules (not only for file access), create/modify `/etc/apparmor.d/local/usr.bin.qtox` file. Rule example for reading only, recursively (note the comma!): ``` /path/to/directory/** r, ``` For reading and writing, recursively: ``` /path/to/directory/** rw, ``` Restart AppArmor to reload profiles after modifications: ``` sudo systemctl restart apparmor ``` If AppArmor restart fails, check syntax errors by invoking AppArmor parser directly: ``` sudo apparmor_parser -r /etc/apparmor.d/usr.bin.qtox ``` For custom installations, when `qTox` executable is not `/usr/bin/qtox` or `/usr/local/bin/qtox`: 1. create `/etc/apparmor.d/tunables/usr.bin.qtox.d/local`, adding `@{qtox_prefix} += /path/to/your/custom/install/prefix` line. 2. modify `/etc/apparmor.d/usr.bin.qtox` profile attachement path: `profile qtox /{usr{,local}/bin/qtox,path/to/your/qtox_executable} {` Restart AppArmor and [check](#checking-if-qtox-is-actually-confined) if `qTox` process under custom path is actually confined. ## Other resources Check [Debian], [Ubuntu], [Upstream AppArmor] Wiki pages for more info. [Debian]: https://wiki.debian.org/AppArmor [Firejail]: https://firejail.wordpress.com [MAC's]: https://en.wikipedia.org/wiki/Mandatory_access_control [Ubuntu]: https://wiki.ubuntu.com/AppArmor [Upstream AppArmor]: https://gitlab.com/apparmor/apparmor/wikis/home qTox/simple_make.sh000077500000000000000000000102701415623743500146500ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see set -eu -o pipefail apt_install() { local apt_packages=( automake autotools-dev build-essential check checkinstall cmake git libavdevice-dev libexif-dev libgdk-pixbuf2.0-dev libgtk2.0-dev libopenal-dev libopus-dev libqrencode-dev libqt5opengl5-dev libqt5svg5-dev libsodium-dev libtool libvpx-dev libxss-dev qrencode qt5-default qttools5-dev qttools5-dev-tools libsqlcipher-dev ) sudo apt-get install "${apt_packages[@]}" } pacman_install() { local pacman_packages=( base-devel git libsodium libvpx libxss openal opus qrencode qt5 ) sudo pacman -S --needed "${pacman_packages[@]}" } dnf_install() { local dnf_group_packages=( 'Development Tools' 'C Development Tools and Libraries' ) sudo dnf group install "${dnf_group_packages[@]}" # pure Fedora doesn't have what it takes to compile qTox (ffmpeg) local fedora_version=$(rpm -E %fedora) local dnf_rpmfusion_package=( http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$fedora_version.noarch.rpm ) sudo dnf install "$dnf_rpmfusion_package" local dnf_packages=( ffmpeg-devel gdk-pixbuf2-devel git glib2-devel gtk2-devel libsodium-devel libtool libvpx-devel libXScrnSaver-devel openal-soft-devel openssl-devel opus-devel qrencode-devel qt5-qtsvg qt5-qtsvg-devel qt5-qttools-devel qt-creator qt-devel qt-doc ) sudo dnf install "${dnf_packages[@]}" } # Fedora by default doesn't include libs in /usr/local/lib so add it fedora_locallib() { local llib_file="/etc/ld.so.conf.d/locallib.conf" local llib_line="/usr/local/lib/" # check whether needed line already exists is_locallib() { grep -q "^$llib_line\$" "$llib_file" } # proceed only if line doesn't exist is_locallib \ || echo "$llib_line" \ | sudo tee -a "$llib_file" } zypper_install() { local zypper_packages=( +pattern:devel_basis cmake git libavcodec-devel libavdevice-devel libopus-devel libexif-devel libQt5Concurrent-devel libqt5-linguist libqt5-linguist-devel libQt5Network-devel libQt5OpenGL-devel libqt5-qtbase-common-devel libqt5-qtsvg-devel libQt5Test-devel libQt5Xml-devel libsodium-devel libvpx-devel libXScrnSaver-devel openal-soft-devel qrencode-devel sqlcipher-devel ) # if not sudo is installed, e.g. in docker image, install it command -v sudo || zypper in sudo sudo zypper in "${zypper_packages[@]}" } main() { if command -v zypper && [ -f /etc/products.d/openSUSE.prod ] then zypper_install elif command -v apt-get then apt_install elif command -v pacman then pacman_install elif command -v dnf then dnf_install fedora_locallib else echo "Unknown package manager, attempting to compile anyways" fi ./bootstrap.sh mkdir -p _build cd _build cmake ../ make -j$(nproc) } main qTox/smileys/000077500000000000000000000000001415623743500135105ustar00rootroot00000000000000qTox/smileys/ASCII+Universe/000077500000000000000000000000001415623743500161345ustar00rootroot00000000000000qTox/smileys/ASCII+Universe/LICENSE-GRAPHICS000066400000000000000000000441341415623743500203450ustar00rootroot00000000000000Attribution 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More_considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part; and b. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the "Licensor." Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. qTox/smileys/ASCII+Universe/README.md000066400000000000000000000002171415623743500174130ustar00rootroot00000000000000## License Copyright 2014 Twitter, Inc and other contributors Graphics licensed under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/ qTox/smileys/ASCII+Universe/emoticons.xml000066400000000000000000002031441415623743500206620ustar00rootroot00000000000000 :smile: 😀 :grinning: 😁 😂 :tearsofjoy: 😃 😄 :happy: 😅 😆 😇 😈 😉 😊 😋 😌 😍 😎 :cool: 😏 😐 :disappointed: 😑 😒 😓 😔 😕 😖 :oops: 😗 😘 :-* :* 😙 😚 😛 😜 😝 😞 :sad: 😟 😠 :angry: 😡 😢 :cry: 😣 😤 😥 😦 😧 😨 😩 😪 😫 😬 😭 😮 😯 😰 😱 😲 😳 :$ :blush: 😴 :sleep: :sleeping: 😵 😶 😷 😸 😹 😺 😻 😼 😽 😾 😿 🙀 🙅 🙆 🙇 🙈 🙉 🙊 :silence: 🙋 :hi: 🙌 🙍 🙎 🙏 💩 🌀 🌁 🌂 🌃 🌄 🌅 🌆 🌇 🌈 🌉 🌊 🌋 🌌 🌍 🌎 🌏 🌐 🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘 🌙 🌚 🌛 🌜 🌝 🌞 🌟 🌠 🌰 🌱 🌲 🌳 🌴 🌵 🌷 🌸 🌹 🌺 🌻 🌼 🌽 🌾 🌿 🍀 🍁 🍂 🍃 🍄 🍅 🍆 🍇 🍈 🍉 🍊 🍋 🍌 🍍 🍎 🍏 🍐 🍑 🍒 🍓 🍔 🍕 🍖 🍗 🍘 🍙 🍚 🍛 🍜 🍝 🍞 🍟 🍠 🍡 🍢 🍣 🍤 🍥 🍦 🍧 🍨 🍩 🍪 :cookie: 🍫 🍬 🍭 🍮 🍯 🍰 🍱 🍲 🍳 🍴 🍵 🍶 🍷 🍸 🍹 🍺 :beer: 🍻 :beers: 🍼 🎀 🎁 🎂 🎃 🎄 🎅 🎆 🎇 🎈 🎉 🎊 🎋 🎌 🎍 🎎 🎏 🎐 🎑 🎒 🎓 🎠 🎡 🎢 🎣 🎤 🎥 🎦 🎧 🎨 🎩 🎪 🎫 🎬 🎭 🎮 🎯 🎰 🎱 🎲 🎳 🎴 🎵 🎶 🎷 🎸 🎹 🎺 🎻 🎼 🎽 🎾 🎿 🏀 🏁 🏂 🏃 🏄 🏆 🏇 🏈 🏉 🏊 🏠 🏡 🏢 🏣 🏤 🏥 🏦 🏧 🏨 🏩 🏪 🏫 🏬 🏭 🏮 🏯 🏰 🐀 🐁 🐂 🐃 🐄 🐅 🐆 🐇 🐈 🐉 🐊 🐋 🐌 🐍 🐎 🐏 🐐 🐑 🐒 🐓 🐔 🐕 🐖 🐗 🐘 🐙 🐚 🐛 🐜 🐝 🐞 🐟 🐠 🐡 🐢 🐣 🐤 🐥 🐦 🐧 🐨 🐩 🐪 🐫 🐬 🐭 🐮 🐯 🐰 🐱 🐲 🐳 🐴 🐵 🐶 🐷 🐸 🐹 🐺 🐻 🐼 🐽 🐾 👀 👂 👃 👄 👅 🖕 :finger: :fuck: 👆 👇 👈 👉 👊 👋 👌 👍 👎 👏 👐 👑 👒 👓 👔 👕 👖 👗 👘 👙 👚 👛 👜 👝 👞 👟 👠 👡 👢 👣 👤 👥 👦 👧 👨 👩 👪 👫 👬 👭 👮 👯 👰 👱 👲 👳 👴 👵 👶 👷 👸 👹 👺 👻 👼 :angel: 👽 👾 👿 :devil: :666: 💀 :skull: 💁 💂 💃 💄 💅 💆 💇 💈 💉 :syringe: :injection: 💊 💋 💌 💍 💎 💏 💐 💑 💒 💓 💔 💕 💖 💗 💘 💙 💚 💛 💜 💝 💞 💟 💠 💡 💢 💣 :bomb: 💤 💥 💦 💧 💨 💪 💫 💬 💭 💮 💯 💰 💱 💲 💳 💴 💵 💶 💷 💸 💹 💺 💻 💼 💽 💾 💿 📀 📁 📂 📃 📄 📅 📆 📇 📈 📉 📊 📋 📌 📍 📎 📏 📐 📑 📒 📓 📔 📕 📖 📗 📘 📙 📚 📛 📜 📝 📞 📟 📠 📡 📢 📣 📤 📥 📦 📧 📨 📩 📪 📫 📬 📭 📮 📯 📰 📱 📲 📳 📴 📵 📶 📷 📹 📺 📻 📼 🔀 🔁 🔂 🔃 🔄 🔅 🔆 🔇 🔈 🔉 🔊 🔋 🔌 🔍 🔎 🔏 🔐 🔑 🔒 🔓 🔔 🔕 🔖 🔗 🔘 🔙 🔚 🔛 🔜 🔝 🔞 🔟 🔠 🔡 🔢 🔣 🔤 🔥 🔦 🔧 🔨 🔩 🔪 🔫 🔬 🔭 🔮 🔯 🔰 🔱 🔲 🔳 🔴 🔵 🔶 🔷 🔸 🔹 🔺 🔻 🔼 🔽 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧 🗻 🗼 🗽 🗾 🗿 🀄 🃏 🚀 🚁 🚂 🚃 🚄 🚅 🚆 🚇 🚈 🚉 🚊 🚋 🚌 🚍 🚎 🚏 🚐 🚑 🚒 🚓 🚔 🚕 🚖 🚗 🚘 🚙 🚚 🚛 🚜 🚝 🚞 🚟 🚠 🚡 🚢 🚣 🚤 🚥 🚦 🚧 🚨 🚩 🚪 🚫 🚬 🚭 🚮 🚯 🚰 🚱 🚲 🚳 🚴 🚵 🚶 🚷 🚸 🚹 🚺 🚻 🚼 🚽 🚾 🚿 🛀 🛁 🛂 🛃 🛄 🛅 <3 :heart: :love: © ® 🈚 🈯 🈺 🈁 🈂 🈲 🈳 🈴 🈵 🈶 🈷 🈸 🈹 🉐 🉑 🅰 🅱 🅾 🅿 🆎 🆑 🆒 🆓 🆔 🆕 🆖 🆗 🆘 🆙 🆚 🇦 🇧 🇨 🇩 🇪 🇫 🇬 🇭 🇮 🇯 🇰 🇱 🇲 🇳 🇴 🇵 🇶 🇷 🇸 🇹 🇺 🇻 🇼 🇽 🇾 🇿 🇨🇳 🇩🇪 🇪🇸 🇫🇷 🇬🇧 🇮🇹 🇯🇵 🇰🇷 🇷🇺 🇺🇸 qTox/smileys/ASCII+emojione/000077500000000000000000000000001415623743500161415ustar00rootroot00000000000000qTox/smileys/ASCII+emojione/emoticons.xml000066400000000000000000002031661415623743500206730ustar00rootroot00000000000000 :smile: 😀 :grinning: 😁 😂 :tearsofjoy: 😃 😄 :happy: 😅 😆 😇 😈 😉 😊 😋 😌 😍 😎 :cool: 😏 😐 :disappointed: 😑 😒 😓 😔 😕 😖 :oops: 😗 😘 :-* :* 😙 😚 😛 😜 😝 😞 :sad: 😟 😠 :angry: 😡 😢 :cry: 😣 😤 😥 😦 😧 😨 😩 😪 😫 😬 😭 😮 😯 😰 😱 😲 😳 :$ :blush: 😴 :sleep: :sleeping: 😵 😶 😷 😸 😹 😺 😻 😼 😽 😾 😿 🙀 🙅 🙆 🙇 🙈 🙉 🙊 :silence: 🙋 :hi: 🙌 🙍 🙎 🙏 💩 🌀 🌁 🌂 🌃 🌄 🌅 🌆 🌇 🌈 🌉 🌊 🌋 🌌 🌍 🌎 🌏 🌐 🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘 🌙 🌚 🌛 🌜 🌝 🌞 🌟 🌠 🌰 🌱 🌲 🌳 🌴 🌵 🌷 🌸 🌹 🌺 🌻 🌼 🌽 🌾 🌿 🍀 🍁 🍂 🍃 🍄 🍅 🍆 🍇 🍈 🍉 🍊 🍋 🍌 🍍 🍎 🍏 🍐 🍑 🍒 🍓 🍔 🍕 🍖 🍗 🍘 🍙 🍚 🍛 🍜 🍝 🍞 🍟 🍠 🍡 🍢 🍣 🍤 🍥 🍦 🍧 🍨 🍩 🍪 :cookie: 🍫 🍬 🍭 🍮 🍯 🍰 🍱 🍲 🍳 🍴 🍵 🍶 🍷 🍸 🍹 🍺 :beer: 🍻 :beers: 🍼 🎀 🎁 🎂 🎃 🎄 🎅 🎆 🎇 🎈 🎉 🎊 🎋 🎌 🎍 🎎 🎏 🎐 🎑 🎒 🎓 🎠 🎡 🎢 🎣 🎤 🎥 🎦 🎧 🎨 🎩 🎪 🎫 🎬 🎭 🎮 🎯 🎰 🎱 🎲 🎳 🎴 🎵 🎶 🎷 🎸 🎹 🎺 🎻 🎼 🎽 🎾 🎿 🏀 🏁 🏂 🏃 🏄 🏆 🏇 🏈 🏉 🏊 🏠 🏡 🏢 🏣 🏤 🏥 🏦 🏧 🏨 🏩 🏪 🏫 🏬 🏭 🏮 🏯 🏰 🐀 🐁 🐂 🐃 🐄 🐅 🐆 🐇 🐈 🐉 🐊 🐋 🐌 🐍 🐎 🐏 🐐 🐑 🐒 🐓 🐔 🐕 🐖 🐗 🐘 🐙 🐚 🐛 🐜 🐝 🐞 🐟 🐠 🐡 🐢 🐣 🐤 🐥 🐦 🐧 🐨 🐩 🐪 🐫 🐬 🐭 🐮 🐯 🐰 🐱 🐲 🐳 🐴 🐵 🐶 🐷 🐸 🐹 🐺 🐻 🐼 🐽 🐾 👀 👂 👃 👄 👅 🖕 :finger: :fuck: 👆 👇 👈 👉 👊 👋 👌 👍 👎 👏 👐 👑 👒 👓 👔 👕 👖 👗 👘 👙 👚 👛 👜 👝 👞 👟 👠 👡 👢 👣 👤 👥 👦 👧 👨 👩 👪 👫 👬 👭 👮 👯 👰 👱 👲 👳 👴 👵 👶 👷 👸 👹 👺 👻 👼 :angel: 👽 👾 👿 :devil: :666: 💀 :skull: 💁 💂 💃 💄 💅 💆 💇 💈 💉 :syringe: :injection: 💊 💋 💌 💍 💎 💏 💐 💑 💒 💓 💔 💕 💖 💗 💘 💙 💚 💛 💜 💝 💞 💟 💠 💡 💢 💣 :bomb: 💤 💥 💦 💧 💨 💪 💫 💬 💭 💮 💯 💰 💱 💲 💳 💴 💵 💶 💷 💸 💹 💺 💻 💼 💽 💾 💿 📀 📁 📂 📃 📄 📅 📆 📇 📈 📉 📊 📋 📌 📍 📎 📏 📐 📑 📒 📓 📔 📕 📖 📗 📘 📙 📚 📛 📜 📝 📞 📟 📠 📡 📢 📣 📤 📥 📦 📧 📨 📩 📪 📫 📬 📭 📮 📯 📰 📱 📲 📳 📴 📵 📶 📷 📹 📺 📻 📼 🔀 🔁 🔂 🔃 🔄 🔅 🔆 🔇 🔈 🔉 🔊 🔋 🔌 🔍 🔎 🔏 🔐 🔑 🔒 🔓 🔔 🔕 🔖 🔗 🔘 🔙 🔚 🔛 🔜 🔝 🔞 🔟 🔠 🔡 🔢 🔣 🔤 🔥 🔦 🔧 🔨 🔩 🔪 🔫 🔬 🔭 🔮 🔯 🔰 🔱 🔲 🔳 🔴 🔵 🔶 🔷 🔸 🔹 🔺 🔻 🔼 🔽 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧 🗻 🗼 🗽 🗾 🗿 🀄 🃏 🚀 🚁 🚂 🚃 🚄 🚅 🚆 🚇 🚈 🚉 🚊 🚋 🚌 🚍 🚎 🚏 🚐 🚑 🚒 🚓 🚔 🚕 🚖 🚗 🚘 🚙 🚚 🚛 🚜 🚝 🚞 🚟 🚠 🚡 🚢 🚣 🚤 🚥 🚦 🚧 🚨 🚩 🚪 🚫 🚬 🚭 🚮 🚯 🚰 🚱 🚲 🚳 🚴 🚵 🚶 🚷 🚸 🚹 🚺 🚻 🚼 🚽 🚾 🚿 🛀 🛁 🛂 🛃 🛄 🛅 <3 :heart: :love: © ® 🈚 🈯 🈺 🈁 🈂 🈲 🈳 🈴 🈵 🈶 🈷 🈸 🈹 🉐 🉑 🅰 🅱 🅾 🅿 🆎 🆑 🆒 🆓 🆔 🆕 🆖 🆗 🆘 🆙 🆚 🇦 🇧 🇨 🇩 🇪 🇫 🇬 🇭 🇮 🇯 🇰 🇱 🇲 🇳 🇴 🇵 🇶 🇷 🇸 🇹 🇺 🇻 🇼 🇽 🇾 🇿 🇨🇳 🇩🇪 🇪🇸 🇫🇷 🇬🇧 🇮🇹 🇯🇵 🇰🇷 🇷🇺 🇺🇸 qTox/smileys/Classic/000077500000000000000000000000001415623743500150715ustar00rootroot00000000000000qTox/smileys/Classic/angry.png000066400000000000000000000026651415623743500167300ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CjIDATH]HWwu4+胵yI$<RRKHX4>X04TZ(I61)җ4|6*HQΎ{:a=={9BJ[!^` LSM)k[ !BM.< =0%2}k]qz礔5,()7#R@R ?A8O0)gX7ٔ/n`(b.3fJe A#oGe2:类Y7lH,{ )8x5\ #g$/'z$j݇yC^!ē=!Zj){̈́|l4Ƅ!7OѝMGp[JRtBdw[BEngؓe&[~U @3gU#<'-\Jw$ ^Po*=у³B vBqOγo?9Bq|r!`0TubL."NIވq[.h Q+60%|/YJ$Զxe|>w`h>}_Wb\LRN@}5Ɋ+W|i$@VLK8&fXK8p58XƹKװLHLyQ. ag7\⇈5# lۖxp$wvc3XzÃS{+OfC/_͟0kEK^]iٍu٤K zk(;ߎx 80c岼M 4]Lh'S >2kXPGv8rAJhU fxhJꪰ_BZma!22}k]W7-zlgfSE7mj<]Oacu?IENDB`qTox/smileys/Classic/cool.png000066400000000000000000000030331415623743500165320ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CIDATHklU;;a`Xb+P>4 hEZ&jH0`$5c$&JLEŤXBPjۥ>lIN2sϹ̙;gɊ9J.m&N`!OW5l JXhdZu`8BesHJ?%B4t R28rSD-=(I~% ?؅wim␶ lo$|p,Xins֢;d7a5~LduR~M㈩1f汓w "mRJpk3XysBHEQ.K !$QUUiJ'B {MZ-KQWpChkǃ80 |>]qdeeM[PP$zIBMմT !|3<\>wKaP !CrmV)=_PraI'J8 ߃ȗRʎ ީpύ;BB/o`'^ ݕٿWXZUhd_TltU0 &Si:Ԏ/w2iwو5PlAyqX;W/@QysT p_Pʷ,!_JXi|@p0ݱ`ԌaP`ץ@i (SE F !`+=9S>ow^VΌ(xa}(?q**X[^`2xa ԝ24(Ă~z$2j@`^.bfIdz "2)]Yo&~B__tۡzūr5ke eR RJlҒoSsoco9:!o۴뗮u|c!aԠ?^qĖp_uPWߌߏ,2q,q `߫7cu?V61}^>->EW"~I{ >?n v~3`tM ~J$Zxճp CMDn2$l@vOtR˅xG3i3tR?]'rdwJܔI}NNXюcȼ|/у:΀R!{byEaaAYS\@Wdl_H;ǹ +.Ky;tˤis9wBFC#FT3ܱL=7lst@ʸѝ.x.}^/o#|A?h 3 ۣ_R+IENDB`qTox/smileys/Classic/emoticons.xml000066400000000000000000000033141415623743500176140ustar00rootroot00000000000000 :) :-) 😊 8-) 8) 😎 :O :-O 😲 :p :P 😋 :/ :-/ 😕 ;) ;-) 😉 :( :-( 😖 ;( ;-( 😢 :D :-D 😃 :| :-| 😐 ;D ;-D 😄 :@ 😠 D: 😨 xD XD 😆 qTox/smileys/Classic/happy.png000066400000000000000000000025311415623743500167210ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CIDATH}H]u?9'=re+oQCG Gs#B90-1]6 Xe a6#\A#W=r57_q;w~vF*AX)͊lu)+0\o7bg7Kr[\l|; &|l]*2\ե,(BTu).v6 *=wy#pfT`_ȦDT8pk4g v0耋)R}^"LϰYD&5S#t큂Uz=W[RԩR,Jʊ"%.+']hwcʊ\%Rm;eF %΀͑<h1y`;(:ш'kM5`wL$'o1!?k5=:4 +AuCF(-X]'1ʑKsF(0È a ~8P06ޅ kT0,|5/`zfѶ>LK1Sp,)gęÑIE{g/:^|47 |v('jz;6|S>!xd :㵁~Yp߃ϿCp_¢ AD0 ^_m 5ˢo<!Ԕj]ˢt"/ $LMDj_F~lD_"ې?E,Ccc¡̹,z# X[|}GX:4 yqܱphK~+qu sM{d𚯹[v |_(i?mCƭjIENDB`qTox/smileys/Classic/laugh.png000066400000000000000000000025321415623743500167010ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CIDATHoLU?gxgg1n[ ]bS^i-52իM\m,uˁ#mKXjlXJeI6Oͬ!]rv^~=GR*R4T~`^DuR]mӠeeϚӈcf//U"24/RnW8{q!ƳO"[@>?^F3͌{S#"+M'.̌3.JnAY%x!DROq\rq}nPUSYǐ5F%&X)l,ݵ I(OL|jǔRvӠɵÂڌra4)`9Nty` ztBGp 3G}wҶGᏻ J!k'8^5*RD]cm K>^ۊ@p=dqw82!{1k@ŦAQq!Fxch C-p 9pf'7 a&?B Ӡ@ɦAe⣙å+q]>Lp~}K ~8 /=%q[ۀ\'X4 PuYA( ul@ʚK:i ii1륿хv1ү7{#P̡?Q;OI/vvu{e:>w !SAi?$-_,oߖjCI-ĹZN!nL9ۢk2ޫPJydggc vR\YF,E=j``vCWi,ѐu>yIgpHu/-4P>?]4h3sSS}#9g[~#ǧ*MkJ U!I WK+˅*A"oC%$o֭ϝ"4!i"uy}E[3쎋̫ KAhfM|ioXWI<OwxUIENDB`qTox/smileys/Classic/laugh_closed_eyes.png000066400000000000000000000026461415623743500212650ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'C[IDATHLUe?05Zdc(Wd gRktؘ XcY-]kj-L$a9VT)KLuuatpƅE=y"R*xi$X=AD.ER.FiﳘX:yM&]~<ݮR)"}S+t]g]㽼l\%A-F~h< U5xggih="21X)he3mΤA(ކS{,q`b#c6aw+mKwgD JD#*ʘjf)*O{>1nԕUPU+PL:>L'fNg>:D> Exx5׮3[Dlvl`vwtw/WW is /ݮQ#6 s1*|X^!+<#Ëo\'N04P@i~8pa`MxWhl@_P^ ^3~~`YV+J`:/Aŗ`ۂ(x76CB<\3{Q MMx{8afΛ5$"y.b?5t]kW±Xon~ ki$`9VO{tM`LjIF+eU[7EwB;@ڐzGX7\~ <ʹ9cs !řJDe?0#N:;;eZ|64# H3"R]UCٓꕑo%/M_`|w s!U5x=^F7u9\={u&lCRSx8~!M{ !*ݮ6fe~7fpN6Q'[wpI=yg4L'y<M[ T; XX.-˒Fy\IKqJwRĠipGy>\Gbڤ{i)`|&n}p2l8} qmoo෿f#nO?\IENDB`qTox/smileys/Classic/plain.png000066400000000000000000000022641415623743500167060ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CiIDATH͗OHW?qfa[eQUM ?-PĞ=Hs൵CrHm)%4bPa/vٌۙ׃Fi6K0oORV"ZJ] YրR L~ۢ4pkO`TSBf/N20(R ,"ag|jm2--}Ʀpn>{J)` 3M1_ `G]<-$0yA[V9tڋy4^ %pI9ڔRH*~b#ir{NSgc"b3݄t&d[̈6 bDwyt)F00W'J)D$tw堃 ¹7 /± V-$dJ`w1[E_݅7P|{+>q SKm[tt`uxOXZ.}t7-4 >ֆ3BMv:;ww im'""ahiiTi{\H}͆{9;+-PY{b mg?Xj Csʲ*5]*Kju)K}VVȔ BYՀGgYkm~пX܂Wf#ՔRN m`$R:@ablf'7G4C{K~Na)1dܣ?]HB.#`ѹF>sg*WVJyiqRli8q{> Ժ1no=4?v S`E srqIENDB`qTox/smileys/Classic/sad.png000066400000000000000000000024261415623743500163520ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CIDATH͗]H\GsֽnאP~hnbWk R"-HͫI=!!b/B(ZB h4`ֶz]=}X7~ݍm?0s̝9WR TY anԦc= :n;B^E 3?tq#iCtΉ̿+ à4@K?݄eю"Edq`mS wQ^I?Kǻ< xi J}V:}mxN7c3z}| efBajD`JΥ8v,И|4Ɵ0{׾SJnF<[8vֈmѣ2AKyNN7cꔈdxtK26҂ 7`wF ~_|_sȌ`b8A#P5&^}Gmv#(k1LVXjECSS[x<4v-ͫ"@7GlK.+ E9;OP9L_ly6m]וt:}[Ԕ<~<ɔiY~8;ˏg.^%u+F[]8!34,-_H^Yvt/Sh:I+{c𧹂Fp+!.\9\iNT~ʋ:9G,]O}d:yf]̜.\*y8GT SM`,w=l;CA?GjAxe/S\dYv4M:,M `u͔]d .8}@q<;ww_@ iZb5,p /ObYLv{” bҦ5|{/2bк5IENDB`qTox/smileys/Classic/smile.png000066400000000000000000000025331415623743500167130ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CIDATH[Le2ú6hI@Xp3b#^R7*il 7iڤSMHLKJ4 ֆbKV.ZՔKfu~s9ߙQ"Rr@`Y"}= r5ʝEEBVfzIDt1ދy6vFⴈ u2'%tv@c T2 *Edb`iPF8S y)F~M爏`X)ňb\'J/9WֺhRH|YJ#w|q6S+UZm"0⣦clܽ‡}N-D+׋J<9uF|"EBvu~/7<DF.wPiYW$D]H^óCc]G>*Ƅ礩={Jbcڗ 4!y :y`@w,e|.~"0؃ߐ(Vdด'X4J99hZȞ j&83;&3]I@BkMawy4؃Zx3mRJܾentׂg@S7@)TW>[B6URʘwAwvvvyCG.CkP*\p:t~H-ߠmk?z-u|l?wnt(Bjmpwn|j*OA]W^pim!$@Ks˧PS=R)x{s5fEkt8~4ab+JE/j*)QU*<_㰱NXuÆ l.a{qɲP}RkSA_Pݫ;1x19~i=( c;nRH)tBa=|=عUR- n216_0~%>Ia)̸La,R4/\w}^B%΀1\_xG oî'}V}` ̔m32(WcCFYm>H2`)79'69g[p0?~.NN##RM_㼧foy-YPxoZP^v (/GoSHQIENDB`qTox/smileys/Classic/tongue.png000066400000000000000000000024321415623743500171010ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CIDATH]LUwvf\ZM6 t)Hʇ41mhL*i$Ӡ4 lFM(2;p}]`\?Ι{)%JjR`Y,Rʕ}XU=EqM(zI!nEb ?L*HAӢWJBMhUm5cUKw00DqjDcT^vƄ` Â\h1Bh`M:HSW zufjLH)Bxq҆ S΀p(puNrMUBhv-8v BWL3& ^[3ߗCCMWid=v܃ ܷ_jg7i4(BiQ\Weo|)()L<("{KwӢXSښ K!إwr֋tsSj#?RңW#:;aT:CSJRsBܪJÖK, 8H̲XR\e<ߒ?, fR.WR n9~H*\9/£T=n0=xl InnAU9 UR ep`γ'wvW(fvx&!(2M#ħΠz1ݦِ0t-Q6XJfZ\m!-X|'[po |m=DLT]N8}cSwgWo #`z}gl7'cS\K :?Uu NU8>nໟj?Ҭ+y{ ~r 07Mb !IENDB`qTox/smileys/Classic/uncertain.png000066400000000000000000000024311415623743500175670ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CIDATH[LUg6ƕK7Yո-&bbj_6 I>x!#)e?Ѣ[_¨<9"N@aR͉T.)e*k5 Fbp,Lr,Td !vδ7Rь1juvc{IR B Wff?l~_yv|Bh^F ~D{#^!!iT :*H@x5LH)Blgzs8?OTN¦|m/7.L(Jk}ta= uFwhiuʟέm:4]kpJ83DgWigoo Y瘦נ@B,msG8}X9ow;Lcj+ !|*PSSw SǓM- 5b _F J֯tػgYhU*E ?*-ƣlf6:|mYu'af(|u|%֜|^?#uyxD{!d%T>"l}I"*щ졗~mD=Y.7aP}*1)e\H `fCu(y ^?7!0r~ `&M Cb #R/d9ERʄesXz/_@[ƠBMG.S*EXn,| ۶#vvcEJ$l,L%M{Ih=GơEx9%d_ƠY_Aܳ]3LVIENDB`qTox/smileys/Classic/wink.png000066400000000000000000000023471415623743500165550ustar00rootroot00000000000000PNG  IHDR;0sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitleqToxѓTtEXtDescriptionCreated for the qTox project. Inspired by the "Never mind!" smiley pack. FtEXtCopyrightCC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/'CIDATH͗ML\Uw|>_֪!|hkjeԘ@v6E H(IS7R#1,j4!iaM#ZH,8bj)r~sZ)J:L(p00""ӫu+RmhY&ͶCϋ]SQ]A $+װ]04B8}CDVJ!C=p+0O O&TD+J-Z/ΪnJBb/1l#+6K8hǸ=d:{|A4^D.+J.փQ.O{ʗNL)[&8hm (s֋gۛO~Z/] p:dzXkuixy{ Fr{XFRʽ64ښ ޙ^b\s YM F2inm<Fڜ}}65l6bZ& (L&"Cl*f"p5 ŝnS`v 7`qu>/v!(=] w.vC_g.RY4r7RV2Q J4l+05>IXIÔ _],p^U8m2ID p0p6 ]~ kA !v'z> (.U |s&3A:tʴO  er/ ESԫCKU9Ec.2ԋr(Gّh?G^tyK`@LϏ l+KV6+>q23o}g6 ?D>{Ý*} xY($6/ o`,OIENDB`qTox/smileys/Universe/000077500000000000000000000000001415623743500153105ustar00rootroot00000000000000qTox/smileys/Universe/1f004.svg000066400000000000000000000055551415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f0cf.svg000066400000000000000000000076601415623743500167410ustar00rootroot00000000000000 qTox/smileys/Universe/1f170.svg000066400000000000000000000024451415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f171.svg000066400000000000000000000025321415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f17e.svg000066400000000000000000000022521415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f17f.svg000066400000000000000000000022571415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f18e.svg000066400000000000000000000035661415623743500166670ustar00rootroot00000000000000 qTox/smileys/Universe/1f191.svg000066400000000000000000000031141415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f192.svg000066400000000000000000000046541415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f193.svg000066400000000000000000000054661415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f194.svg000066400000000000000000000027301415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f195.svg000066400000000000000000000047001415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f196.svg000066400000000000000000000034331415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f197.svg000066400000000000000000000033611415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f198.svg000066400000000000000000000042661415623743500166110ustar00rootroot00000000000000 qTox/smileys/Universe/1f199.svg000066400000000000000000000041471415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f19a.svg000066400000000000000000000032611415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f1e6.svg000066400000000000000000000024451415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f1e7.svg000066400000000000000000000025321415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f1e8-1f1f3.svg000066400000000000000000000040121415623743500174700ustar00rootroot00000000000000 qTox/smileys/Universe/1f1e8.svg000066400000000000000000000023441415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f1e9-1f1ea.svg000066400000000000000000000020071415623743500175500ustar00rootroot00000000000000 qTox/smileys/Universe/1f1e9.svg000066400000000000000000000022471415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ea-1f1f8.svg000066400000000000000000000047161415623743500175610ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ea.svg000066400000000000000000000023671415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f1eb-1f1f7.svg000066400000000000000000000020161415623743500175500ustar00rootroot00000000000000 qTox/smileys/Universe/1f1eb.svg000066400000000000000000000023051415623743500167270ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ec-1f1e7.svg000066400000000000000000000101111415623743500175430ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ec.svg000066400000000000000000000024721415623743500167350ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ed.svg000066400000000000000000000022401415623743500167270ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ee-1f1f9.svg000066400000000000000000000020161415623743500175550ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ee.svg000066400000000000000000000020451415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ef-1f1f5.svg000066400000000000000000000017321415623743500175560ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ef.svg000066400000000000000000000022071415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f0-1f1f7.svg000066400000000000000000000120271415623743500174720ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f0.svg000066400000000000000000000024761415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f1.svg000066400000000000000000000021341415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f2.svg000066400000000000000000000026751415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f3.svg000066400000000000000000000023671415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f4.svg000066400000000000000000000022531415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f5.svg000066400000000000000000000023171415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f6.svg000066400000000000000000000030111415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f7-1f1fa.svg000066400000000000000000000020151415623743500175470ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f7.svg000066400000000000000000000025311415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f8.svg000066400000000000000000000024371415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f1f9.svg000066400000000000000000000022061415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f1fa-1f1f8.svg000066400000000000000000000076071415623743500175640ustar00rootroot00000000000000 qTox/smileys/Universe/1f1fa.svg000066400000000000000000000022341415623743500167300ustar00rootroot00000000000000 qTox/smileys/Universe/1f1fb.svg000066400000000000000000000023361415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/1f1fc.svg000066400000000000000000000027051415623743500167350ustar00rootroot00000000000000 qTox/smileys/Universe/1f1fd.svg000066400000000000000000000026331415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f1fe.svg000066400000000000000000000023611415623743500167350ustar00rootroot00000000000000 qTox/smileys/Universe/1f1ff.svg000066400000000000000000000023331415623743500167350ustar00rootroot00000000000000 qTox/smileys/Universe/1f201.svg000066400000000000000000000033161415623743500165650ustar00rootroot00000000000000 qTox/smileys/Universe/1f202.svg000066400000000000000000000032101415623743500165570ustar00rootroot00000000000000 qTox/smileys/Universe/1f21a.svg000066400000000000000000000067331415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f22f.svg000066400000000000000000000056721415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f232.svg000066400000000000000000000077431415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f233.svg000066400000000000000000000045011415623743500165670ustar00rootroot00000000000000 qTox/smileys/Universe/1f234.svg000066400000000000000000000040361415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f235.svg000066400000000000000000000067261415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f236.svg000066400000000000000000000042421415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f237.svg000066400000000000000000000033151415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f238.svg000066400000000000000000000031371415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f239.svg000066400000000000000000000055771415623743500166130ustar00rootroot00000000000000 qTox/smileys/Universe/1f23a.svg000066400000000000000000000053671415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f250.svg000066400000000000000000000064611415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f251.svg000066400000000000000000000033211415623743500165660ustar00rootroot00000000000000 qTox/smileys/Universe/1f300.svg000066400000000000000000000037501415623743500165670ustar00rootroot00000000000000 qTox/smileys/Universe/1f301.svg000066400000000000000000000115661415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f302.svg000066400000000000000000000041221415623743500165630ustar00rootroot00000000000000 qTox/smileys/Universe/1f303.svg000066400000000000000000000120101415623743500165570ustar00rootroot00000000000000 qTox/smileys/Universe/1f304.svg000066400000000000000000000067031415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f305.svg000066400000000000000000000110521415623743500165660ustar00rootroot00000000000000 qTox/smileys/Universe/1f306.svg000066400000000000000000000060771415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f307.svg000066400000000000000000000123221415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f308.svg000066400000000000000000000056021415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f309.svg000066400000000000000000000111401415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f30a.svg000066400000000000000000000025701415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f30b.svg000066400000000000000000000062011415623743500166430ustar00rootroot00000000000000 qTox/smileys/Universe/1f30c.svg000066400000000000000000000215211415623743500166460ustar00rootroot00000000000000 qTox/smileys/Universe/1f30d.svg000066400000000000000000000052151415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f30e.svg000066400000000000000000000060401415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f30f.svg000066400000000000000000000116401415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f310.svg000066400000000000000000000041111415623743500165600ustar00rootroot00000000000000 qTox/smileys/Universe/1f311.svg000066400000000000000000000054331415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f312.svg000066400000000000000000000061251415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f313.svg000066400000000000000000000056301415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f314.svg000066400000000000000000000061451415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f315.svg000066400000000000000000000054331415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f316.svg000066400000000000000000000061621415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f317.svg000066400000000000000000000056661415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f318.svg000066400000000000000000000061071415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f319.svg000066400000000000000000000031061415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f31a.svg000066400000000000000000000063511415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f31b.svg000066400000000000000000000043521415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f31c.svg000066400000000000000000000043721415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f31d.svg000066400000000000000000000063551415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f31e.svg000066400000000000000000000070241415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f31f.svg000066400000000000000000000051171415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f320.svg000066400000000000000000000035241415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f330.svg000066400000000000000000000046701415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f331.svg000066400000000000000000000021251415623743500165660ustar00rootroot00000000000000 qTox/smileys/Universe/1f332.svg000066400000000000000000000066071415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f333.svg000066400000000000000000000101661415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f334.svg000066400000000000000000000103211415623743500165660ustar00rootroot00000000000000 qTox/smileys/Universe/1f335.svg000066400000000000000000000103511415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f337.svg000066400000000000000000000026711415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f338.svg000066400000000000000000000162241415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f339.svg000066400000000000000000000033321415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f33a.svg000066400000000000000000000072331415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f33b.svg000066400000000000000000000051141415623743500166500ustar00rootroot00000000000000 qTox/smileys/Universe/1f33c.svg000066400000000000000000000077251415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f33d.svg000066400000000000000000000137711415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f33e.svg000066400000000000000000000055241415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f33f.svg000066400000000000000000000130101415623743500166460ustar00rootroot00000000000000 qTox/smileys/Universe/1f340.svg000066400000000000000000000032611415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f341.svg000066400000000000000000000027271415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f342.svg000066400000000000000000000055741415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f343.svg000066400000000000000000000056511415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f344.svg000066400000000000000000000052531415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f345.svg000066400000000000000000000026651415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f346.svg000066400000000000000000000022221415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f347.svg000066400000000000000000000047031415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f348.svg000066400000000000000000000046701415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f349.svg000066400000000000000000000070661415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f34a.svg000066400000000000000000000027031415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f34b.svg000066400000000000000000000040761415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f34c.svg000066400000000000000000000060061415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f34d.svg000066400000000000000000000063541415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f34e.svg000066400000000000000000000023731415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f34f.svg000066400000000000000000000024761415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f350.svg000066400000000000000000000023271415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f351.svg000066400000000000000000000035051415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f352.svg000066400000000000000000000034131415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f353.svg000066400000000000000000000111601415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f354.svg000066400000000000000000000077201415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f355.svg000066400000000000000000000055421415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f356.svg000066400000000000000000000041571415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f357.svg000066400000000000000000000025031415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f358.svg000066400000000000000000000066601415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f359.svg000066400000000000000000000070021415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f35a.svg000066400000000000000000000077451415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f35b.svg000066400000000000000000000101511415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f35c.svg000066400000000000000000000147641415623743500166660ustar00rootroot00000000000000 qTox/smileys/Universe/1f35d.svg000066400000000000000000000134101415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f35e.svg000066400000000000000000000023161415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f35f.svg000066400000000000000000000155341415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f360.svg000066400000000000000000000042511415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f361.svg000066400000000000000000000037271415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f362.svg000066400000000000000000000040441415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f363.svg000066400000000000000000000144021415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f364.svg000066400000000000000000000106331415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f365.svg000066400000000000000000000064251415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f366.svg000066400000000000000000000135441415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f367.svg000066400000000000000000000071051415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f368.svg000066400000000000000000000066601415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f369.svg000066400000000000000000000131431415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f36a.svg000066400000000000000000000060201415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f36b.svg000066400000000000000000000060601415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f36c.svg000066400000000000000000000114071415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f36d.svg000066400000000000000000000103321415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f36e.svg000066400000000000000000000040031415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f36f.svg000066400000000000000000000055461415623743500166700ustar00rootroot00000000000000 qTox/smileys/Universe/1f370.svg000066400000000000000000000074351415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f371.svg000066400000000000000000000156121415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f372.svg000066400000000000000000000074151415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f373.svg000066400000000000000000000033431415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f374.svg000066400000000000000000000031641415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f375.svg000066400000000000000000000034321415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f376.svg000066400000000000000000000116201415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f377.svg000066400000000000000000000046461415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f378.svg000066400000000000000000000053571415623743500166130ustar00rootroot00000000000000 qTox/smileys/Universe/1f379.svg000066400000000000000000000133271415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f37a.svg000066400000000000000000000070711415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f37b.svg000066400000000000000000000161151415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f37c.svg000066400000000000000000000050661415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f380.svg000066400000000000000000000073411415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f381.svg000066400000000000000000000035161415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f382.svg000066400000000000000000000075131415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f383.svg000066400000000000000000000051351415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f384.svg000066400000000000000000000130141415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f385.svg000066400000000000000000000074071415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f386.svg000066400000000000000000000105131415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f387.svg000066400000000000000000000155041415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f388.svg000066400000000000000000000032001415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f389.svg000066400000000000000000000145401415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f38a.svg000066400000000000000000000136361415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f38b.svg000066400000000000000000000140001415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f38c.svg000066400000000000000000000047021415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f38d.svg000066400000000000000000000101271415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f38e.svg000066400000000000000000000172171415623743500166670ustar00rootroot00000000000000 qTox/smileys/Universe/1f38f.svg000066400000000000000000000105111415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f390.svg000066400000000000000000000044651415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f391.svg000066400000000000000000000131641415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f392.svg000066400000000000000000000112761415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f393.svg000066400000000000000000000052421415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a0.svg000066400000000000000000000056431415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a1.svg000066400000000000000000000131351415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a2.svg000066400000000000000000000057721415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a3.svg000066400000000000000000000074071415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a4.svg000066400000000000000000000042031415623743500166460ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a5.svg000066400000000000000000000062441415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a6.svg000066400000000000000000000027631415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a7.svg000066400000000000000000000043431415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a8.svg000066400000000000000000000037111415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f3a9.svg000066400000000000000000000044171415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f3aa.svg000066400000000000000000000153731415623743500167350ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ab.svg000066400000000000000000000056461415623743500167400ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ac.svg000066400000000000000000000032571415623743500167350ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ad.svg000066400000000000000000000064401415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ae.svg000066400000000000000000000100001415623743500167170ustar00rootroot00000000000000 qTox/smileys/Universe/1f3af.svg000066400000000000000000000051461415623743500167370ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b0.svg000066400000000000000000000114241415623743500166460ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b1.svg000066400000000000000000000032241415623743500166460ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b2.svg000066400000000000000000000064011415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b3.svg000066400000000000000000000073141415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b4.svg000066400000000000000000000060611415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b5.svg000066400000000000000000000017641415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b6.svg000066400000000000000000000035611415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b7.svg000066400000000000000000000043041415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b8.svg000066400000000000000000000052201415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f3b9.svg000066400000000000000000000040601415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ba.svg000066400000000000000000000074001415623743500167260ustar00rootroot00000000000000 qTox/smileys/Universe/1f3bb.svg000066400000000000000000000073211415623743500167310ustar00rootroot00000000000000 qTox/smileys/Universe/1f3bc.svg000066400000000000000000000076351415623743500167420ustar00rootroot00000000000000 qTox/smileys/Universe/1f3bd.svg000066400000000000000000000044151415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/1f3be.svg000066400000000000000000000041061415623743500167320ustar00rootroot00000000000000 qTox/smileys/Universe/1f3bf.svg000066400000000000000000000144461415623743500167430ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c0.svg000066400000000000000000000035541415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c1.svg000066400000000000000000000032471415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c2.svg000066400000000000000000000131611415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c3.svg000066400000000000000000000076031415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c4.svg000066400000000000000000000077361415623743500166660ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c6.svg000066400000000000000000000050341415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c7.svg000066400000000000000000000165061415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c8.svg000066400000000000000000000060271415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f3c9.svg000066400000000000000000000034011415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ca.svg000066400000000000000000000045521415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e0.svg000066400000000000000000000042701415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e1.svg000066400000000000000000000066751415623743500166660ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e2.svg000066400000000000000000000065321415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e3.svg000066400000000000000000000045031415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e4.svg000066400000000000000000000053311415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e5.svg000066400000000000000000000052321415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e6.svg000066400000000000000000000063251415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e7.svg000066400000000000000000000047641415623743500166710ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e8.svg000066400000000000000000000055051415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f3e9.svg000066400000000000000000000060311415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ea.svg000066400000000000000000000104251415623743500167320ustar00rootroot00000000000000 qTox/smileys/Universe/1f3eb.svg000066400000000000000000000063261415623743500167400ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ec.svg000066400000000000000000000070611415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ed.svg000066400000000000000000000110031415623743500167260ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ee.svg000066400000000000000000000052021415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/1f3ef.svg000066400000000000000000000044141415623743500167400ustar00rootroot00000000000000 qTox/smileys/Universe/1f3f0.svg000066400000000000000000000073041415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f400.svg000066400000000000000000000060201415623743500165610ustar00rootroot00000000000000 qTox/smileys/Universe/1f401.svg000066400000000000000000000045351415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f402.svg000066400000000000000000000030511415623743500165640ustar00rootroot00000000000000 qTox/smileys/Universe/1f403.svg000066400000000000000000000033711415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f404.svg000066400000000000000000000053401415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f405.svg000066400000000000000000000063771415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f406.svg000066400000000000000000000131071415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f407.svg000066400000000000000000000054551415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f408.svg000066400000000000000000000051351415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f409.svg000066400000000000000000000137161415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f40a.svg000066400000000000000000000067151415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f40b.svg000066400000000000000000000031101415623743500166400ustar00rootroot00000000000000 qTox/smileys/Universe/1f40c.svg000066400000000000000000000047261415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f40d.svg000066400000000000000000000036461415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f40e.svg000066400000000000000000000062041415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f40f.svg000066400000000000000000000056061415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f410.svg000066400000000000000000000044171415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f411.svg000066400000000000000000000041741415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f412.svg000066400000000000000000000057071415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f413.svg000066400000000000000000000050241415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f414.svg000066400000000000000000000053111415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f415.svg000066400000000000000000000054521415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f416.svg000066400000000000000000000033041415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f417.svg000066400000000000000000000075611415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f418.svg000066400000000000000000000037141415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f419.svg000066400000000000000000000044401415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f41a.svg000066400000000000000000000073051415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f41b.svg000066400000000000000000000130051415623743500166450ustar00rootroot00000000000000 qTox/smileys/Universe/1f41c.svg000066400000000000000000000073161415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f41d.svg000066400000000000000000000100341415623743500166460ustar00rootroot00000000000000 qTox/smileys/Universe/1f41e.svg000066400000000000000000000121601415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f41f.svg000066400000000000000000000051521415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f420.svg000066400000000000000000000064671415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f421.svg000066400000000000000000000074061415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f422.svg000066400000000000000000000055461415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f423.svg000066400000000000000000000044461415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f424.svg000066400000000000000000000054001415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f425.svg000066400000000000000000000054711415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f426.svg000066400000000000000000000051761415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f427.svg000066400000000000000000000044521415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f428.svg000066400000000000000000000046761415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f429.svg000066400000000000000000000065201415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f42a.svg000066400000000000000000000042531415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f42b.svg000066400000000000000000000046021415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f42c.svg000066400000000000000000000027351415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f42d.svg000066400000000000000000000077261415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f42e.svg000066400000000000000000000053031415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f42f.svg000066400000000000000000000120721415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f430.svg000066400000000000000000000055371415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f431.svg000066400000000000000000000126621415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f432.svg000066400000000000000000000072361415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f433.svg000066400000000000000000000043021415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f434.svg000066400000000000000000000075041415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f435.svg000066400000000000000000000062071415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f436.svg000066400000000000000000000043101415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f437.svg000066400000000000000000000054251415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f438.svg000066400000000000000000000052671415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f439.svg000066400000000000000000000122361415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f43a.svg000066400000000000000000000071431415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f43b.svg000066400000000000000000000055241415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f43c.svg000066400000000000000000000067521415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f43d.svg000066400000000000000000000022721415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f43e.svg000066400000000000000000000065541415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f440.svg000066400000000000000000000026431415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f442.svg000066400000000000000000000041241415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f443.svg000066400000000000000000000047741415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f444.svg000066400000000000000000000027301415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f445.svg000066400000000000000000000022721415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f446.svg000066400000000000000000000022551415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f447.svg000066400000000000000000000022341415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f448.svg000066400000000000000000000022331415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f449.svg000066400000000000000000000022301415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f44a.svg000066400000000000000000000050761415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f44b.svg000066400000000000000000000044651415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f44c.svg000066400000000000000000000052261415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f44d.svg000066400000000000000000000051121415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f44e.svg000066400000000000000000000050021415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f44f.svg000066400000000000000000000063661415623743500166700ustar00rootroot00000000000000 qTox/smileys/Universe/1f450.svg000066400000000000000000000050321415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f451.svg000066400000000000000000000064471415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f452.svg000066400000000000000000000067101415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f453.svg000066400000000000000000000046301415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f454.svg000066400000000000000000000045711415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f455.svg000066400000000000000000000037361415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f456.svg000066400000000000000000000025461415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f457.svg000066400000000000000000000030741415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f458.svg000066400000000000000000000042301415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f459.svg000066400000000000000000000034231415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f45a.svg000066400000000000000000000036041415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f45b.svg000066400000000000000000000041701415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f45c.svg000066400000000000000000000030261415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f45d.svg000066400000000000000000000027751415623743500166670ustar00rootroot00000000000000 qTox/smileys/Universe/1f45e.svg000066400000000000000000000036161415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f45f.svg000066400000000000000000000036601415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f460.svg000066400000000000000000000025351415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f461.svg000066400000000000000000000030311415623743500165670ustar00rootroot00000000000000 qTox/smileys/Universe/1f462.svg000066400000000000000000000032771415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f463.svg000066400000000000000000000101041415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f464.svg000066400000000000000000000023611415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f465.svg000066400000000000000000000037551415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f466.svg000066400000000000000000000053401415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f467.svg000066400000000000000000000075511415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f468.svg000066400000000000000000000065371415623743500166140ustar00rootroot00000000000000 qTox/smileys/Universe/1f469.svg000066400000000000000000000047711415623743500166130ustar00rootroot00000000000000 qTox/smileys/Universe/1f46a.svg000066400000000000000000000215341415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f46b.svg000066400000000000000000000175001415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f46c.svg000066400000000000000000000165051415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f46d.svg000066400000000000000000000176211415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f46e.svg000066400000000000000000000076521415623743500166700ustar00rootroot00000000000000 qTox/smileys/Universe/1f46f.svg000066400000000000000000000237711415623743500166710ustar00rootroot00000000000000 qTox/smileys/Universe/1f470.svg000066400000000000000000000073271415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f471.svg000066400000000000000000000045071415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f472.svg000066400000000000000000000102221415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f473.svg000066400000000000000000000072501415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f474.svg000066400000000000000000000061411415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f475.svg000066400000000000000000000067431415623743500166110ustar00rootroot00000000000000 qTox/smileys/Universe/1f476.svg000066400000000000000000000046401415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f477.svg000066400000000000000000000073601415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f478.svg000066400000000000000000000065751415623743500166170ustar00rootroot00000000000000 qTox/smileys/Universe/1f479.svg000066400000000000000000000130671415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f47a.svg000066400000000000000000000075261415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f47b.svg000066400000000000000000000037721415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f47c.svg000066400000000000000000000100411415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f47d.svg000066400000000000000000000032751415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f47e.svg000066400000000000000000000021351415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f47f.svg000066400000000000000000000176711415623743500166740ustar00rootroot00000000000000 image/svg+xmlqTox/smileys/Universe/1f480.svg000066400000000000000000000036271415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f481.svg000066400000000000000000000066271415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f482.svg000066400000000000000000000042421415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f483.svg000066400000000000000000000073661415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f484.svg000066400000000000000000000034751415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f485.svg000066400000000000000000000051421415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f486.svg000066400000000000000000000077501415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f487.svg000066400000000000000000000104001415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f488.svg000066400000000000000000000054011415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f489.svg000066400000000000000000000070141415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f48a.svg000066400000000000000000000021041415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f48b.svg000066400000000000000000000026341415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f48c.svg000066400000000000000000000046371415623743500166700ustar00rootroot00000000000000 qTox/smileys/Universe/1f48d.svg000066400000000000000000000033201415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f48e.svg000066400000000000000000000024111415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f48f.svg000066400000000000000000000125471415623743500166720ustar00rootroot00000000000000 qTox/smileys/Universe/1f490.svg000066400000000000000000000104361415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f491.svg000066400000000000000000000120141415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f492.svg000066400000000000000000000065701415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f493.svg000066400000000000000000000046701415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f494.svg000066400000000000000000000024771415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f495.svg000066400000000000000000000025211415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f496.svg000066400000000000000000000037401415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f497.svg000066400000000000000000000034101415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f498.svg000066400000000000000000000064511415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f499.svg000066400000000000000000000016341415623743500166110ustar00rootroot00000000000000 qTox/smileys/Universe/1f49a.svg000066400000000000000000000016341415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f49b.svg000066400000000000000000000016341415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f49c.svg000066400000000000000000000016341415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f49d.svg000066400000000000000000000035441415623743500166660ustar00rootroot00000000000000 qTox/smileys/Universe/1f49e.svg000066400000000000000000000036761415623743500166750ustar00rootroot00000000000000 qTox/smileys/Universe/1f49f.svg000066400000000000000000000022421415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a0.svg000066400000000000000000000112271415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a1.svg000066400000000000000000000045331415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a2.svg000066400000000000000000000037141415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a3.svg000066400000000000000000000030401415623743500166440ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a4.svg000066400000000000000000000031121415623743500166450ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a5.svg000066400000000000000000000031211415623743500166460ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a6.svg000066400000000000000000000025041415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a7.svg000066400000000000000000000014161415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a8.svg000066400000000000000000000023551415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f4a9.svg000066400000000000000000000054651415623743500166670ustar00rootroot00000000000000 qTox/smileys/Universe/1f4aa.svg000066400000000000000000000025111415623743500167240ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ab.svg000066400000000000000000000033141415623743500167270ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ac.svg000066400000000000000000000030151415623743500167260ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ad.svg000066400000000000000000000023451415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ae.svg000066400000000000000000000133651415623743500167410ustar00rootroot00000000000000 qTox/smileys/Universe/1f4af.svg000066400000000000000000000061621415623743500167370ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b0.svg000066400000000000000000000046721415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b1.svg000066400000000000000000000050271415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b2.svg000066400000000000000000000024211415623743500166460ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b3.svg000066400000000000000000000037161415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b4.svg000066400000000000000000000032701415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b5.svg000066400000000000000000000031751415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b6.svg000066400000000000000000000035061415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b7.svg000066400000000000000000000032251415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b8.svg000066400000000000000000000177361415623743500166730ustar00rootroot00000000000000 qTox/smileys/Universe/1f4b9.svg000066400000000000000000000044101415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ba.svg000066400000000000000000000052471415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f4bb.svg000066400000000000000000000043561415623743500167370ustar00rootroot00000000000000 qTox/smileys/Universe/1f4bc.svg000066400000000000000000000032231415623743500167300ustar00rootroot00000000000000 qTox/smileys/Universe/1f4bd.svg000066400000000000000000000052561415623743500167410ustar00rootroot00000000000000 qTox/smileys/Universe/1f4be.svg000066400000000000000000000025251415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f4bf.svg000066400000000000000000000036051415623743500167370ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c0.svg000066400000000000000000000036051415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c1.svg000066400000000000000000000021521415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c2.svg000066400000000000000000000024451415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c3.svg000066400000000000000000000036661415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c4.svg000066400000000000000000000054611415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c5.svg000066400000000000000000000121421415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c6.svg000066400000000000000000000122241415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c7.svg000066400000000000000000000054421415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c8.svg000066400000000000000000000036241415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f4c9.svg000066400000000000000000000036261415623743500166660ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ca.svg000066400000000000000000000042201415623743500167250ustar00rootroot00000000000000 qTox/smileys/Universe/1f4cb.svg000066400000000000000000000053141415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/1f4cc.svg000066400000000000000000000030721415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/1f4cd.svg000066400000000000000000000023361415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ce.svg000066400000000000000000000031541415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f4cf.svg000066400000000000000000000053001415623743500167320ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d0.svg000066400000000000000000000015751415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d1.svg000066400000000000000000000070241415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d2.svg000066400000000000000000000115131415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d3.svg000066400000000000000000000113251415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d4.svg000066400000000000000000000045621415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d5.svg000066400000000000000000000041501415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d6.svg000066400000000000000000000041471415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d7.svg000066400000000000000000000041501415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d8.svg000066400000000000000000000041501415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f4d9.svg000066400000000000000000000041501415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f4da.svg000066400000000000000000000064321415623743500167350ustar00rootroot00000000000000 qTox/smileys/Universe/1f4db.svg000066400000000000000000000023711415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/1f4dc.svg000066400000000000000000000053531415623743500167400ustar00rootroot00000000000000 qTox/smileys/Universe/1f4dd.svg000066400000000000000000000116071415623743500167400ustar00rootroot00000000000000 qTox/smileys/Universe/1f4de.svg000066400000000000000000000016621415623743500167410ustar00rootroot00000000000000 qTox/smileys/Universe/1f4df.svg000066400000000000000000000151341415623743500167410ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e0.svg000066400000000000000000000132671415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e1.svg000066400000000000000000000061751415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e2.svg000066400000000000000000000033721415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e3.svg000066400000000000000000000024561415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e4.svg000066400000000000000000000027521415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e5.svg000066400000000000000000000027501415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e6.svg000066400000000000000000000060451415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e7.svg000066400000000000000000000046711415623743500166670ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e8.svg000066400000000000000000000054471415623743500166720ustar00rootroot00000000000000 qTox/smileys/Universe/1f4e9.svg000066400000000000000000000045271415623743500166710ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ea.svg000066400000000000000000000031141415623743500167300ustar00rootroot00000000000000 qTox/smileys/Universe/1f4eb.svg000066400000000000000000000031111415623743500167260ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ec.svg000066400000000000000000000055271415623743500167440ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ed.svg000066400000000000000000000025741415623743500167440ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ee.svg000066400000000000000000000041631415623743500167410ustar00rootroot00000000000000 qTox/smileys/Universe/1f4ef.svg000066400000000000000000000026441415623743500167440ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f0.svg000066400000000000000000000055351415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f1.svg000066400000000000000000000015041415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f2.svg000066400000000000000000000021311415623743500166500ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f3.svg000066400000000000000000000035011415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f4.svg000066400000000000000000000044401415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f5.svg000066400000000000000000000031411415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f6.svg000066400000000000000000000024341415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f7.svg000066400000000000000000000041301415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f4f9.svg000066400000000000000000000026401415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f4fa.svg000066400000000000000000000040641415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f4fb.svg000066400000000000000000000124301415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/1f4fc.svg000066400000000000000000000063761415623743500167500ustar00rootroot00000000000000 qTox/smileys/Universe/1f500.svg000066400000000000000000000036111415623743500165650ustar00rootroot00000000000000 qTox/smileys/Universe/1f501.svg000066400000000000000000000025571415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f502.svg000066400000000000000000000037671415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f503.svg000066400000000000000000000025521415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f504.svg000066400000000000000000000027231415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f505.svg000066400000000000000000000055351415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f506.svg000066400000000000000000000057351415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f507.svg000066400000000000000000000032731415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f508.svg000066400000000000000000000017651415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f509.svg000066400000000000000000000027061415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f50a.svg000066400000000000000000000045431415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f50b.svg000066400000000000000000000033671415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f50c.svg000066400000000000000000000033501415623743500166500ustar00rootroot00000000000000 qTox/smileys/Universe/1f50d.svg000066400000000000000000000025671415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f50e.svg000066400000000000000000000025641415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f50f.svg000066400000000000000000000031351415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f510.svg000066400000000000000000000034621415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f511.svg000066400000000000000000000025011415623743500165640ustar00rootroot00000000000000 qTox/smileys/Universe/1f512.svg000066400000000000000000000020061415623743500165650ustar00rootroot00000000000000 qTox/smileys/Universe/1f513.svg000066400000000000000000000020271415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f514.svg000066400000000000000000000022221415623743500165670ustar00rootroot00000000000000 qTox/smileys/Universe/1f515.svg000066400000000000000000000043371415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f516.svg000066400000000000000000000032471415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f517.svg000066400000000000000000000023631415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f518.svg000066400000000000000000000022511415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f519.svg000066400000000000000000000063141415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f51a.svg000066400000000000000000000044221415623743500166500ustar00rootroot00000000000000 qTox/smileys/Universe/1f51b.svg000066400000000000000000000045111415623743500166500ustar00rootroot00000000000000 qTox/smileys/Universe/1f51c.svg000066400000000000000000000053731415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f51d.svg000066400000000000000000000041761415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f51e.svg000066400000000000000000000044031415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f51f.svg000066400000000000000000000030211415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f520.svg000066400000000000000000000054331415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f521.svg000066400000000000000000000057121415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f522.svg000066400000000000000000000054761415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f523.svg000066400000000000000000000063211415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f524.svg000066400000000000000000000045621415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f525.svg000066400000000000000000000041211415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f526.svg000066400000000000000000000046361415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f527.svg000066400000000000000000000022241415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f528.svg000066400000000000000000000021771415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f529.svg000066400000000000000000000115561415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f52a.svg000066400000000000000000000022151415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f52b.svg000066400000000000000000000045131415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f52c.svg000066400000000000000000000055421415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f52d.svg000066400000000000000000000060631415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f52e.svg000066400000000000000000000033071415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f52f.svg000066400000000000000000000036021415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f530.svg000066400000000000000000000024071415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f531.svg000066400000000000000000000035311415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f532.svg000066400000000000000000000017741415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f533.svg000066400000000000000000000017741415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f534.svg000066400000000000000000000013311415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f535.svg000066400000000000000000000013311415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f536.svg000066400000000000000000000014741415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f537.svg000066400000000000000000000014741415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f538.svg000066400000000000000000000014731415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f539.svg000066400000000000000000000014731415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f53a.svg000066400000000000000000000014221415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f53b.svg000066400000000000000000000014211415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f53c.svg000066400000000000000000000020351415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f53d.svg000066400000000000000000000020341415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f550.svg000066400000000000000000000030151415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f551.svg000066400000000000000000000030071415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f552.svg000066400000000000000000000026731415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f553.svg000066400000000000000000000030041415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f554.svg000066400000000000000000000030031415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f555.svg000066400000000000000000000026731415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f556.svg000066400000000000000000000027611415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f557.svg000066400000000000000000000027621415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f558.svg000066400000000000000000000026521415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f559.svg000066400000000000000000000027671415623743500166160ustar00rootroot00000000000000 qTox/smileys/Universe/1f55a.svg000066400000000000000000000030701415623743500166520ustar00rootroot00000000000000 qTox/smileys/Universe/1f55b.svg000066400000000000000000000027401415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f55c.svg000066400000000000000000000030701415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f55d.svg000066400000000000000000000030731415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f55e.svg000066400000000000000000000027321415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f55f.svg000066400000000000000000000030721415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f560.svg000066400000000000000000000027671415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f561.svg000066400000000000000000000022671415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f562.svg000066400000000000000000000027601415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f563.svg000066400000000000000000000027611415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f564.svg000066400000000000000000000026511415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f565.svg000066400000000000000000000027661415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f566.svg000066400000000000000000000027751415623743500166130ustar00rootroot00000000000000 qTox/smileys/Universe/1f567.svg000066400000000000000000000026721415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f595.svg000066400000000000000000000031631415623743500166050ustar00rootroot00000000000000 image/svg+xmlqTox/smileys/Universe/1f5fb.svg000066400000000000000000000032051415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/1f5fc.svg000066400000000000000000000050641415623743500167420ustar00rootroot00000000000000 qTox/smileys/Universe/1f5fd.svg000066400000000000000000000063271415623743500167460ustar00rootroot00000000000000 qTox/smileys/Universe/1f5fe.svg000066400000000000000000000043141415623743500167410ustar00rootroot00000000000000 qTox/smileys/Universe/1f5ff.svg000066400000000000000000000045251415623743500167460ustar00rootroot00000000000000 qTox/smileys/Universe/1f600.svg000066400000000000000000000116041415623743500165670ustar00rootroot00000000000000 image/svg+xmlqTox/smileys/Universe/1f601.svg000066400000000000000000000063361415623743500165760ustar00rootroot00000000000000 image/svg+xmlqTox/smileys/Universe/1f602.svg000066400000000000000000000067121415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f603.svg000066400000000000000000000032621415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f604.svg000066400000000000000000000041611415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f605.svg000066400000000000000000000045241415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f606.svg000066400000000000000000000046011415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f607.svg000066400000000000000000000051621415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f608.svg000066400000000000000000000250101415623743500165730ustar00rootroot00000000000000 image/svg+xmlqTox/smileys/Universe/1f609.svg000066400000000000000000000051401415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f60a.svg000066400000000000000000000047131415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f60b.svg000066400000000000000000000041121415623743500166450ustar00rootroot00000000000000 qTox/smileys/Universe/1f60c.svg000066400000000000000000000054111415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f60d.svg000066400000000000000000000036721415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f60e.svg000066400000000000000000000035371415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f60f.svg000066400000000000000000000057031415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f610.svg000066400000000000000000000027041415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f611.svg000066400000000000000000000026441415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f612.svg000066400000000000000000000051661415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f613.svg000066400000000000000000000040701415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f614.svg000066400000000000000000000047041415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f615.svg000066400000000000000000000025761415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f616.svg000066400000000000000000000064341415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f617.svg000066400000000000000000000047451415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f618.svg000066400000000000000000000075511415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f619.svg000066400000000000000000000055161415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f61a.svg000066400000000000000000000077451415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f61b.svg000066400000000000000000000031751415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f61c.svg000066400000000000000000000041231415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f61d.svg000066400000000000000000000045201415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f61e.svg000066400000000000000000000044301415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f61f.svg000066400000000000000000000043731415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f620.svg000066400000000000000000000042621415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/1f621.svg000066400000000000000000000042621415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f622.svg000066400000000000000000000055211415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f623.svg000066400000000000000000000054311415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f624.svg000066400000000000000000000102401415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f625.svg000066400000000000000000000050111415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/1f626.svg000066400000000000000000000032541415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f627.svg000066400000000000000000000046041415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f628.svg000066400000000000000000000053061415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f629.svg000066400000000000000000000057151415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f62a.svg000066400000000000000000000057131415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f62b.svg000066400000000000000000000057011415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f62c.svg000066400000000000000000000040771415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f62d.svg000066400000000000000000000061541415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f62e.svg000066400000000000000000000026631415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f62f.svg000066400000000000000000000042311415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f630.svg000066400000000000000000000055061415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f631.svg000066400000000000000000000057631415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f632.svg000066400000000000000000000060131415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/1f633.svg000066400000000000000000000061401415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/1f634.svg000066400000000000000000000063541415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f635.svg000066400000000000000000000055111415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f636.svg000066400000000000000000000023251415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f637.svg000066400000000000000000000036541415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f638.svg000066400000000000000000000115001415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f639.svg000066400000000000000000000122741415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f63a.svg000066400000000000000000000074071415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f63b.svg000066400000000000000000000102461415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f63c.svg000066400000000000000000000103651415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f63d.svg000066400000000000000000000126111415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f63e.svg000066400000000000000000000072531415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f63f.svg000066400000000000000000000107631415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f640.svg000066400000000000000000000106161415623743500165750ustar00rootroot00000000000000 qTox/smileys/Universe/1f645.svg000066400000000000000000000110111415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/1f646.svg000066400000000000000000000103501415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/1f647.svg000066400000000000000000000076611415623743500166120ustar00rootroot00000000000000 qTox/smileys/Universe/1f648.svg000066400000000000000000000130311415623743500165770ustar00rootroot00000000000000 qTox/smileys/Universe/1f649.svg000066400000000000000000000116711415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f64a.svg000066400000000000000000000112631415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f64b.svg000066400000000000000000000066161415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f64c.svg000066400000000000000000000104231415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f64d.svg000066400000000000000000000054231415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f64e.svg000066400000000000000000000055161415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f64f.svg000066400000000000000000000046571415623743500166730ustar00rootroot00000000000000 qTox/smileys/Universe/1f680.svg000066400000000000000000000036341415623743500166030ustar00rootroot00000000000000 qTox/smileys/Universe/1f681.svg000066400000000000000000000073311415623743500166020ustar00rootroot00000000000000 qTox/smileys/Universe/1f682.svg000066400000000000000000000126151415623743500166040ustar00rootroot00000000000000 qTox/smileys/Universe/1f683.svg000066400000000000000000000117371415623743500166110ustar00rootroot00000000000000 qTox/smileys/Universe/1f684.svg000066400000000000000000000050571415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f685.svg000066400000000000000000000040541415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f686.svg000066400000000000000000000062651415623743500166140ustar00rootroot00000000000000 qTox/smileys/Universe/1f687.svg000066400000000000000000000062211415623743500166050ustar00rootroot00000000000000 qTox/smileys/Universe/1f688.svg000066400000000000000000000050451415623743500166110ustar00rootroot00000000000000 qTox/smileys/Universe/1f689.svg000066400000000000000000000133641415623743500166150ustar00rootroot00000000000000 qTox/smileys/Universe/1f68a.svg000066400000000000000000000106251415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/1f68b.svg000066400000000000000000000113241415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f68c.svg000066400000000000000000000070611415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f68d.svg000066400000000000000000000105151415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f68e.svg000066400000000000000000000107561415623743500166730ustar00rootroot00000000000000 qTox/smileys/Universe/1f68f.svg000066400000000000000000000054121415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f690.svg000066400000000000000000000065571415623743500166130ustar00rootroot00000000000000 qTox/smileys/Universe/1f691.svg000066400000000000000000000063031415623743500166010ustar00rootroot00000000000000 qTox/smileys/Universe/1f692.svg000066400000000000000000000063541415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f693.svg000066400000000000000000000066411415623743500166100ustar00rootroot00000000000000 qTox/smileys/Universe/1f694.svg000066400000000000000000000106161415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f695.svg000066400000000000000000000055311415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/1f696.svg000066400000000000000000000105001415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/1f697.svg000066400000000000000000000041151415623743500166060ustar00rootroot00000000000000 qTox/smileys/Universe/1f698.svg000066400000000000000000000077021415623743500166140ustar00rootroot00000000000000 qTox/smileys/Universe/1f699.svg000066400000000000000000000052621415623743500166140ustar00rootroot00000000000000 qTox/smileys/Universe/1f69a.svg000066400000000000000000000043501415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f69b.svg000066400000000000000000000053641415623743500166700ustar00rootroot00000000000000 qTox/smileys/Universe/1f69c.svg000066400000000000000000000114711415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f69d.svg000066400000000000000000000062251415623743500166670ustar00rootroot00000000000000 qTox/smileys/Universe/1f69e.svg000066400000000000000000000100331415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f69f.svg000066400000000000000000000053451415623743500166730ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a0.svg000066400000000000000000000063621415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a1.svg000066400000000000000000000053621415623743500166550ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a2.svg000066400000000000000000000051341415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a3.svg000066400000000000000000000065271415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a4.svg000066400000000000000000000060741415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a5.svg000066400000000000000000000026521415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a6.svg000066400000000000000000000026701415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a7.svg000066400000000000000000000042751415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a8.svg000066400000000000000000000023111415623743500166530ustar00rootroot00000000000000 qTox/smileys/Universe/1f6a9.svg000066400000000000000000000017111415623743500166570ustar00rootroot00000000000000 qTox/smileys/Universe/1f6aa.svg000066400000000000000000000072571415623743500167420ustar00rootroot00000000000000 qTox/smileys/Universe/1f6ab.svg000066400000000000000000000016711415623743500167350ustar00rootroot00000000000000 qTox/smileys/Universe/1f6ac.svg000066400000000000000000000041331415623743500167320ustar00rootroot00000000000000 qTox/smileys/Universe/1f6ad.svg000066400000000000000000000045741415623743500167440ustar00rootroot00000000000000 qTox/smileys/Universe/1f6ae.svg000066400000000000000000000035251415623743500167400ustar00rootroot00000000000000 qTox/smileys/Universe/1f6af.svg000066400000000000000000000052231415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b0.svg000066400000000000000000000035111415623743500166470ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b1.svg000066400000000000000000000044211415623743500166510ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b2.svg000066400000000000000000000106561415623743500166610ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b3.svg000066400000000000000000000065121415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b4.svg000066400000000000000000000140351415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b5.svg000066400000000000000000000144441415623743500166630ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b6.svg000066400000000000000000000065711415623743500166660ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b7.svg000066400000000000000000000044111415623743500166560ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b8.svg000066400000000000000000000052531415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f6b9.svg000066400000000000000000000027051415623743500166640ustar00rootroot00000000000000 qTox/smileys/Universe/1f6ba.svg000066400000000000000000000025071415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/1f6bb.svg000066400000000000000000000042141415623743500167320ustar00rootroot00000000000000 qTox/smileys/Universe/1f6bc.svg000066400000000000000000000053751415623743500167440ustar00rootroot00000000000000 qTox/smileys/Universe/1f6bd.svg000066400000000000000000000050411415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/1f6be.svg000066400000000000000000000036501415623743500167400ustar00rootroot00000000000000 qTox/smileys/Universe/1f6bf.svg000066400000000000000000000054711415623743500167440ustar00rootroot00000000000000 qTox/smileys/Universe/1f6c0.svg000066400000000000000000000056031415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f6c1.svg000066400000000000000000000046771415623743500166670ustar00rootroot00000000000000 qTox/smileys/Universe/1f6c2.svg000066400000000000000000000053761415623743500166650ustar00rootroot00000000000000 qTox/smileys/Universe/1f6c3.svg000066400000000000000000000043261415623743500166600ustar00rootroot00000000000000 qTox/smileys/Universe/1f6c4.svg000066400000000000000000000037001415623743500166540ustar00rootroot00000000000000 qTox/smileys/Universe/1f6c5.svg000066400000000000000000000045331415623743500166620ustar00rootroot00000000000000 qTox/smileys/Universe/203c.svg000066400000000000000000000026461415623743500165100ustar00rootroot00000000000000 qTox/smileys/Universe/2049.svg000066400000000000000000000031351415623743500164310ustar00rootroot00000000000000 qTox/smileys/Universe/2122.svg000066400000000000000000000031131415623743500164150ustar00rootroot00000000000000 qTox/smileys/Universe/2139.svg000066400000000000000000000022711415623743500164310ustar00rootroot00000000000000 qTox/smileys/Universe/2194.svg000066400000000000000000000022461415623743500164340ustar00rootroot00000000000000 qTox/smileys/Universe/2195.svg000066400000000000000000000022421415623743500164310ustar00rootroot00000000000000 qTox/smileys/Universe/2196.svg000066400000000000000000000021201415623743500164250ustar00rootroot00000000000000 qTox/smileys/Universe/2197.svg000066400000000000000000000021111415623743500164260ustar00rootroot00000000000000 qTox/smileys/Universe/2198.svg000066400000000000000000000021211415623743500164300ustar00rootroot00000000000000 qTox/smileys/Universe/2199.svg000066400000000000000000000021251415623743500164350ustar00rootroot00000000000000 qTox/smileys/Universe/21a9.svg000066400000000000000000000020411415623743500165020ustar00rootroot00000000000000 qTox/smileys/Universe/21aa.svg000066400000000000000000000020361415623743500165560ustar00rootroot00000000000000 qTox/smileys/Universe/23-20e3.svg000066400000000000000000000035271415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/231a.svg000066400000000000000000000034501415623743500165010ustar00rootroot00000000000000 qTox/smileys/Universe/231b.svg000066400000000000000000000033541415623743500165050ustar00rootroot00000000000000 qTox/smileys/Universe/23e9.svg000066400000000000000000000016731415623743500165220ustar00rootroot00000000000000 qTox/smileys/Universe/23ea.svg000066400000000000000000000016601415623743500165660ustar00rootroot00000000000000 qTox/smileys/Universe/23eb.svg000066400000000000000000000016721415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/23ec.svg000066400000000000000000000016571415623743500165760ustar00rootroot00000000000000 qTox/smileys/Universe/23f0.svg000066400000000000000000000056711415623743500165140ustar00rootroot00000000000000 qTox/smileys/Universe/23f3.svg000066400000000000000000000031061415623743500165060ustar00rootroot00000000000000 qTox/smileys/Universe/24c2.svg000066400000000000000000000026451415623743500165120ustar00rootroot00000000000000 qTox/smileys/Universe/25aa.svg000066400000000000000000000013621415623743500165630ustar00rootroot00000000000000 qTox/smileys/Universe/25ab.svg000066400000000000000000000013621415623743500165640ustar00rootroot00000000000000 qTox/smileys/Universe/25b6.svg000066400000000000000000000016241415623743500165120ustar00rootroot00000000000000 qTox/smileys/Universe/25c0.svg000066400000000000000000000016221415623743500165030ustar00rootroot00000000000000 qTox/smileys/Universe/25fb.svg000066400000000000000000000013611415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/25fc.svg000066400000000000000000000013611415623743500165710ustar00rootroot00000000000000 qTox/smileys/Universe/25fd.svg000066400000000000000000000013621415623743500165730ustar00rootroot00000000000000 qTox/smileys/Universe/25fe.svg000066400000000000000000000013621415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/2600.svg000066400000000000000000000052441415623743500164250ustar00rootroot00000000000000 qTox/smileys/Universe/2601.svg000066400000000000000000000027341415623743500164270ustar00rootroot00000000000000 qTox/smileys/Universe/260e.svg000066400000000000000000000100261415623743500165040ustar00rootroot00000000000000 qTox/smileys/Universe/2611.svg000066400000000000000000000027611415623743500164300ustar00rootroot00000000000000 qTox/smileys/Universe/2614.svg000066400000000000000000000052751415623743500164360ustar00rootroot00000000000000 qTox/smileys/Universe/2615.svg000066400000000000000000000057331415623743500164360ustar00rootroot00000000000000 qTox/smileys/Universe/261d.svg000066400000000000000000000063771415623743500165220ustar00rootroot00000000000000 qTox/smileys/Universe/263a.svg000066400000000000000000000051071415623743500165070ustar00rootroot00000000000000 qTox/smileys/Universe/2648.svg000066400000000000000000000032421415623743500164350ustar00rootroot00000000000000 qTox/smileys/Universe/2649.svg000066400000000000000000000035121415623743500164360ustar00rootroot00000000000000 qTox/smileys/Universe/264a.svg000066400000000000000000000035131415623743500165070ustar00rootroot00000000000000 qTox/smileys/Universe/264b.svg000066400000000000000000000051041415623743500165060ustar00rootroot00000000000000 qTox/smileys/Universe/264c.svg000066400000000000000000000040341415623743500165100ustar00rootroot00000000000000 qTox/smileys/Universe/264d.svg000066400000000000000000000046101415623743500165110ustar00rootroot00000000000000 qTox/smileys/Universe/264e.svg000066400000000000000000000032431415623743500165130ustar00rootroot00000000000000 qTox/smileys/Universe/264f.svg000066400000000000000000000035331415623743500165160ustar00rootroot00000000000000 qTox/smileys/Universe/2650.svg000066400000000000000000000026761415623743500164400ustar00rootroot00000000000000 qTox/smileys/Universe/2651.svg000066400000000000000000000035031415623743500164270ustar00rootroot00000000000000 qTox/smileys/Universe/2652.svg000066400000000000000000000044241415623743500164330ustar00rootroot00000000000000 qTox/smileys/Universe/2653.svg000066400000000000000000000033171415623743500164340ustar00rootroot00000000000000 qTox/smileys/Universe/2660.svg000066400000000000000000000017451415623743500164350ustar00rootroot00000000000000 qTox/smileys/Universe/2663.svg000066400000000000000000000023231415623743500164310ustar00rootroot00000000000000 qTox/smileys/Universe/2665.svg000066400000000000000000000014101415623743500164270ustar00rootroot00000000000000 qTox/smileys/Universe/2666.svg000066400000000000000000000015041415623743500164340ustar00rootroot00000000000000 qTox/smileys/Universe/2668.svg000066400000000000000000000051421415623743500164400ustar00rootroot00000000000000 qTox/smileys/Universe/267b.svg000066400000000000000000000103171415623743500165130ustar00rootroot00000000000000 qTox/smileys/Universe/267f.svg000066400000000000000000000042001415623743500165110ustar00rootroot00000000000000 qTox/smileys/Universe/2693.svg000066400000000000000000000022561415623743500164410ustar00rootroot00000000000000 qTox/smileys/Universe/26a0.svg000066400000000000000000000023261415623743500165040ustar00rootroot00000000000000 qTox/smileys/Universe/26a1.svg000066400000000000000000000020211415623743500164750ustar00rootroot00000000000000 qTox/smileys/Universe/26aa.svg000066400000000000000000000013311415623743500165600ustar00rootroot00000000000000 qTox/smileys/Universe/26ab.svg000066400000000000000000000013311415623743500165610ustar00rootroot00000000000000 qTox/smileys/Universe/26bd.svg000066400000000000000000000163331415623743500165740ustar00rootroot00000000000000 qTox/smileys/Universe/26be.svg000066400000000000000000000131171415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/26c4.svg000066400000000000000000000101571415623743500165130ustar00rootroot00000000000000 qTox/smileys/Universe/26c5.svg000066400000000000000000000045501415623743500165140ustar00rootroot00000000000000 qTox/smileys/Universe/26ce.svg000066400000000000000000000034231415623743500165720ustar00rootroot00000000000000 qTox/smileys/Universe/26d4.svg000066400000000000000000000017431415623743500165150ustar00rootroot00000000000000 qTox/smileys/Universe/26ea.svg000066400000000000000000000057171415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/26f2.svg000066400000000000000000000075641415623743500165240ustar00rootroot00000000000000 qTox/smileys/Universe/26f3.svg000066400000000000000000000041441415623743500165140ustar00rootroot00000000000000 qTox/smileys/Universe/26f5.svg000066400000000000000000000032171415623743500165160ustar00rootroot00000000000000 qTox/smileys/Universe/26fa.svg000066400000000000000000000124311415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/26fd.svg000066400000000000000000000065571415623743500166070ustar00rootroot00000000000000 qTox/smileys/Universe/2702.svg000066400000000000000000000051771415623743500164350ustar00rootroot00000000000000 qTox/smileys/Universe/2705.svg000066400000000000000000000021621415623743500164270ustar00rootroot00000000000000 qTox/smileys/Universe/2708.svg000066400000000000000000000046151415623743500164370ustar00rootroot00000000000000 qTox/smileys/Universe/2709.svg000066400000000000000000000037561415623743500164450ustar00rootroot00000000000000 qTox/smileys/Universe/270a.svg000066400000000000000000000063101415623743500165020ustar00rootroot00000000000000 qTox/smileys/Universe/270b.svg000066400000000000000000000021451415623743500165050ustar00rootroot00000000000000 qTox/smileys/Universe/270c.svg000066400000000000000000000064061415623743500165120ustar00rootroot00000000000000 qTox/smileys/Universe/270f.svg000066400000000000000000000045641415623743500165200ustar00rootroot00000000000000 qTox/smileys/Universe/2712.svg000066400000000000000000000031711415623743500164260ustar00rootroot00000000000000 qTox/smileys/Universe/2714.svg000066400000000000000000000015571415623743500164360ustar00rootroot00000000000000 qTox/smileys/Universe/2716.svg000066400000000000000000000021141415623743500164260ustar00rootroot00000000000000 qTox/smileys/Universe/2728.svg000066400000000000000000000041151415623743500164340ustar00rootroot00000000000000 qTox/smileys/Universe/2733.svg000066400000000000000000000024361415623743500164340ustar00rootroot00000000000000 qTox/smileys/Universe/2734.svg000066400000000000000000000026761415623743500164430ustar00rootroot00000000000000 qTox/smileys/Universe/2744.svg000066400000000000000000000050141415623743500164310ustar00rootroot00000000000000 qTox/smileys/Universe/2747.svg000066400000000000000000000056141415623743500164420ustar00rootroot00000000000000 qTox/smileys/Universe/274c.svg000066400000000000000000000021131415623743500165050ustar00rootroot00000000000000 qTox/smileys/Universe/274e.svg000066400000000000000000000025171415623743500165170ustar00rootroot00000000000000 qTox/smileys/Universe/2753.svg000066400000000000000000000021751415623743500164360ustar00rootroot00000000000000 qTox/smileys/Universe/2754.svg000066400000000000000000000021751415623743500164370ustar00rootroot00000000000000 qTox/smileys/Universe/2755.svg000066400000000000000000000017061415623743500164370ustar00rootroot00000000000000 qTox/smileys/Universe/2757.svg000066400000000000000000000017061415623743500164410ustar00rootroot00000000000000 qTox/smileys/Universe/2764.svg000066400000000000000000000016341415623743500164370ustar00rootroot00000000000000 qTox/smileys/Universe/2795.svg000066400000000000000000000015401415623743500164370ustar00rootroot00000000000000 qTox/smileys/Universe/2796.svg000066400000000000000000000013401415623743500164360ustar00rootroot00000000000000 qTox/smileys/Universe/2797.svg000066400000000000000000000022521415623743500164420ustar00rootroot00000000000000 qTox/smileys/Universe/27a1.svg000066400000000000000000000016401415623743500165040ustar00rootroot00000000000000 qTox/smileys/Universe/27b0.svg000066400000000000000000000025331415623743500165060ustar00rootroot00000000000000 qTox/smileys/Universe/27bf.svg000066400000000000000000000040671415623743500166000ustar00rootroot00000000000000 qTox/smileys/Universe/2934.svg000066400000000000000000000021121415623743500164260ustar00rootroot00000000000000 qTox/smileys/Universe/2935.svg000066400000000000000000000021141415623743500164310ustar00rootroot00000000000000 qTox/smileys/Universe/2b05.svg000066400000000000000000000016431415623743500165050ustar00rootroot00000000000000 qTox/smileys/Universe/2b06.svg000066400000000000000000000016401415623743500165030ustar00rootroot00000000000000 qTox/smileys/Universe/2b07.svg000066400000000000000000000016431415623743500165070ustar00rootroot00000000000000 qTox/smileys/Universe/2b1b.svg000066400000000000000000000013611415623743500165600ustar00rootroot00000000000000 qTox/smileys/Universe/2b1c.svg000066400000000000000000000013611415623743500165610ustar00rootroot00000000000000 qTox/smileys/Universe/2b50.svg000066400000000000000000000021511415623743500165000ustar00rootroot00000000000000 qTox/smileys/Universe/2b55.svg000066400000000000000000000015021415623743500165040ustar00rootroot00000000000000 qTox/smileys/Universe/30-20e3.svg000066400000000000000000000022451415623743500167250ustar00rootroot00000000000000 qTox/smileys/Universe/3030.svg000066400000000000000000000023051415623743500164160ustar00rootroot00000000000000 qTox/smileys/Universe/303d.svg000066400000000000000000000031261415623743500165040ustar00rootroot00000000000000 qTox/smileys/Universe/31-20e3.svg000066400000000000000000000021371415623743500167260ustar00rootroot00000000000000 qTox/smileys/Universe/32-20e3.svg000066400000000000000000000024741415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/3297.svg000066400000000000000000000055221415623743500164410ustar00rootroot00000000000000 qTox/smileys/Universe/3299.svg000066400000000000000000000102261415623743500164400ustar00rootroot00000000000000 qTox/smileys/Universe/33-20e3.svg000066400000000000000000000025421415623743500167300ustar00rootroot00000000000000 qTox/smileys/Universe/34-20e3.svg000066400000000000000000000024421415623743500167300ustar00rootroot00000000000000 qTox/smileys/Universe/35-20e3.svg000066400000000000000000000026251415623743500167340ustar00rootroot00000000000000 qTox/smileys/Universe/36-20e3.svg000066400000000000000000000026051415623743500167330ustar00rootroot00000000000000 qTox/smileys/Universe/37-20e3.svg000066400000000000000000000022501415623743500167300ustar00rootroot00000000000000 qTox/smileys/Universe/38-20e3.svg000066400000000000000000000027071415623743500167400ustar00rootroot00000000000000 qTox/smileys/Universe/39-20e3.svg000066400000000000000000000026231415623743500167360ustar00rootroot00000000000000 qTox/smileys/Universe/LICENSE000066400000000000000000000020671415623743500163220ustar00rootroot00000000000000Copyright (c) 2014 Twitter, Inc and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. qTox/smileys/Universe/LICENSE-GRAPHICS000066400000000000000000000441341415623743500175210ustar00rootroot00000000000000Attribution 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More_considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part; and b. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the "Licensor." Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. qTox/smileys/Universe/README.md000066400000000000000000000002171415623743500165670ustar00rootroot00000000000000## License Copyright 2014 Twitter, Inc and other contributors Graphics licensed under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/ qTox/smileys/Universe/a9.svg000066400000000000000000000024641415623743500163500ustar00rootroot00000000000000 qTox/smileys/Universe/ae.svg000066400000000000000000000026501415623743500164210ustar00rootroot00000000000000 qTox/smileys/Universe/e50a.svg000066400000000000000000000115531415623743500165700ustar00rootroot00000000000000 qTox/smileys/Universe/emoticons.xml000066400000000000000000001757041415623743500200500ustar00rootroot00000000000000 :-) :) :smile: 😀 :grinning: 😁 😂 :tearsofjoy: 😃 :-D :D 😄 :happy: 😅 😆 😇 O:) O:-) 😈 3:) 3:-) 😉 ;-) ;) 😊 ^_^ 😋 :-P :P :-p :p 😌 😍 (L) (l) 😎 :cool: (H) (h) 8-) 8) 😏 😐 :| :-| :disappointed: 😑 -_- 😒 ಠ_ಠ 😓 😔 😕 :-/ :/ :-\ :\ 😖 :-s :s :oops: 😗 😘 :-* :* 😙 😚 😛 😜 ;P ;-P 😝 😞 :-( :( :sad: 😟 😠 :angry: >:-( >:( 😡 😢 :cry: :'( :,( 😣 😤 😥 😦 😧 😨 😩 😪 😫 😬 😭 😮 😯 😰 😱 😲 :-o :o :-O :O 😳 :$ :blush: 😴 :sleep: :sleeping: |-) #) 😵 😶 😷 😸 :3 =^_^= =(^_^)= 😹 😺 😻 😼 😽 😾 😿 🙀 🙅 🙆 🙇 🙈 🙉 🙊 :silence: :-x :-X :x :X 🙋 :hi: 🙌 🙍 🙎 🙏 💩 🌀 🌁 🌂 🌃 🌄 🌅 🌆 🌇 🌈 🌉 🌊 🌋 🌌 🌍 🌎 🌏 🌐 🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘 🌙 🌚 🌛 🌜 🌝 🌞 🌟 🌠 🌰 🌱 🌲 🌳 🌴 🌵 🌷 🌸 🌹 🌺 🌻 🌼 🌽 🌾 🌿 🍀 🍁 🍂 🍃 🍄 🍅 🍆 🍇 🍈 🍉 🍊 🍋 🍌 🍍 🍎 🍏 🍐 🍑 🍒 🍓 🍔 🍕 🍖 🍗 🍘 🍙 🍚 🍛 🍜 🍝 🍞 🍟 🍠 🍡 🍢 🍣 🍤 🍥 🍦 🍧 🍨 🍩 🍪 :cookie: 🍫 🍬 🍭 🍮 🍯 🍰 🍱 🍲 🍳 🍴 🍵 🍶 🍷 🍸 🍹 🍺 :beer: 🍻 :beers: 🍼 🎀 🎁 🎂 🎃 🎄 🎅 🎆 🎇 🎈 🎉 🎊 🎋 🎌 🎍 🎎 🎏 🎐 🎑 🎒 🎓 🎠 🎡 🎢 🎣 🎤 🎥 🎦 🎧 🎨 🎩 🎪 🎫 🎬 🎭 🎮 🎯 🎰 🎱 🎲 🎳 🎴 🎵 🎶 🎷 🎸 🎹 🎺 🎻 🎼 🎽 🎾 🎿 🏀 🏁 🏂 🏃 🏄 🏆 🏇 🏈 🏉 🏊 🏠 🏡 🏢 🏣 🏤 🏥 🏦 🏧 🏨 🏩 🏪 🏫 🏬 🏭 🏮 🏯 🏰 🐀 🐁 🐂 🐃 🐄 🐅 🐆 🐇 🐈 🐉 🐊 🐋 🐌 🐍 🐎 🐏 🐐 🐑 🐒 🐓 🐔 🐕 🐖 🐗 🐘 🐙 🐚 🐛 🐜 🐝 🐞 🐟 🐠 🐡 🐢 🐣 🐤 🐥 🐦 🐧 🐨 🐩 🐪 🐫 🐬 🐭 🐮 🐯 🐰 🐱 🐲 🐳 🐴 🐵 🐶 🐷 🐸 🐹 🐺 🐻 🐼 🐽 🐾 👀 👂 👃 👄 👅 🖕 :finger: :fuck: 👆 👇 👈 👉 👊 👋 👌 👍 👎 👏 👐 👑 👒 👓 👔 👕 👖 👗 👘 👙 👚 👛 👜 👝 👞 👟 👠 👡 👢 👣 👤 👥 👦 👧 👨 👩 👪 👫 👬 👭 👮 👯 👰 👱 👲 👳 👴 👵 👶 👷 👸 👹 👺 👻 👼 :angel: 0:) O:) 0:-) O:-) 👽 👾 👿 :devil: :666: }:-) }:-> ]:-) ]:-> >:-> 💀 :skull: 💁 💂 💃 💄 💅 💆 💇 💈 💉 :syringe: :injection: 💊 💋 💌 💍 💎 💏 💐 💑 💒 💓 💔 💕 💖 💗 💘 💙 💚 💛 💜 💝 💞 💟 💠 💡 💢 💣 :bomb: 💤 💥 💦 💧 💨 💪 💫 💬 💭 💮 💯 💰 💱 💲 💳 💴 💵 💶 💷 💸 💹 💺 💻 💼 💽 💾 💿 📀 📁 📂 📃 📄 📅 📆 📇 📈 📉 📊 📋 📌 📍 📎 📏 📐 📑 📒 📓 📔 📕 📖 📗 📘 📙 📚 📛 📜 📝 📞 📟 📠 📡 📢 📣 📤 📥 📦 📧 📨 📩 📪 📫 📬 📭 📮 📯 📰 📱 📲 📳 📴 📵 📶 📷 📹 📺 📻 📼 🔀 🔁 🔂 🔃 🔄 🔅 🔆 🔇 🔈 🔉 🔊 🔋 🔌 🔍 🔎 🔏 🔐 🔑 🔒 🔓 🔔 🔕 🔖 🔗 🔘 🔙 🔚 🔛 🔜 🔝 🔞 🔟 🔠 🔡 🔢 🔣 🔤 🔥 🔦 🔧 🔨 🔩 🔪 🔫 🔬 🔭 🔮 🔯 🔰 🔱 🔲 🔳 🔴 🔵 🔶 🔷 🔸 🔹 🔺 🔻 🔼 🔽 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧 🗻 🗼 🗽 🗾 🗿 🀄 🃏 🚀 🚁 🚂 🚃 🚄 🚅 🚆 🚇 🚈 🚉 🚊 🚋 🚌 🚍 🚎 🚏 🚐 🚑 🚒 🚓 🚔 🚕 🚖 🚗 🚘 🚙 🚚 🚛 🚜 🚝 🚞 🚟 🚠 🚡 🚢 🚣 🚤 🚥 🚦 🚧 🚨 🚩 🚪 🚫 🚬 🚭 🚮 🚯 🚰 🚱 🚲 🚳 🚴 🚵 🚶 🚷 🚸 🚹 🚺 🚻 🚼 🚽 🚾 🚿 🛀 🛁 🛂 🛃 🛄 🛅 <3 :heart: :love: © ® 🈚 🈯 🈺 🈁 🈂 🈲 🈳 🈴 🈵 🈶 🈷 🈸 🈹 🉐 🉑 🅰 🅱 🅾 🅿 🆎 🆑 🆒 🆓 🆔 🆕 🆖 🆗 🆘 🆙 🆚 🇦 🇧 🇨 🇩 🇪 🇫 🇬 🇭 🇮 🇯 🇰 🇱 🇲 🇳 🇴 🇵 🇶 🇷 🇸 🇹 🇺 🇻 🇼 🇽 🇾 🇿 🇨🇳 🇩🇪 🇪🇸 🇫🇷 🇬🇧 🇮🇹 🇯🇵 🇰🇷 🇷🇺 🇺🇸 qTox/smileys/emojione.qrc000066400000000000000000002276001415623743500160330ustar00rootroot00000000000000 emojione/0023-20e3.svg emojione/0023.svg emojione/002a-20e3.svg emojione/002a.svg emojione/0030-20e3.svg emojione/0030.svg emojione/0031-20e3.svg emojione/0031.svg emojione/0032-20e3.svg emojione/0032.svg emojione/0033-20e3.svg emojione/0033.svg emojione/0034-20e3.svg emojione/0034.svg emojione/0035-20e3.svg emojione/0035.svg emojione/0036-20e3.svg emojione/0036.svg emojione/0037-20e3.svg emojione/0037.svg emojione/0038-20e3.svg emojione/0038.svg emojione/0039-20e3.svg emojione/0039.svg emojione/00a9.svg emojione/00ae.svg emojione/1f004.svg emojione/1f0cf.svg emojione/1f1e6-1f1e8.svg emojione/1f1e6-1f1e9.svg emojione/1f1e6-1f1ea.svg emojione/1f1e6-1f1eb.svg emojione/1f1e6-1f1ec.svg emojione/1f1e6-1f1ee.svg emojione/1f1e6-1f1f1.svg emojione/1f1e6-1f1f2.svg emojione/1f1e6-1f1f4.svg emojione/1f1e6-1f1f6.svg emojione/1f1e6-1f1f7.svg emojione/1f1e6-1f1f8.svg emojione/1f1e6-1f1f9.svg emojione/1f1e6-1f1fa.svg emojione/1f1e6-1f1fc.svg emojione/1f1e6-1f1fd.svg emojione/1f1e6-1f1ff.svg emojione/1f1e6.svg emojione/1f1e7-1f1e6.svg emojione/1f1e7-1f1e7.svg emojione/1f1e7-1f1e9.svg emojione/1f1e7-1f1ea.svg emojione/1f1e7-1f1eb.svg emojione/1f1e7-1f1ec.svg emojione/1f1e7-1f1ed.svg emojione/1f1e7-1f1ee.svg emojione/1f1e7-1f1ef.svg emojione/1f1e7-1f1f1.svg emojione/1f1e7-1f1f2.svg emojione/1f1e7-1f1f3.svg emojione/1f1e7-1f1f4.svg emojione/1f1e7-1f1f6.svg emojione/1f1e7-1f1f7.svg emojione/1f1e7-1f1f8.svg emojione/1f1e7-1f1f9.svg emojione/1f1e7-1f1fb.svg emojione/1f1e7-1f1fc.svg emojione/1f1e7-1f1fe.svg emojione/1f1e7-1f1ff.svg emojione/1f1e7.svg emojione/1f1e8-1f1e6.svg emojione/1f1e8-1f1e8.svg emojione/1f1e8-1f1e9.svg emojione/1f1e8-1f1eb.svg emojione/1f1e8-1f1ec.svg emojione/1f1e8-1f1ed.svg emojione/1f1e8-1f1ee.svg emojione/1f1e8-1f1f0.svg emojione/1f1e8-1f1f1.svg emojione/1f1e8-1f1f2.svg emojione/1f1e8-1f1f3.svg emojione/1f1e8-1f1f4.svg emojione/1f1e8-1f1f5.svg emojione/1f1e8-1f1f7.svg emojione/1f1e8-1f1fa.svg emojione/1f1e8-1f1fb.svg emojione/1f1e8-1f1fc.svg emojione/1f1e8-1f1fd.svg emojione/1f1e8-1f1fe.svg emojione/1f1e8-1f1ff.svg emojione/1f1e8.svg emojione/1f1e9-1f1ea.svg emojione/1f1e9-1f1ec.svg emojione/1f1e9-1f1ef.svg emojione/1f1e9-1f1f0.svg emojione/1f1e9-1f1f2.svg emojione/1f1e9-1f1f4.svg emojione/1f1e9-1f1ff.svg emojione/1f1e9.svg emojione/1f1ea-1f1e6.svg emojione/1f1ea-1f1e8.svg emojione/1f1ea-1f1ea.svg emojione/1f1ea-1f1ec.svg emojione/1f1ea-1f1ed.svg emojione/1f1ea-1f1f7.svg emojione/1f1ea-1f1f8.svg emojione/1f1ea-1f1f9.svg emojione/1f1ea-1f1fa.svg emojione/1f1ea.svg emojione/1f1eb-1f1ee.svg emojione/1f1eb-1f1ef.svg emojione/1f1eb-1f1f0.svg emojione/1f1eb-1f1f2.svg emojione/1f1eb-1f1f4.svg emojione/1f1eb-1f1f7.svg emojione/1f1eb.svg emojione/1f1ec-1f1e6.svg emojione/1f1ec-1f1e7.svg emojione/1f1ec-1f1e9.svg emojione/1f1ec-1f1ea.svg emojione/1f1ec-1f1eb.svg emojione/1f1ec-1f1ec.svg emojione/1f1ec-1f1ed.svg emojione/1f1ec-1f1ee.svg emojione/1f1ec-1f1f1.svg emojione/1f1ec-1f1f2.svg emojione/1f1ec-1f1f3.svg emojione/1f1ec-1f1f5.svg emojione/1f1ec-1f1f6.svg emojione/1f1ec-1f1f7.svg emojione/1f1ec-1f1f8.svg emojione/1f1ec-1f1f9.svg emojione/1f1ec-1f1fa.svg emojione/1f1ec-1f1fc.svg emojione/1f1ec-1f1fe.svg emojione/1f1ec.svg emojione/1f1ed-1f1f0.svg emojione/1f1ed-1f1f2.svg emojione/1f1ed-1f1f3.svg emojione/1f1ed-1f1f7.svg emojione/1f1ed-1f1f9.svg emojione/1f1ed-1f1fa.svg emojione/1f1ed.svg emojione/1f1ee-1f1e8.svg emojione/1f1ee-1f1e9.svg emojione/1f1ee-1f1ea.svg emojione/1f1ee-1f1f1.svg emojione/1f1ee-1f1f2.svg emojione/1f1ee-1f1f3.svg emojione/1f1ee-1f1f4.svg emojione/1f1ee-1f1f6.svg emojione/1f1ee-1f1f7.svg emojione/1f1ee-1f1f8.svg emojione/1f1ee-1f1f9.svg emojione/1f1ee.svg emojione/1f1ef-1f1ea.svg emojione/1f1ef-1f1f2.svg emojione/1f1ef-1f1f4.svg emojione/1f1ef-1f1f5.svg emojione/1f1ef.svg emojione/1f1f0-1f1ea.svg emojione/1f1f0-1f1ec.svg emojione/1f1f0-1f1ed.svg emojione/1f1f0-1f1ee.svg emojione/1f1f0-1f1f2.svg emojione/1f1f0-1f1f3.svg emojione/1f1f0-1f1f5.svg emojione/1f1f0-1f1f7.svg emojione/1f1f0-1f1fc.svg emojione/1f1f0-1f1fe.svg emojione/1f1f0-1f1ff.svg emojione/1f1f0.svg emojione/1f1f1-1f1e6.svg emojione/1f1f1-1f1e7.svg emojione/1f1f1-1f1e8.svg emojione/1f1f1-1f1ee.svg emojione/1f1f1-1f1f0.svg emojione/1f1f1-1f1f7.svg emojione/1f1f1-1f1f8.svg emojione/1f1f1-1f1f9.svg emojione/1f1f1-1f1fa.svg emojione/1f1f1-1f1fb.svg emojione/1f1f1-1f1fe.svg emojione/1f1f1.svg emojione/1f1f2-1f1e6.svg emojione/1f1f2-1f1e8.svg emojione/1f1f2-1f1e9.svg emojione/1f1f2-1f1ea.svg emojione/1f1f2-1f1eb.svg emojione/1f1f2-1f1ec.svg emojione/1f1f2-1f1ed.svg emojione/1f1f2-1f1f0.svg emojione/1f1f2-1f1f1.svg emojione/1f1f2-1f1f2.svg emojione/1f1f2-1f1f3.svg emojione/1f1f2-1f1f4.svg emojione/1f1f2-1f1f5.svg emojione/1f1f2-1f1f6.svg emojione/1f1f2-1f1f7.svg emojione/1f1f2-1f1f8.svg emojione/1f1f2-1f1f9.svg emojione/1f1f2-1f1fa.svg emojione/1f1f2-1f1fb.svg emojione/1f1f2-1f1fc.svg emojione/1f1f2-1f1fd.svg emojione/1f1f2-1f1fe.svg emojione/1f1f2-1f1ff.svg emojione/1f1f2.svg emojione/1f1f3-1f1e6.svg emojione/1f1f3-1f1e8.svg emojione/1f1f3-1f1ea.svg emojione/1f1f3-1f1eb.svg emojione/1f1f3-1f1ec.svg emojione/1f1f3-1f1ee.svg emojione/1f1f3-1f1f1.svg emojione/1f1f3-1f1f4.svg emojione/1f1f3-1f1f5.svg emojione/1f1f3-1f1f7.svg emojione/1f1f3-1f1fa.svg emojione/1f1f3-1f1ff.svg emojione/1f1f3.svg emojione/1f1f4-1f1f2.svg emojione/1f1f4.svg emojione/1f1f5-1f1e6.svg emojione/1f1f5-1f1ea.svg emojione/1f1f5-1f1eb.svg emojione/1f1f5-1f1ec.svg emojione/1f1f5-1f1ed.svg emojione/1f1f5-1f1f0.svg emojione/1f1f5-1f1f1.svg emojione/1f1f5-1f1f2.svg emojione/1f1f5-1f1f3.svg emojione/1f1f5-1f1f7.svg emojione/1f1f5-1f1f8.svg emojione/1f1f5-1f1f9.svg emojione/1f1f5-1f1fc.svg emojione/1f1f5-1f1fe.svg emojione/1f1f5.svg emojione/1f1f6-1f1e6.svg emojione/1f1f6.svg emojione/1f1f7-1f1ea.svg emojione/1f1f7-1f1f4.svg emojione/1f1f7-1f1f8.svg emojione/1f1f7-1f1fa.svg emojione/1f1f7-1f1fc.svg emojione/1f1f7.svg emojione/1f1f8-1f1e6.svg emojione/1f1f8-1f1e7.svg emojione/1f1f8-1f1e8.svg emojione/1f1f8-1f1e9.svg emojione/1f1f8-1f1ea.svg emojione/1f1f8-1f1ec.svg emojione/1f1f8-1f1ed.svg emojione/1f1f8-1f1ee.svg emojione/1f1f8-1f1ef.svg emojione/1f1f8-1f1f0.svg emojione/1f1f8-1f1f1.svg emojione/1f1f8-1f1f2.svg emojione/1f1f8-1f1f3.svg emojione/1f1f8-1f1f4.svg emojione/1f1f8-1f1f7.svg emojione/1f1f8-1f1f8.svg emojione/1f1f8-1f1f9.svg emojione/1f1f8-1f1fb.svg emojione/1f1f8-1f1fd.svg emojione/1f1f8-1f1fe.svg emojione/1f1f8-1f1ff.svg emojione/1f1f8.svg emojione/1f1f9-1f1e6.svg emojione/1f1f9-1f1e8.svg emojione/1f1f9-1f1e9.svg emojione/1f1f9-1f1eb.svg emojione/1f1f9-1f1ec.svg emojione/1f1f9-1f1ed.svg emojione/1f1f9-1f1ef.svg emojione/1f1f9-1f1f0.svg emojione/1f1f9-1f1f1.svg emojione/1f1f9-1f1f2.svg emojione/1f1f9-1f1f3.svg emojione/1f1f9-1f1f4.svg emojione/1f1f9-1f1f7.svg emojione/1f1f9-1f1f9.svg emojione/1f1f9-1f1fb.svg emojione/1f1f9-1f1fc.svg emojione/1f1f9-1f1ff.svg emojione/1f1f9.svg emojione/1f1fa-1f1e6.svg emojione/1f1fa-1f1ec.svg emojione/1f1fa-1f1f2.svg emojione/1f1fa-1f1f8.svg emojione/1f1fa-1f1fe.svg emojione/1f1fa-1f1ff.svg emojione/1f1fa.svg emojione/1f1fb-1f1e6.svg emojione/1f1fb-1f1e8.svg emojione/1f1fb-1f1ea.svg emojione/1f1fb-1f1ec.svg emojione/1f1fb-1f1ee.svg emojione/1f1fb-1f1f3.svg emojione/1f1fb-1f1fa.svg emojione/1f1fb.svg emojione/1f1fc-1f1eb.svg emojione/1f1fc-1f1f8.svg emojione/1f1fc.svg emojione/1f1fd-1f1f0.svg emojione/1f1fd.svg emojione/1f1fe-1f1ea.svg emojione/1f1fe-1f1f9.svg emojione/1f1fe.svg emojione/1f1ff-1f1e6.svg emojione/1f1ff-1f1f2.svg emojione/1f1ff-1f1fc.svg emojione/1f1ff.svg emojione/1f3a0.svg emojione/1f3a1.svg emojione/1f3a2.svg emojione/1f3a3.svg emojione/1f3a4.svg emojione/1f3a5.svg emojione/1f3a6.svg emojione/1f3a7.svg emojione/1f3a8.svg emojione/1f3a9.svg emojione/1f3aa.svg emojione/1f3ab.svg emojione/1f3ac.svg emojione/1f3ad.svg emojione/1f3ae.svg emojione/1f3af.svg emojione/1f3b0.svg emojione/1f3b1.svg emojione/1f3b2.svg emojione/1f3b3.svg emojione/1f3b4.svg emojione/1f3b5.svg emojione/1f3b6.svg emojione/1f3b7.svg emojione/1f3b8.svg emojione/1f3b9.svg emojione/1f3ba.svg emojione/1f3bb.svg emojione/1f3bc.svg emojione/1f3bd.svg emojione/1f3be.svg emojione/1f3bf.svg emojione/1f3c0.svg emojione/1f3c1.svg emojione/1f3c2.svg emojione/1f3c3-1f3fb.svg emojione/1f3c3-1f3fc.svg emojione/1f3c3-1f3fd.svg emojione/1f3c3-1f3fe.svg emojione/1f3c3-1f3ff.svg emojione/1f3c3.svg emojione/1f3c4-1f3fb.svg emojione/1f3c4-1f3fc.svg emojione/1f3c4-1f3fd.svg emojione/1f3c4-1f3fe.svg emojione/1f3c4-1f3ff.svg emojione/1f3c4.svg emojione/1f3c5.svg emojione/1f3c6.svg emojione/1f3c7-1f3fb.svg emojione/1f3c7-1f3fc.svg emojione/1f3c7-1f3fd.svg emojione/1f3c7-1f3fe.svg emojione/1f3c7-1f3ff.svg emojione/1f3c7.svg emojione/1f3c8.svg emojione/1f3c9.svg emojione/1f3ca-1f3fb.svg emojione/1f3ca-1f3fc.svg emojione/1f3ca-1f3fd.svg emojione/1f3ca-1f3fe.svg emojione/1f3ca-1f3ff.svg emojione/1f3ca.svg emojione/1f3cb-1f3fb.svg emojione/1f3cb-1f3fc.svg emojione/1f3cb-1f3fd.svg emojione/1f3cb-1f3fe.svg emojione/1f3cb-1f3ff.svg emojione/1f3cb.svg emojione/1f3cc.svg emojione/1f3cd.svg emojione/1f3ce.svg emojione/1f3cf.svg emojione/1f3d0.svg emojione/1f3d1.svg emojione/1f3d2.svg emojione/1f3d3.svg emojione/1f3d4.svg emojione/1f3d5.svg emojione/1f3d6.svg emojione/1f3d7.svg emojione/1f3d8.svg emojione/1f3d9.svg emojione/1f3da.svg emojione/1f3db.svg emojione/1f3dc.svg emojione/1f3dd.svg emojione/1f3de.svg emojione/1f3df.svg emojione/1f3e0.svg emojione/1f3e1.svg emojione/1f3e2.svg emojione/1f3e3.svg emojione/1f3e4.svg emojione/1f3e5.svg emojione/1f3e6.svg emojione/1f3e7.svg emojione/1f3e8.svg emojione/1f3e9.svg emojione/1f3ea.svg emojione/1f3eb.svg emojione/1f3ec.svg emojione/1f3ed.svg emojione/1f3ee.svg emojione/1f3ef.svg emojione/1f3f0.svg emojione/1f3f3.svg emojione/1f3f4.svg emojione/1f3f5.svg emojione/1f3f7.svg emojione/1f3f8.svg emojione/1f3f9.svg emojione/1f3fa.svg emojione/1f4a0.svg emojione/1f4a1.svg emojione/1f4a2.svg emojione/1f4a3.svg emojione/1f4a4.svg emojione/1f4a5.svg emojione/1f4a6.svg emojione/1f4a7.svg emojione/1f4a8.svg emojione/1f4a9.svg emojione/1f4aa-1f3fb.svg emojione/1f4aa-1f3fc.svg emojione/1f4aa-1f3fd.svg emojione/1f4aa-1f3fe.svg emojione/1f4aa-1f3ff.svg emojione/1f4aa.svg emojione/1f4ab.svg emojione/1f4ac.svg emojione/1f4ad.svg emojione/1f4ae.svg emojione/1f4af.svg emojione/1f4b0.svg emojione/1f4b1.svg emojione/1f4b2.svg emojione/1f4b3.svg emojione/1f4b4.svg emojione/1f4b5.svg emojione/1f4b6.svg emojione/1f4b7.svg emojione/1f4b8.svg emojione/1f4b9.svg emojione/1f4ba.svg emojione/1f4bb.svg emojione/1f4bc.svg emojione/1f4bd.svg emojione/1f4be.svg emojione/1f4bf.svg emojione/1f4c0.svg emojione/1f4c1.svg emojione/1f4c2.svg emojione/1f4c3.svg emojione/1f4c4.svg emojione/1f4c5.svg emojione/1f4c6.svg emojione/1f4c7.svg emojione/1f4c8.svg emojione/1f4c9.svg emojione/1f4ca.svg emojione/1f4cb.svg emojione/1f4cc.svg emojione/1f4cd.svg emojione/1f4ce.svg emojione/1f4cf.svg emojione/1f4d0.svg emojione/1f4d1.svg emojione/1f4d2.svg emojione/1f4d3.svg emojione/1f4d4.svg emojione/1f4d5.svg emojione/1f4d6.svg emojione/1f4d7.svg emojione/1f4d8.svg emojione/1f4d9.svg emojione/1f4da.svg emojione/1f4db.svg emojione/1f4dc.svg emojione/1f4dd.svg emojione/1f4de.svg emojione/1f4df.svg emojione/1f4e0.svg emojione/1f4e1.svg emojione/1f4e2.svg emojione/1f4e3.svg emojione/1f4e4.svg emojione/1f4e5.svg emojione/1f4e6.svg emojione/1f4e7.svg emojione/1f4e8.svg emojione/1f4e9.svg emojione/1f4ea.svg emojione/1f4eb.svg emojione/1f4ec.svg emojione/1f4ed.svg emojione/1f4ee.svg emojione/1f4ef.svg emojione/1f4f0.svg emojione/1f4f1.svg emojione/1f4f2.svg emojione/1f4f3.svg emojione/1f4f4.svg emojione/1f4f5.svg emojione/1f4f6.svg emojione/1f4f7.svg emojione/1f4f8.svg emojione/1f4f9.svg emojione/1f4fa.svg emojione/1f4fb.svg emojione/1f4fc.svg emojione/1f4fd.svg emojione/1f4ff.svg emojione/1f5a4.svg emojione/1f5a5.svg emojione/1f5a8.svg emojione/1f5b1.svg emojione/1f5b2.svg emojione/1f5bc.svg emojione/1f5c2.svg emojione/1f5c3.svg emojione/1f5c4.svg emojione/1f5d1.svg emojione/1f5d2.svg emojione/1f5d3.svg emojione/1f5dc.svg emojione/1f5dd.svg emojione/1f5de.svg emojione/1f5e1.svg emojione/1f5e3.svg emojione/1f5e8.svg emojione/1f5ef.svg emojione/1f5f3.svg emojione/1f5fa.svg emojione/1f5fb.svg emojione/1f5fc.svg emojione/1f5fd.svg emojione/1f5fe.svg emojione/1f5ff.svg emojione/1f6a0.svg emojione/1f6a1.svg emojione/1f6a2.svg emojione/1f6a3-1f3fb.svg emojione/1f6a3-1f3fc.svg emojione/1f6a3-1f3fd.svg emojione/1f6a3-1f3fe.svg emojione/1f6a3-1f3ff.svg emojione/1f6a3.svg emojione/1f6a4.svg emojione/1f6a5.svg emojione/1f6a6.svg emojione/1f6a7.svg emojione/1f6a8.svg emojione/1f6a9.svg emojione/1f6aa.svg emojione/1f6ab.svg emojione/1f6ac.svg emojione/1f6ad.svg emojione/1f6ae.svg emojione/1f6af.svg emojione/1f6b0.svg emojione/1f6b1.svg emojione/1f6b2.svg emojione/1f6b3.svg emojione/1f6b4-1f3fb.svg emojione/1f6b4-1f3fc.svg emojione/1f6b4-1f3fd.svg emojione/1f6b4-1f3fe.svg emojione/1f6b4-1f3ff.svg emojione/1f6b4.svg emojione/1f6b5-1f3fb.svg emojione/1f6b5-1f3fc.svg emojione/1f6b5-1f3fd.svg emojione/1f6b5-1f3fe.svg emojione/1f6b5-1f3ff.svg emojione/1f6b5.svg emojione/1f6b6-1f3fb.svg emojione/1f6b6-1f3fc.svg emojione/1f6b6-1f3fd.svg emojione/1f6b6-1f3fe.svg emojione/1f6b6-1f3ff.svg emojione/1f6b6.svg emojione/1f6b7.svg emojione/1f6b8.svg emojione/1f6b9.svg emojione/1f6ba.svg emojione/1f6bb.svg emojione/1f6bc.svg emojione/1f6bd.svg emojione/1f6be.svg emojione/1f6bf.svg emojione/1f6c0-1f3fb.svg emojione/1f6c0-1f3fc.svg emojione/1f6c0-1f3fd.svg emojione/1f6c0-1f3fe.svg emojione/1f6c0-1f3ff.svg emojione/1f6c0.svg emojione/1f6c1.svg emojione/1f6c2.svg emojione/1f6c3.svg emojione/1f6c4.svg emojione/1f6c5.svg emojione/1f6cb.svg emojione/1f6cc.svg emojione/1f6cd.svg emojione/1f6ce.svg emojione/1f6cf.svg emojione/1f6d0.svg emojione/1f6d1.svg emojione/1f6d2.svg emojione/1f6e0.svg emojione/1f6e1.svg emojione/1f6e2.svg emojione/1f6e3.svg emojione/1f6e4.svg emojione/1f6e5.svg emojione/1f6e9.svg emojione/1f6eb.svg emojione/1f6ec.svg emojione/1f6f0.svg emojione/1f6f3.svg emojione/1f6f4.svg emojione/1f6f5.svg emojione/1f6f6.svg emojione/1f9c0.svg emojione/1f17e.svg emojione/1f17f.svg emojione/1f18e.svg emojione/1f19a.svg emojione/1f21a.svg emojione/1f22f.svg emojione/1f23a.svg emojione/1f30a.svg emojione/1f30b.svg emojione/1f30c.svg emojione/1f30d.svg emojione/1f30e.svg emojione/1f30f.svg emojione/1f31a.svg emojione/1f31b.svg emojione/1f31c.svg emojione/1f31d.svg emojione/1f31e.svg emojione/1f31f.svg emojione/1f32a.svg emojione/1f32b.svg emojione/1f32c.svg emojione/1f32d.svg emojione/1f32e.svg emojione/1f32f.svg emojione/1f33a.svg emojione/1f33b.svg emojione/1f33c.svg emojione/1f33d.svg emojione/1f33e.svg emojione/1f33f.svg emojione/1f34a.svg emojione/1f34b.svg emojione/1f34c.svg emojione/1f34d.svg emojione/1f34e.svg emojione/1f34f.svg emojione/1f35a.svg emojione/1f35b.svg emojione/1f35c.svg emojione/1f35d.svg emojione/1f35e.svg emojione/1f35f.svg emojione/1f36a.svg emojione/1f36b.svg emojione/1f36c.svg emojione/1f36d.svg emojione/1f36e.svg emojione/1f36f.svg emojione/1f37a.svg emojione/1f37b.svg emojione/1f37c.svg emojione/1f37d.svg emojione/1f37e.svg emojione/1f37f.svg emojione/1f38a.svg emojione/1f38b.svg emojione/1f38c.svg emojione/1f38d.svg emojione/1f38e.svg emojione/1f38f.svg emojione/1f39a.svg emojione/1f39b.svg emojione/1f39e.svg emojione/1f39f.svg emojione/1f40a.svg emojione/1f40b.svg emojione/1f40c.svg emojione/1f40d.svg emojione/1f40e.svg emojione/1f40f.svg emojione/1f41a.svg emojione/1f41b.svg emojione/1f41c.svg emojione/1f41d.svg emojione/1f41e.svg emojione/1f41f.svg emojione/1f42a.svg emojione/1f42b.svg emojione/1f42c.svg emojione/1f42d.svg emojione/1f42e.svg emojione/1f42f.svg emojione/1f43a.svg emojione/1f43b.svg emojione/1f43c.svg emojione/1f43d.svg emojione/1f43e.svg emojione/1f43f.svg emojione/1f44a-1f3fb.svg emojione/1f44a-1f3fc.svg emojione/1f44a-1f3fd.svg emojione/1f44a-1f3fe.svg emojione/1f44a-1f3ff.svg emojione/1f44a.svg emojione/1f44b-1f3fb.svg emojione/1f44b-1f3fc.svg emojione/1f44b-1f3fd.svg emojione/1f44b-1f3fe.svg emojione/1f44b-1f3ff.svg emojione/1f44b.svg emojione/1f44c-1f3fb.svg emojione/1f44c-1f3fc.svg emojione/1f44c-1f3fd.svg emojione/1f44c-1f3fe.svg emojione/1f44c-1f3ff.svg emojione/1f44c.svg emojione/1f44d-1f3fb.svg emojione/1f44d-1f3fc.svg emojione/1f44d-1f3fd.svg emojione/1f44d-1f3fe.svg emojione/1f44d-1f3ff.svg emojione/1f44d.svg emojione/1f44e-1f3fb.svg emojione/1f44e-1f3fc.svg emojione/1f44e-1f3fd.svg emojione/1f44e-1f3fe.svg emojione/1f44e-1f3ff.svg emojione/1f44e.svg emojione/1f44f-1f3fb.svg emojione/1f44f-1f3fc.svg emojione/1f44f-1f3fd.svg emojione/1f44f-1f3fe.svg emojione/1f44f-1f3ff.svg emojione/1f44f.svg emojione/1f45a.svg emojione/1f45b.svg emojione/1f45c.svg emojione/1f45d.svg emojione/1f45e.svg emojione/1f45f.svg emojione/1f46a.svg emojione/1f46b.svg emojione/1f46c.svg emojione/1f46d.svg emojione/1f46e-1f3fb.svg emojione/1f46e-1f3fc.svg emojione/1f46e-1f3fd.svg emojione/1f46e-1f3fe.svg emojione/1f46e-1f3ff.svg emojione/1f46e.svg emojione/1f46f.svg emojione/1f47a.svg emojione/1f47b.svg emojione/1f47c-1f3fb.svg emojione/1f47c-1f3fc.svg emojione/1f47c-1f3fd.svg emojione/1f47c-1f3fe.svg emojione/1f47c-1f3ff.svg emojione/1f47c.svg emojione/1f47d.svg emojione/1f47e.svg emojione/1f47f.svg emojione/1f48a.svg emojione/1f48b.svg emojione/1f48c.svg emojione/1f48d.svg emojione/1f48e.svg emojione/1f48f.svg emojione/1f49a.svg emojione/1f49b.svg emojione/1f49c.svg emojione/1f49d.svg emojione/1f49e.svg emojione/1f49f.svg emojione/1f50a.svg emojione/1f50b.svg emojione/1f50c.svg emojione/1f50d.svg emojione/1f50e.svg emojione/1f50f.svg emojione/1f51a.svg emojione/1f51b.svg emojione/1f51c.svg emojione/1f51d.svg emojione/1f51e.svg emojione/1f51f.svg emojione/1f52a.svg emojione/1f52b.svg emojione/1f52c.svg emojione/1f52d.svg emojione/1f52e.svg emojione/1f52f.svg emojione/1f53a.svg emojione/1f53b.svg emojione/1f53c.svg emojione/1f53d.svg emojione/1f54a.svg emojione/1f54b.svg emojione/1f54c.svg emojione/1f54d.svg emojione/1f54e.svg emojione/1f55a.svg emojione/1f55b.svg emojione/1f55c.svg emojione/1f55d.svg emojione/1f55e.svg emojione/1f55f.svg emojione/1f56f.svg emojione/1f57a-1f3fb.svg emojione/1f57a-1f3fc.svg emojione/1f57a-1f3fd.svg emojione/1f57a-1f3fe.svg emojione/1f57a-1f3ff.svg emojione/1f57a.svg emojione/1f58a.svg emojione/1f58b.svg emojione/1f58c.svg emojione/1f58d.svg emojione/1f60a.svg emojione/1f60b.svg emojione/1f60c.svg emojione/1f60d.svg emojione/1f60e.svg emojione/1f60f.svg emojione/1f61a.svg emojione/1f61b.svg emojione/1f61c.svg emojione/1f61d.svg emojione/1f61e.svg emojione/1f61f.svg emojione/1f62a.svg emojione/1f62b.svg emojione/1f62c.svg emojione/1f62d.svg emojione/1f62e.svg emojione/1f62f.svg emojione/1f63a.svg emojione/1f63b.svg emojione/1f63c.svg emojione/1f63d.svg emojione/1f63e.svg emojione/1f63f.svg emojione/1f64a.svg emojione/1f64b-1f3fb.svg emojione/1f64b-1f3fc.svg emojione/1f64b-1f3fd.svg emojione/1f64b-1f3fe.svg emojione/1f64b-1f3ff.svg emojione/1f64b.svg emojione/1f64c-1f3fb.svg emojione/1f64c-1f3fc.svg emojione/1f64c-1f3fd.svg emojione/1f64c-1f3fe.svg emojione/1f64c-1f3ff.svg emojione/1f64c.svg emojione/1f64d-1f3fb.svg emojione/1f64d-1f3fc.svg emojione/1f64d-1f3fd.svg emojione/1f64d-1f3fe.svg emojione/1f64d-1f3ff.svg emojione/1f64d.svg emojione/1f64e-1f3fb.svg emojione/1f64e-1f3fc.svg emojione/1f64e-1f3fd.svg emojione/1f64e-1f3fe.svg emojione/1f64e-1f3ff.svg emojione/1f64e.svg emojione/1f64f-1f3fb.svg emojione/1f64f-1f3fc.svg emojione/1f64f-1f3fd.svg emojione/1f64f-1f3fe.svg emojione/1f64f-1f3ff.svg emojione/1f64f.svg emojione/1f68a.svg emojione/1f68b.svg emojione/1f68c.svg emojione/1f68d.svg emojione/1f68e.svg emojione/1f68f.svg emojione/1f69a.svg emojione/1f69b.svg emojione/1f69c.svg emojione/1f69d.svg emojione/1f69e.svg emojione/1f69f.svg emojione/1f91a-1f3fb.svg emojione/1f91a-1f3fc.svg emojione/1f91a-1f3fd.svg emojione/1f91a-1f3fe.svg emojione/1f91a-1f3ff.svg emojione/1f91a.svg emojione/1f91b-1f3fb.svg emojione/1f91b-1f3fc.svg emojione/1f91b-1f3fd.svg emojione/1f91b-1f3fe.svg emojione/1f91b-1f3ff.svg emojione/1f91b.svg emojione/1f91c-1f3fb.svg emojione/1f91c-1f3fc.svg emojione/1f91c-1f3fd.svg emojione/1f91c-1f3fe.svg emojione/1f91c-1f3ff.svg emojione/1f91c.svg emojione/1f91d-1f3fb.svg emojione/1f91d-1f3fc.svg emojione/1f91d-1f3fd.svg emojione/1f91d-1f3fe.svg emojione/1f91d-1f3ff.svg emojione/1f91d.svg emojione/1f91e-1f3fb.svg emojione/1f91e-1f3fc.svg emojione/1f91e-1f3fd.svg emojione/1f91e-1f3fe.svg emojione/1f91e-1f3ff.svg emojione/1f91e.svg emojione/1f93a.svg emojione/1f93b-1f3fb.svg emojione/1f93b-1f3fc.svg emojione/1f93b-1f3fd.svg emojione/1f93b-1f3fe.svg emojione/1f93b-1f3ff.svg emojione/1f93b.svg emojione/1f93c-1f3fb.svg emojione/1f93c-1f3fc.svg emojione/1f93c-1f3fd.svg emojione/1f93c-1f3fe.svg emojione/1f93c-1f3ff.svg emojione/1f93c.svg emojione/1f93d-1f3fb.svg emojione/1f93d-1f3fc.svg emojione/1f93d-1f3fd.svg emojione/1f93d-1f3fe.svg emojione/1f93d-1f3ff.svg emojione/1f93d.svg emojione/1f93e-1f3fb.svg emojione/1f93e-1f3fc.svg emojione/1f93e-1f3fd.svg emojione/1f93e-1f3fe.svg emojione/1f93e-1f3ff.svg emojione/1f93e.svg emojione/1f93f.svg emojione/1f95a.svg emojione/1f95b.svg emojione/1f95c.svg emojione/1f95d.svg emojione/1f95e.svg emojione/1f98a.svg emojione/1f98b.svg emojione/1f98c.svg emojione/1f98d.svg emojione/1f98e.svg emojione/1f98f.svg emojione/1f170.svg emojione/1f171.svg emojione/1f191.svg emojione/1f192.svg emojione/1f193.svg emojione/1f194.svg emojione/1f195.svg emojione/1f196.svg emojione/1f197.svg emojione/1f198.svg emojione/1f199.svg emojione/1f201.svg emojione/1f202.svg emojione/1f232.svg emojione/1f233.svg emojione/1f234.svg emojione/1f235.svg emojione/1f236.svg emojione/1f237.svg emojione/1f238.svg emojione/1f239.svg emojione/1f250.svg emojione/1f251.svg emojione/1f300.svg emojione/1f301.svg emojione/1f302.svg emojione/1f303.svg emojione/1f304.svg emojione/1f305.svg emojione/1f306.svg emojione/1f307.svg emojione/1f308.svg emojione/1f309.svg emojione/1f310.svg emojione/1f311.svg emojione/1f312.svg emojione/1f313.svg emojione/1f314.svg emojione/1f315.svg emojione/1f316.svg emojione/1f317.svg emojione/1f318.svg emojione/1f319.svg emojione/1f320.svg emojione/1f321.svg emojione/1f324.svg emojione/1f325.svg emojione/1f326.svg emojione/1f327.svg emojione/1f328.svg emojione/1f329.svg emojione/1f330.svg emojione/1f331.svg emojione/1f332.svg emojione/1f333.svg emojione/1f334.svg emojione/1f335.svg emojione/1f336.svg emojione/1f337.svg emojione/1f338.svg emojione/1f339.svg emojione/1f340.svg emojione/1f341.svg emojione/1f342.svg emojione/1f343.svg emojione/1f344.svg emojione/1f345.svg emojione/1f346.svg emojione/1f347.svg emojione/1f348.svg emojione/1f349.svg emojione/1f350.svg emojione/1f351.svg emojione/1f352.svg emojione/1f353.svg emojione/1f354.svg emojione/1f355.svg emojione/1f356.svg emojione/1f357.svg emojione/1f358.svg emojione/1f359.svg emojione/1f360.svg emojione/1f361.svg emojione/1f362.svg emojione/1f363.svg emojione/1f364.svg emojione/1f365.svg emojione/1f366.svg emojione/1f367.svg emojione/1f368.svg emojione/1f369.svg emojione/1f370.svg emojione/1f371.svg emojione/1f372.svg emojione/1f373.svg emojione/1f374.svg emojione/1f375.svg emojione/1f376.svg emojione/1f377.svg emojione/1f378.svg emojione/1f379.svg emojione/1f380.svg emojione/1f381.svg emojione/1f382.svg emojione/1f383.svg emojione/1f384.svg emojione/1f385-1f3fb.svg emojione/1f385-1f3fc.svg emojione/1f385-1f3fd.svg emojione/1f385-1f3fe.svg emojione/1f385-1f3ff.svg emojione/1f385.svg emojione/1f386.svg emojione/1f387.svg emojione/1f388.svg emojione/1f389.svg emojione/1f390.svg emojione/1f391.svg emojione/1f392.svg emojione/1f393.svg emojione/1f396.svg emojione/1f397.svg emojione/1f399.svg emojione/1f400.svg emojione/1f401.svg emojione/1f402.svg emojione/1f403.svg emojione/1f404.svg emojione/1f405.svg emojione/1f406.svg emojione/1f407.svg emojione/1f408.svg emojione/1f409.svg emojione/1f410.svg emojione/1f411.svg emojione/1f412.svg emojione/1f413.svg emojione/1f414.svg emojione/1f415.svg emojione/1f416.svg emojione/1f417.svg emojione/1f418.svg emojione/1f419.svg emojione/1f420.svg emojione/1f421.svg emojione/1f422.svg emojione/1f423.svg emojione/1f424.svg emojione/1f425.svg emojione/1f426.svg emojione/1f427.svg emojione/1f428.svg emojione/1f429.svg emojione/1f430.svg emojione/1f431.svg emojione/1f432.svg emojione/1f433.svg emojione/1f434.svg emojione/1f435.svg emojione/1f436.svg emojione/1f437.svg emojione/1f438.svg emojione/1f439.svg emojione/1f440.svg emojione/1f441-1f5e8.svg emojione/1f441.svg emojione/1f442-1f3fb.svg emojione/1f442-1f3fc.svg emojione/1f442-1f3fd.svg emojione/1f442-1f3fe.svg emojione/1f442-1f3ff.svg emojione/1f442.svg emojione/1f443-1f3fb.svg emojione/1f443-1f3fc.svg emojione/1f443-1f3fd.svg emojione/1f443-1f3fe.svg emojione/1f443-1f3ff.svg emojione/1f443.svg emojione/1f444.svg emojione/1f445.svg emojione/1f446-1f3fb.svg emojione/1f446-1f3fc.svg emojione/1f446-1f3fd.svg emojione/1f446-1f3fe.svg emojione/1f446-1f3ff.svg emojione/1f446.svg emojione/1f447-1f3fb.svg emojione/1f447-1f3fc.svg emojione/1f447-1f3fd.svg emojione/1f447-1f3fe.svg emojione/1f447-1f3ff.svg emojione/1f447.svg emojione/1f448-1f3fb.svg emojione/1f448-1f3fc.svg emojione/1f448-1f3fd.svg emojione/1f448-1f3fe.svg emojione/1f448-1f3ff.svg emojione/1f448.svg emojione/1f449-1f3fb.svg emojione/1f449-1f3fc.svg emojione/1f449-1f3fd.svg emojione/1f449-1f3fe.svg emojione/1f449-1f3ff.svg emojione/1f449.svg emojione/1f450-1f3fb.svg emojione/1f450-1f3fc.svg emojione/1f450-1f3fd.svg emojione/1f450-1f3fe.svg emojione/1f450-1f3ff.svg emojione/1f450.svg emojione/1f451.svg emojione/1f452.svg emojione/1f453.svg emojione/1f454.svg emojione/1f455.svg emojione/1f456.svg emojione/1f457.svg emojione/1f458.svg emojione/1f459.svg emojione/1f460.svg emojione/1f461.svg emojione/1f462.svg emojione/1f463.svg emojione/1f464.svg emojione/1f465.svg emojione/1f466-1f3fb.svg emojione/1f466-1f3fc.svg emojione/1f466-1f3fd.svg emojione/1f466-1f3fe.svg emojione/1f466-1f3ff.svg emojione/1f466.svg emojione/1f467-1f3fb.svg emojione/1f467-1f3fc.svg emojione/1f467-1f3fd.svg emojione/1f467-1f3fe.svg emojione/1f467-1f3ff.svg emojione/1f467.svg emojione/1f468-1f3fb.svg emojione/1f468-1f3fc.svg emojione/1f468-1f3fd.svg emojione/1f468-1f3fe.svg emojione/1f468-1f3ff.svg emojione/1f468-1f468-1f466-1f466.svg emojione/1f468-1f468-1f466.svg emojione/1f468-1f468-1f467-1f466.svg emojione/1f468-1f468-1f467-1f467.svg emojione/1f468-1f468-1f467.svg emojione/1f468-1f469-1f466-1f466.svg emojione/1f468-1f469-1f467-1f466.svg emojione/1f468-1f469-1f467-1f467.svg emojione/1f468-1f469-1f467.svg emojione/1f468-2764-1f48b-1f468.svg emojione/1f468-2764-1f468.svg emojione/1f468.svg emojione/1f469-1f3fb.svg emojione/1f469-1f3fc.svg emojione/1f469-1f3fd.svg emojione/1f469-1f3fe.svg emojione/1f469-1f3ff.svg emojione/1f469-1f469-1f466-1f466.svg emojione/1f469-1f469-1f466.svg emojione/1f469-1f469-1f467-1f466.svg emojione/1f469-1f469-1f467-1f467.svg emojione/1f469-1f469-1f467.svg emojione/1f469-2764-1f48b-1f469.svg emojione/1f469-2764-1f469.svg emojione/1f469.svg emojione/1f470-1f3fb.svg emojione/1f470-1f3fc.svg emojione/1f470-1f3fd.svg emojione/1f470-1f3fe.svg emojione/1f470-1f3ff.svg emojione/1f470.svg emojione/1f471-1f3fb.svg emojione/1f471-1f3fc.svg emojione/1f471-1f3fd.svg emojione/1f471-1f3fe.svg emojione/1f471-1f3ff.svg emojione/1f471.svg emojione/1f472-1f3fb.svg emojione/1f472-1f3fc.svg emojione/1f472-1f3fd.svg emojione/1f472-1f3fe.svg emojione/1f472-1f3ff.svg emojione/1f472.svg emojione/1f473-1f3fb.svg emojione/1f473-1f3fc.svg emojione/1f473-1f3fd.svg emojione/1f473-1f3fe.svg emojione/1f473-1f3ff.svg emojione/1f473.svg emojione/1f474-1f3fb.svg emojione/1f474-1f3fc.svg emojione/1f474-1f3fd.svg emojione/1f474-1f3fe.svg emojione/1f474-1f3ff.svg emojione/1f474.svg emojione/1f475-1f3fb.svg emojione/1f475-1f3fc.svg emojione/1f475-1f3fd.svg emojione/1f475-1f3fe.svg emojione/1f475-1f3ff.svg emojione/1f475.svg emojione/1f476-1f3fb.svg emojione/1f476-1f3fc.svg emojione/1f476-1f3fd.svg emojione/1f476-1f3fe.svg emojione/1f476-1f3ff.svg emojione/1f476.svg emojione/1f477-1f3fb.svg emojione/1f477-1f3fc.svg emojione/1f477-1f3fd.svg emojione/1f477-1f3fe.svg emojione/1f477-1f3ff.svg emojione/1f477.svg emojione/1f478-1f3fb.svg emojione/1f478-1f3fc.svg emojione/1f478-1f3fd.svg emojione/1f478-1f3fe.svg emojione/1f478-1f3ff.svg emojione/1f478.svg emojione/1f479.svg emojione/1f480.svg emojione/1f481-1f3fb.svg emojione/1f481-1f3fc.svg emojione/1f481-1f3fd.svg emojione/1f481-1f3fe.svg emojione/1f481-1f3ff.svg emojione/1f481.svg emojione/1f482-1f3fb.svg emojione/1f482-1f3fc.svg emojione/1f482-1f3fd.svg emojione/1f482-1f3fe.svg emojione/1f482-1f3ff.svg emojione/1f482.svg emojione/1f483-1f3fb.svg emojione/1f483-1f3fc.svg emojione/1f483-1f3fd.svg emojione/1f483-1f3fe.svg emojione/1f483-1f3ff.svg emojione/1f483.svg emojione/1f484.svg emojione/1f485-1f3fb.svg emojione/1f485-1f3fc.svg emojione/1f485-1f3fd.svg emojione/1f485-1f3fe.svg emojione/1f485-1f3ff.svg emojione/1f485.svg emojione/1f486-1f3fb.svg emojione/1f486-1f3fc.svg emojione/1f486-1f3fd.svg emojione/1f486-1f3fe.svg emojione/1f486-1f3ff.svg emojione/1f486.svg emojione/1f487-1f3fb.svg emojione/1f487-1f3fc.svg emojione/1f487-1f3fd.svg emojione/1f487-1f3fe.svg emojione/1f487-1f3ff.svg emojione/1f487.svg emojione/1f488.svg emojione/1f489.svg emojione/1f490.svg emojione/1f491.svg emojione/1f492.svg emojione/1f493.svg emojione/1f494.svg emojione/1f495.svg emojione/1f496.svg emojione/1f497.svg emojione/1f498.svg emojione/1f499.svg emojione/1f500.svg emojione/1f501.svg emojione/1f502.svg emojione/1f503.svg emojione/1f504.svg emojione/1f505.svg emojione/1f506.svg emojione/1f507.svg emojione/1f508.svg emojione/1f509.svg emojione/1f510.svg emojione/1f511.svg emojione/1f512.svg emojione/1f513.svg emojione/1f514.svg emojione/1f515.svg emojione/1f516.svg emojione/1f517.svg emojione/1f518.svg emojione/1f519.svg emojione/1f520.svg emojione/1f521.svg emojione/1f522.svg emojione/1f523.svg emojione/1f524.svg emojione/1f525.svg emojione/1f526.svg emojione/1f527.svg emojione/1f528.svg emojione/1f529.svg emojione/1f530.svg emojione/1f531.svg emojione/1f532.svg emojione/1f533.svg emojione/1f534.svg emojione/1f535.svg emojione/1f536.svg emojione/1f537.svg emojione/1f538.svg emojione/1f539.svg emojione/1f549.svg emojione/1f550.svg emojione/1f551.svg emojione/1f552.svg emojione/1f553.svg emojione/1f554.svg emojione/1f555.svg emojione/1f556.svg emojione/1f557.svg emojione/1f558.svg emojione/1f559.svg emojione/1f560.svg emojione/1f561.svg emojione/1f562.svg emojione/1f563.svg emojione/1f564.svg emojione/1f565.svg emojione/1f566.svg emojione/1f567.svg emojione/1f570.svg emojione/1f573.svg emojione/1f574.svg emojione/1f575-1f3fb.svg emojione/1f575-1f3fc.svg emojione/1f575-1f3fd.svg emojione/1f575-1f3fe.svg emojione/1f575-1f3ff.svg emojione/1f575.svg emojione/1f576.svg emojione/1f577.svg emojione/1f578.svg emojione/1f579.svg emojione/1f587.svg emojione/1f590-1f3fb.svg emojione/1f590-1f3fc.svg emojione/1f590-1f3fd.svg emojione/1f590-1f3fe.svg emojione/1f590-1f3ff.svg emojione/1f590.svg emojione/1f595-1f3fb.svg emojione/1f595-1f3fc.svg emojione/1f595-1f3fd.svg emojione/1f595-1f3fe.svg emojione/1f595-1f3ff.svg emojione/1f595.svg emojione/1f596-1f3fb.svg emojione/1f596-1f3fc.svg emojione/1f596-1f3fd.svg emojione/1f596-1f3fe.svg emojione/1f596-1f3ff.svg emojione/1f596.svg emojione/1f600.svg emojione/1f601.svg emojione/1f602.svg emojione/1f603.svg emojione/1f604.svg emojione/1f605.svg emojione/1f606.svg emojione/1f607.svg emojione/1f608.svg emojione/1f609.svg emojione/1f610.svg emojione/1f611.svg emojione/1f612.svg emojione/1f613.svg emojione/1f614.svg emojione/1f615.svg emojione/1f616.svg emojione/1f617.svg emojione/1f618.svg emojione/1f619.svg emojione/1f620.svg emojione/1f621.svg emojione/1f622.svg emojione/1f623.svg emojione/1f624.svg emojione/1f625.svg emojione/1f626.svg emojione/1f627.svg emojione/1f628.svg emojione/1f629.svg emojione/1f630.svg emojione/1f631.svg emojione/1f632.svg emojione/1f633.svg emojione/1f634.svg emojione/1f635.svg emojione/1f636.svg emojione/1f637.svg emojione/1f638.svg emojione/1f639.svg emojione/1f640.svg emojione/1f641.svg emojione/1f642.svg emojione/1f643.svg emojione/1f644.svg emojione/1f645-1f3fb.svg emojione/1f645-1f3fc.svg emojione/1f645-1f3fd.svg emojione/1f645-1f3fe.svg emojione/1f645-1f3ff.svg emojione/1f645.svg emojione/1f646-1f3fb.svg emojione/1f646-1f3fc.svg emojione/1f646-1f3fd.svg emojione/1f646-1f3fe.svg emojione/1f646-1f3ff.svg emojione/1f646.svg emojione/1f647-1f3fb.svg emojione/1f647-1f3fc.svg emojione/1f647-1f3fd.svg emojione/1f647-1f3fe.svg emojione/1f647-1f3ff.svg emojione/1f647.svg emojione/1f648.svg emojione/1f649.svg emojione/1f680.svg emojione/1f681.svg emojione/1f682.svg emojione/1f683.svg emojione/1f684.svg emojione/1f685.svg emojione/1f686.svg emojione/1f687.svg emojione/1f688.svg emojione/1f689.svg emojione/1f690.svg emojione/1f691.svg emojione/1f692.svg emojione/1f693.svg emojione/1f694.svg emojione/1f695.svg emojione/1f696.svg emojione/1f697.svg emojione/1f698.svg emojione/1f699.svg emojione/1f910.svg emojione/1f911.svg emojione/1f912.svg emojione/1f913.svg emojione/1f914.svg emojione/1f915.svg emojione/1f916.svg emojione/1f917.svg emojione/1f918-1f3fb.svg emojione/1f918-1f3fc.svg emojione/1f918-1f3fd.svg emojione/1f918-1f3fe.svg emojione/1f918-1f3ff.svg emojione/1f918.svg emojione/1f919-1f3fb.svg emojione/1f919-1f3fc.svg emojione/1f919-1f3fd.svg emojione/1f919-1f3fe.svg emojione/1f919-1f3ff.svg emojione/1f919.svg emojione/1f920.svg emojione/1f921.svg emojione/1f922.svg emojione/1f923.svg emojione/1f924.svg emojione/1f925.svg emojione/1f926-1f3fb.svg emojione/1f926-1f3fc.svg emojione/1f926-1f3fd.svg emojione/1f926-1f3fe.svg emojione/1f926-1f3ff.svg emojione/1f926.svg emojione/1f927.svg emojione/1f930-1f3fb.svg emojione/1f930-1f3fc.svg emojione/1f930-1f3fd.svg emojione/1f930-1f3fe.svg emojione/1f930-1f3ff.svg emojione/1f930.svg emojione/1f933-1f3fb.svg emojione/1f933-1f3fc.svg emojione/1f933-1f3fd.svg emojione/1f933-1f3fe.svg emojione/1f933-1f3ff.svg emojione/1f933.svg emojione/1f934-1f3fb.svg emojione/1f934-1f3fc.svg emojione/1f934-1f3fd.svg emojione/1f934-1f3fe.svg emojione/1f934-1f3ff.svg emojione/1f934.svg emojione/1f935-1f3fb.svg emojione/1f935-1f3fc.svg emojione/1f935-1f3fd.svg emojione/1f935-1f3fe.svg emojione/1f935-1f3ff.svg emojione/1f935.svg emojione/1f936-1f3fb.svg emojione/1f936-1f3fc.svg emojione/1f936-1f3fd.svg emojione/1f936-1f3fe.svg emojione/1f936-1f3ff.svg emojione/1f936.svg emojione/1f937-1f3fb.svg emojione/1f937-1f3fc.svg emojione/1f937-1f3fd.svg emojione/1f937-1f3fe.svg emojione/1f937-1f3ff.svg emojione/1f937.svg emojione/1f938-1f3fb.svg emojione/1f938-1f3fc.svg emojione/1f938-1f3fd.svg emojione/1f938-1f3fe.svg emojione/1f938-1f3ff.svg emojione/1f938.svg emojione/1f939-1f3fb.svg emojione/1f939-1f3fc.svg emojione/1f939-1f3fd.svg emojione/1f939-1f3fe.svg emojione/1f939-1f3ff.svg emojione/1f939.svg emojione/1f940.svg emojione/1f942.svg emojione/1f943.svg emojione/1f944.svg emojione/1f945.svg emojione/1f946.svg emojione/1f947.svg emojione/1f948.svg emojione/1f949.svg emojione/1f950.svg emojione/1f951.svg emojione/1f952.svg emojione/1f953.svg emojione/1f954.svg emojione/1f955.svg emojione/1f956.svg emojione/1f957.svg emojione/1f958.svg emojione/1f959.svg emojione/1f960.svg emojione/1f961.svg emojione/1f980.svg emojione/1f981.svg emojione/1f982.svg emojione/1f983.svg emojione/1f984.svg emojione/1f985.svg emojione/1f986.svg emojione/1f987.svg emojione/1f988.svg emojione/1f989.svg emojione/1f990.svg emojione/1f991.svg emojione/2b05.svg emojione/2b06.svg emojione/2b07.svg emojione/2b1b.svg emojione/2b1c.svg emojione/2b50.svg emojione/2b55.svg emojione/21a9.svg emojione/21aa.svg emojione/23cf.svg emojione/23e9.svg emojione/23ea.svg emojione/23eb.svg emojione/23ec.svg emojione/23ed.svg emojione/23ee.svg emojione/23ef.svg emojione/23f0.svg emojione/23f1.svg emojione/23f2.svg emojione/23f3.svg emojione/23f8.svg emojione/23f9.svg emojione/23fa.svg emojione/24c2.svg emojione/25aa.svg emojione/25ab.svg emojione/25b6.svg emojione/25c0.svg emojione/25fb.svg emojione/25fc.svg emojione/25fd.svg emojione/25fe.svg emojione/26a0.svg emojione/26a1.svg emojione/26aa.svg emojione/26ab.svg emojione/26b0.svg emojione/26b1.svg emojione/26bd.svg emojione/26be.svg emojione/26c4.svg emojione/26c5.svg emojione/26c8.svg emojione/26ce.svg emojione/26cf.svg emojione/26d1.svg emojione/26d3.svg emojione/26d4.svg emojione/26e9.svg emojione/26ea.svg emojione/26f0.svg emojione/26f1.svg emojione/26f2.svg emojione/26f3.svg emojione/26f4.svg emojione/26f5.svg emojione/26f7.svg emojione/26f8.svg emojione/26f9-1f3fb.svg emojione/26f9-1f3fc.svg emojione/26f9-1f3fd.svg emojione/26f9-1f3fe.svg emojione/26f9-1f3ff.svg emojione/26f9.svg emojione/26fa.svg emojione/26fd.svg emojione/27a1.svg emojione/27b0.svg emojione/27bf.svg emojione/203c.svg emojione/231a.svg emojione/231b.svg emojione/260e.svg emojione/261d-1f3fb.svg emojione/261d-1f3fc.svg emojione/261d-1f3fd.svg emojione/261d-1f3fe.svg emojione/261d-1f3ff.svg emojione/261d.svg emojione/262a.svg emojione/262e.svg emojione/262f.svg emojione/263a.svg emojione/264a.svg emojione/264b.svg emojione/264c.svg emojione/264d.svg emojione/264e.svg emojione/264f.svg emojione/267b.svg emojione/267f.svg emojione/269b.svg emojione/269c.svg emojione/270a-1f3fb.svg emojione/270a-1f3fc.svg emojione/270a-1f3fd.svg emojione/270a-1f3fe.svg emojione/270a-1f3ff.svg emojione/270a.svg emojione/270b-1f3fb.svg emojione/270b-1f3fc.svg emojione/270b-1f3fd.svg emojione/270b-1f3fe.svg emojione/270b-1f3ff.svg emojione/270b.svg emojione/270c-1f3fb.svg emojione/270c-1f3fc.svg emojione/270c-1f3fd.svg emojione/270c-1f3fe.svg emojione/270c-1f3ff.svg emojione/270c.svg emojione/270d-1f3fb.svg emojione/270d-1f3fc.svg emojione/270d-1f3fd.svg emojione/270d-1f3fe.svg emojione/270d-1f3ff.svg emojione/270d.svg emojione/270f.svg emojione/271d.svg emojione/274c.svg emojione/274e.svg emojione/303d.svg emojione/2049.svg emojione/2122.svg emojione/2139.svg emojione/2194.svg emojione/2195.svg emojione/2196.svg emojione/2197.svg emojione/2198.svg emojione/2199.svg emojione/2328.svg emojione/2600.svg emojione/2601.svg emojione/2602.svg emojione/2603.svg emojione/2604.svg emojione/2611.svg emojione/2614.svg emojione/2615.svg emojione/2618.svg emojione/2620.svg emojione/2622.svg emojione/2623.svg emojione/2626.svg emojione/2638.svg emojione/2639.svg emojione/2648.svg emojione/2649.svg emojione/2650.svg emojione/2651.svg emojione/2652.svg emojione/2653.svg emojione/2660.svg emojione/2663.svg emojione/2665.svg emojione/2666.svg emojione/2668.svg emojione/2692.svg emojione/2693.svg emojione/2694.svg emojione/2696.svg emojione/2697.svg emojione/2699.svg emojione/2702.svg emojione/2705.svg emojione/2708.svg emojione/2709.svg emojione/2712.svg emojione/2714.svg emojione/2716.svg emojione/2721.svg emojione/2728.svg emojione/2733.svg emojione/2734.svg emojione/2744.svg emojione/2747.svg emojione/2753.svg emojione/2754.svg emojione/2755.svg emojione/2757.svg emojione/2763.svg emojione/2764.svg emojione/2795.svg emojione/2796.svg emojione/2797.svg emojione/2934.svg emojione/2935.svg emojione/3030.svg emojione/3297.svg emojione/3299.svg emojione/emoticons.xml ASCII+emojione/emoticons.xml qTox/smileys/emojione/000077500000000000000000000000001415623743500153155ustar00rootroot00000000000000qTox/smileys/emojione/0023-20e3.svg000066400000000000000000000013501415623743500170700ustar00rootroot00000000000000qTox/smileys/emojione/0023.svg000066400000000000000000000006331415623743500164240ustar00rootroot00000000000000qTox/smileys/emojione/002a-20e3.svg000066400000000000000000000023411415623743500171470ustar00rootroot00000000000000qTox/smileys/emojione/002a.svg000066400000000000000000000017241415623743500165040ustar00rootroot00000000000000qTox/smileys/emojione/0030-20e3.svg000066400000000000000000000020271415623743500170700ustar00rootroot00000000000000qTox/smileys/emojione/0030.svg000066400000000000000000000014071415623743500164220ustar00rootroot00000000000000qTox/smileys/emojione/0031-20e3.svg000066400000000000000000000010741415623743500170720ustar00rootroot00000000000000qTox/smileys/emojione/0031.svg000066400000000000000000000004561415623743500164260ustar00rootroot00000000000000qTox/smileys/emojione/0032-20e3.svg000066400000000000000000000017441415623743500170770ustar00rootroot00000000000000qTox/smileys/emojione/0032.svg000066400000000000000000000013241415623743500164220ustar00rootroot00000000000000qTox/smileys/emojione/0033-20e3.svg000066400000000000000000000023761415623743500171020ustar00rootroot00000000000000qTox/smileys/emojione/0033.svg000066400000000000000000000017201415623743500164230ustar00rootroot00000000000000qTox/smileys/emojione/0034-20e3.svg000066400000000000000000000010421415623743500170700ustar00rootroot00000000000000qTox/smileys/emojione/0034.svg000066400000000000000000000004211415623743500164210ustar00rootroot00000000000000qTox/smileys/emojione/0035-20e3.svg000066400000000000000000000016761415623743500171060ustar00rootroot00000000000000qTox/smileys/emojione/0035.svg000066400000000000000000000012561415623743500164310ustar00rootroot00000000000000qTox/smileys/emojione/0036-20e3.svg000066400000000000000000000022451415623743500171000ustar00rootroot00000000000000qTox/smileys/emojione/0036.svg000066400000000000000000000016251415623743500164320ustar00rootroot00000000000000qTox/smileys/emojione/0037-20e3.svg000066400000000000000000000011731415623743500171000ustar00rootroot00000000000000qTox/smileys/emojione/0037.svg000066400000000000000000000005531415623743500164320ustar00rootroot00000000000000qTox/smileys/emojione/0038-20e3.svg000066400000000000000000000027071415623743500171050ustar00rootroot00000000000000qTox/smileys/emojione/0038.svg000066400000000000000000000022671415623743500164370ustar00rootroot00000000000000qTox/smileys/emojione/0039-20e3.svg000066400000000000000000000022631415623743500171030ustar00rootroot00000000000000qTox/smileys/emojione/0039.svg000066400000000000000000000016271415623743500164370ustar00rootroot00000000000000qTox/smileys/emojione/00a9.svg000066400000000000000000000021711415623743500165100ustar00rootroot00000000000000qTox/smileys/emojione/00ae.svg000066400000000000000000000010141415623743500165570ustar00rootroot00000000000000qTox/smileys/emojione/1f004.svg000066400000000000000000000024201415623743500165660ustar00rootroot00000000000000qTox/smileys/emojione/1f0cf.svg000066400000000000000000000076511415623743500167460ustar00rootroot00000000000000qTox/smileys/emojione/1f170.svg000066400000000000000000000005461415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f171.svg000066400000000000000000000011211415623743500165700ustar00rootroot00000000000000qTox/smileys/emojione/1f17e.svg000066400000000000000000000007361415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f17f.svg000066400000000000000000000006711415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f18e.svg000066400000000000000000000012621415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f191.svg000066400000000000000000000015731415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f192.svg000066400000000000000000000017601415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f193.svg000066400000000000000000000011141415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f194.svg000066400000000000000000000011571415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f195.svg000066400000000000000000000007701415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f196.svg000066400000000000000000000010761415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f197.svg000066400000000000000000000016761415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f198.svg000066400000000000000000000045431415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f199.svg000066400000000000000000000022351415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f19a.svg000066400000000000000000000022021415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1e8.svg000066400000000000000000000174641415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1e9.svg000066400000000000000000000143011415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1ea.svg000066400000000000000000000007411415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1eb.svg000066400000000000000000000155171415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1ec.svg000066400000000000000000000012601415623743500175540ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1ee.svg000066400000000000000000000025461415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1f1.svg000066400000000000000000000066311415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1f2.svg000066400000000000000000000006611415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1f4.svg000066400000000000000000000034641415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1f6.svg000066400000000000000000000020701415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1f7.svg000066400000000000000000000171301415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1f8.svg000066400000000000000000000157601415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1f9.svg000066400000000000000000000006231415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1fa.svg000066400000000000000000000021011415623743500175460ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1fc.svg000066400000000000000000000013701415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1fd.svg000066400000000000000000000017051415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6-1f1ff.svg000066400000000000000000000016001415623743500175560ustar00rootroot00000000000000qTox/smileys/emojione/1f1e6.svg000066400000000000000000000004541415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1e6.svg000066400000000000000000000021101415623743500174730ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1e7.svg000066400000000000000000000014711415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1e9.svg000066400000000000000000000003521415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1ea.svg000066400000000000000000000006611415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1eb.svg000066400000000000000000000006061415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1ec.svg000066400000000000000000000006561415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1ed.svg000066400000000000000000000006621415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1ee.svg000066400000000000000000000030031415623743500175540ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1ef.svg000066400000000000000000000005671415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1f1.svg000066400000000000000000000302451415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1f2.svg000066400000000000000000000103341415623743500174770ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1f3.svg000066400000000000000000000032011415623743500174730ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1f4.svg000066400000000000000000000167331415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1f6.svg000066400000000000000000000020431415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1f7.svg000066400000000000000000000026651415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1f8.svg000066400000000000000000000011531415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1f9.svg000066400000000000000000000147121415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1fb.svg000066400000000000000000000017071415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1fc.svg000066400000000000000000000011101415623743500175500ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1fe.svg000066400000000000000000000165451415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7-1f1ff.svg000066400000000000000000000151031415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1e7.svg000066400000000000000000000014661415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1e6.svg000066400000000000000000000020631415623743500175030ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1e8.svg000066400000000000000000000072631415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1e9.svg000066400000000000000000000017121415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1eb.svg000066400000000000000000000014131415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1ec.svg000066400000000000000000000010571415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1ed.svg000066400000000000000000000003101415623743500175520ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1ee.svg000066400000000000000000000006621415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1f0.svg000066400000000000000000000045441415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1f1.svg000066400000000000000000000006311415623743500174760ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1f2.svg000066400000000000000000000010671415623743500175030ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1f3.svg000066400000000000000000000012531415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1f4.svg000066400000000000000000000006111415623743500174770ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1f5.svg000066400000000000000000000006621415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1f7.svg000066400000000000000000000011401415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1fa.svg000066400000000000000000000013171415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1fb.svg000066400000000000000000000030461415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1fc.svg000066400000000000000000000012131415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1fd.svg000066400000000000000000000056561415623743500175750ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1fe.svg000066400000000000000000000107251415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8-1f1ff.svg000066400000000000000000000006671415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f1e8.svg000066400000000000000000000013261415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f1e9-1f1ea.svg000066400000000000000000000007111415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1e9-1f1ec.svg000066400000000000000000000200201415623743500175520ustar00rootroot00000000000000qTox/smileys/emojione/1f1e9-1f1ef.svg000066400000000000000000000010251415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1e9-1f1f0.svg000066400000000000000000000011521415623743500174750ustar00rootroot00000000000000qTox/smileys/emojione/1f1e9-1f1f2.svg000066400000000000000000000053461415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f1e9-1f1f4.svg000066400000000000000000000162211415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f1e9-1f1ff.svg000066400000000000000000000012131415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1e9.svg000066400000000000000000000010301415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1e6.svg000066400000000000000000000153251415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1e8.svg000066400000000000000000000127341415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1ea.svg000066400000000000000000000006621415623743500176320ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1ec.svg000066400000000000000000000076661415623743500176470ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1ed.svg000066400000000000000000000016551415623743500176400ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1f7.svg000066400000000000000000000072411415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1f8.svg000066400000000000000000000153531415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1f9.svg000066400000000000000000000022611415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea-1f1fa.svg000066400000000000000000000026611415623743500176340ustar00rootroot00000000000000qTox/smileys/emojione/1f1ea.svg000066400000000000000000000003751415623743500167400ustar00rootroot00000000000000qTox/smileys/emojione/1f1eb-1f1ee.svg000066400000000000000000000011541415623743500176340ustar00rootroot00000000000000qTox/smileys/emojione/1f1eb-1f1ef.svg000066400000000000000000000132561415623743500176430ustar00rootroot00000000000000qTox/smileys/emojione/1f1eb-1f1f0.svg000066400000000000000000000105271415623743500175540ustar00rootroot00000000000000qTox/smileys/emojione/1f1eb-1f1f2.svg000066400000000000000000000011441415623743500175510ustar00rootroot00000000000000qTox/smileys/emojione/1f1eb-1f1f4.svg000066400000000000000000000017121415623743500175540ustar00rootroot00000000000000qTox/smileys/emojione/1f1eb-1f1f7.svg000066400000000000000000000007141415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1eb.svg000066400000000000000000000003601415623743500167330ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1e6.svg000066400000000000000000000007121415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1e7.svg000066400000000000000000000043071415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1e9.svg000066400000000000000000000035151415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1ea.svg000066400000000000000000000016421415623743500176330ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1eb.svg000066400000000000000000000007521415623743500176350ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1ec.svg000066400000000000000000000013231415623743500176310ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1ed.svg000066400000000000000000000010611415623743500176310ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1ee.svg000066400000000000000000000033441415623743500176400ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1f1.svg000066400000000000000000000006571415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1f2.svg000066400000000000000000000012441415623743500175530ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1f3.svg000066400000000000000000000006611415623743500175560ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1f5.svg000066400000000000000000000176301415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1f6.svg000066400000000000000000000061251415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1f7.svg000066400000000000000000000017771415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1f8.svg000066400000000000000000000232161415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1f9.svg000066400000000000000000000136111415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1fa.svg000066400000000000000000000023341415623743500176330ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1fc.svg000066400000000000000000000007461415623743500176420ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec-1f1fe.svg000066400000000000000000000017671415623743500176500ustar00rootroot00000000000000qTox/smileys/emojione/1f1ec.svg000066400000000000000000000013461415623743500167410ustar00rootroot00000000000000qTox/smileys/emojione/1f1ed-1f1f0.svg000066400000000000000000000034401415623743500175520ustar00rootroot00000000000000qTox/smileys/emojione/1f1ed-1f1f2.svg000066400000000000000000000031471415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1ed-1f1f3.svg000066400000000000000000000016671415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f1ed-1f1f7.svg000066400000000000000000000114561415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f1ed-1f1f9.svg000066400000000000000000000116211415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1ed-1f1fa.svg000066400000000000000000000006611415623743500176350ustar00rootroot00000000000000qTox/smileys/emojione/1f1ed.svg000066400000000000000000000003761415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1e8.svg000066400000000000000000000177151415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1e9.svg000066400000000000000000000004201415623743500175560ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1ea.svg000066400000000000000000000007141415623743500176340ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1f1.svg000066400000000000000000000020031415623743500175460ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1f2.svg000066400000000000000000000041021415623743500175510ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1f3.svg000066400000000000000000000064221415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1f4.svg000066400000000000000000000200201415623743500175500ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1f6.svg000066400000000000000000000044151415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1f7.svg000066400000000000000000000117701415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1f8.svg000066400000000000000000000017111415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee-1f1f9.svg000066400000000000000000000007151415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f1ee.svg000066400000000000000000000003121415623743500167330ustar00rootroot00000000000000qTox/smileys/emojione/1f1ef-1f1ea.svg000066400000000000000000000157631415623743500176470ustar00rootroot00000000000000qTox/smileys/emojione/1f1ef-1f1f2.svg000066400000000000000000000012711415623743500175560ustar00rootroot00000000000000qTox/smileys/emojione/1f1ef-1f1f4.svg000066400000000000000000000012301415623743500175530ustar00rootroot00000000000000qTox/smileys/emojione/1f1ef-1f1f5.svg000066400000000000000000000002671415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f1ef.svg000066400000000000000000000007141415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1ea.svg000066400000000000000000000041421415623743500175470ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1ec.svg000066400000000000000000000123511415623743500175520ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1ed.svg000066400000000000000000000151741415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1ee.svg000066400000000000000000000072011415623743500175520ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1f2.svg000066400000000000000000000021411415623743500174660ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1f3.svg000066400000000000000000000020141415623743500174660ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1f5.svg000066400000000000000000000014111415623743500174700ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1f7.svg000066400000000000000000000035511415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1fc.svg000066400000000000000000000012141415623743500175470ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1fe.svg000066400000000000000000000120631415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0-1f1ff.svg000066400000000000000000000133601415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1f0.svg000066400000000000000000000004351415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1e6.svg000066400000000000000000000007201415623743500174730ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1e7.svg000066400000000000000000000030561415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1e8.svg000066400000000000000000000004061415623743500174760ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1ee.svg000066400000000000000000000071351415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1f0.svg000066400000000000000000000070001415623743500174640ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1f7.svg000066400000000000000000000017761415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1f8.svg000066400000000000000000000015621415623743500175030ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1f9.svg000066400000000000000000000006611415623743500175030ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1fa.svg000066400000000000000000000006621415623743500175540ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1fb.svg000066400000000000000000000010161415623743500175470ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1-1f1fe.svg000066400000000000000000000014451415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1f1.svg000066400000000000000000000003271415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1e6.svg000066400000000000000000000010441415623743500174740ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1e8.svg000066400000000000000000000004021415623743500174730ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1e9.svg000066400000000000000000000137631415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1ea.svg000066400000000000000000000144511415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1eb.svg000066400000000000000000000006621415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1ec.svg000066400000000000000000000005561415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1ed.svg000066400000000000000000000022201415623743500175470ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f0.svg000066400000000000000000000024621415623743500174740ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f1.svg000066400000000000000000000006621415623743500174750ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f2.svg000066400000000000000000000010711415623743500174710ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f3.svg000066400000000000000000000036171415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f4.svg000066400000000000000000000027451415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f5.svg000066400000000000000000000172011415623743500174760ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f6.svg000066400000000000000000000130621415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f7.svg000066400000000000000000000006651415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f8.svg000066400000000000000000000054251415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1f9.svg000066400000000000000000000134261415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1fa.svg000066400000000000000000000007461415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1fb.svg000066400000000000000000000011241415623743500175500ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1fc.svg000066400000000000000000000113611415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1fd.svg000066400000000000000000000145101415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1fe.svg000066400000000000000000000030041415623743500175520ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2-1f1ff.svg000066400000000000000000000056131415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1f2.svg000066400000000000000000000006131415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1e6.svg000066400000000000000000000026511415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1e8.svg000066400000000000000000000041211415623743500174760ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1ea.svg000066400000000000000000000007371415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1eb.svg000066400000000000000000000240171415623743500175560ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1ec.svg000066400000000000000000000006271415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1ee.svg000066400000000000000000000042041415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1f1.svg000066400000000000000000000006701415623743500174750ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1f4.svg000066400000000000000000000017041415623743500174770ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1f5.svg000066400000000000000000000017571415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1f7.svg000066400000000000000000000013701415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1fa.svg000066400000000000000000000047601415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3-1f1ff.svg000066400000000000000000000033041415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1f3.svg000066400000000000000000000003761415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f1f4-1f1f2.svg000066400000000000000000000026741415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f1f4.svg000066400000000000000000000012301415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1e6.svg000066400000000000000000000010471415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1ea.svg000066400000000000000000000006261415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1eb.svg000066400000000000000000000111031415623743500175500ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1ec.svg000066400000000000000000000057541415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1ed.svg000066400000000000000000000044431415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1f0.svg000066400000000000000000000012271415623743500174750ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1f1.svg000066400000000000000000000004021415623743500174700ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1f2.svg000066400000000000000000000304441415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1f3.svg000066400000000000000000000167471415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1f7.svg000066400000000000000000000014271415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1f8.svg000066400000000000000000000012031415623743500174770ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1f9.svg000066400000000000000000000166541415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1fc.svg000066400000000000000000000002671415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5-1f1fe.svg000066400000000000000000000120331415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1f5.svg000066400000000000000000000007511415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f1f6-1f1e6.svg000066400000000000000000000010711415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f1f6.svg000066400000000000000000000016211415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f1f7-1f1ea.svg000066400000000000000000000014321415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f7-1f1f4.svg000066400000000000000000000006621415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f1f7-1f1f8.svg000066400000000000000000000124621415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f1f7-1f1fa.svg000066400000000000000000000007371415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f1f7-1f1fc.svg000066400000000000000000000023371415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f1f7.svg000066400000000000000000000014601415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1e6.svg000066400000000000000000000157171415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1e7.svg000066400000000000000000000023351415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1e8.svg000066400000000000000000000014231415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1e9.svg000066400000000000000000000012061415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1ea.svg000066400000000000000000000011361415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1ec.svg000066400000000000000000000020161415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1ed.svg000066400000000000000000000065621415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1ee.svg000066400000000000000000000037651415623743500175750ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1ef.svg000066400000000000000000000017061415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1f0.svg000066400000000000000000000017651415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1f1.svg000066400000000000000000000006611415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1f2.svg000066400000000000000000000131521415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1f3.svg000066400000000000000000000011001415623743500174710ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1f4.svg000066400000000000000000000004241415623743500175020ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1f7.svg000066400000000000000000000013071415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1f8.svg000066400000000000000000000016611415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1f9.svg000066400000000000000000000015241415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1fb.svg000066400000000000000000000123611415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1fd.svg000066400000000000000000000076711415623743500175750ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1fe.svg000066400000000000000000000012031415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8-1f1ff.svg000066400000000000000000000056021415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f1f8.svg000066400000000000000000000017301415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1e6.svg000066400000000000000000000246701415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1e8.svg000066400000000000000000000121671415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1e9.svg000066400000000000000000000006571415623743500175170ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1eb.svg000066400000000000000000000027671415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1ec.svg000066400000000000000000000013171415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1ed.svg000066400000000000000000000011211415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1ef.svg000066400000000000000000000047761415623743500176020ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1f0.svg000066400000000000000000000020221415623743500174730ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1f1.svg000066400000000000000000000011241415623743500174760ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1f2.svg000066400000000000000000000045411415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1f3.svg000066400000000000000000000006421415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1f4.svg000066400000000000000000000005251415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1f7.svg000066400000000000000000000010111415623743500174770ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1f9.svg000066400000000000000000000015331415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1fb.svg000066400000000000000000000027771415623743500175760ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1fc.svg000066400000000000000000000011621415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9-1f1ff.svg000066400000000000000000000015761415623743500175760ustar00rootroot00000000000000qTox/smileys/emojione/1f1f9.svg000066400000000000000000000003451415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f1fa-1f1e6.svg000066400000000000000000000004021415623743500175500ustar00rootroot00000000000000qTox/smileys/emojione/1f1fa-1f1ec.svg000066400000000000000000000027151415623743500176360ustar00rootroot00000000000000qTox/smileys/emojione/1f1fa-1f1f2.svg000066400000000000000000000057331415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1fa-1f1f8.svg000066400000000000000000000057361415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f1fa-1f1fe.svg000066400000000000000000000100241415623743500176310ustar00rootroot00000000000000qTox/smileys/emojione/1f1fa-1f1ff.svg000066400000000000000000000041651415623743500176430ustar00rootroot00000000000000qTox/smileys/emojione/1f1fa.svg000066400000000000000000000007271415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f1fb-1f1e6.svg000066400000000000000000000100751415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1fb-1f1e8.svg000066400000000000000000000011501415623743500175540ustar00rootroot00000000000000qTox/smileys/emojione/1f1fb-1f1ea.svg000066400000000000000000000024541415623743500176350ustar00rootroot00000000000000qTox/smileys/emojione/1f1fb-1f1ec.svg000066400000000000000000000261441415623743500176410ustar00rootroot00000000000000qTox/smileys/emojione/1f1fb-1f1ee.svg000066400000000000000000000174601415623743500176440ustar00rootroot00000000000000qTox/smileys/emojione/1f1fb-1f1f3.svg000066400000000000000000000004001415623743500175450ustar00rootroot00000000000000qTox/smileys/emojione/1f1fb-1f1fa.svg000066400000000000000000000034421415623743500176340ustar00rootroot00000000000000qTox/smileys/emojione/1f1fb.svg000066400000000000000000000003661415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f1fc-1f1eb.svg000066400000000000000000000006621415623743500176360ustar00rootroot00000000000000qTox/smileys/emojione/1f1fc-1f1f8.svg000066400000000000000000000014151415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f1fc.svg000066400000000000000000000005701415623743500167400ustar00rootroot00000000000000qTox/smileys/emojione/1f1fd-1f1f0.svg000066400000000000000000000026351415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f1fd.svg000066400000000000000000000004561415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f1fe-1f1ea.svg000066400000000000000000000006611415623743500176360ustar00rootroot00000000000000qTox/smileys/emojione/1f1fe-1f1f9.svg000066400000000000000000000246541415623743500175770ustar00rootroot00000000000000qTox/smileys/emojione/1f1fe.svg000066400000000000000000000004051415623743500167370ustar00rootroot00000000000000qTox/smileys/emojione/1f1ff-1f1e6.svg000066400000000000000000000023321415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f1ff-1f1f2.svg000066400000000000000000000111671415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f1ff-1f1fc.svg000066400000000000000000000070241415623743500176420ustar00rootroot00000000000000qTox/smileys/emojione/1f1ff.svg000066400000000000000000000003741415623743500167450ustar00rootroot00000000000000qTox/smileys/emojione/1f201.svg000066400000000000000000000005471415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f202.svg000066400000000000000000000013571415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f21a.svg000066400000000000000000000022231415623743500166470ustar00rootroot00000000000000qTox/smileys/emojione/1f22f.svg000066400000000000000000000020441415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f232.svg000066400000000000000000000024561415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f233.svg000066400000000000000000000013301415623743500165710ustar00rootroot00000000000000qTox/smileys/emojione/1f234.svg000066400000000000000000000010271415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f235.svg000066400000000000000000000020531415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f236.svg000066400000000000000000000014151415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f237.svg000066400000000000000000000012141415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f238.svg000066400000000000000000000010051415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f239.svg000066400000000000000000000014671415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f23a.svg000066400000000000000000000014741415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f250.svg000066400000000000000000000021511415623743500165720ustar00rootroot00000000000000qTox/smileys/emojione/1f251.svg000066400000000000000000000010271415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f300.svg000066400000000000000000000020001415623743500165570ustar00rootroot00000000000000qTox/smileys/emojione/1f301.svg000066400000000000000000000076221415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f302.svg000066400000000000000000000022341415623743500165720ustar00rootroot00000000000000qTox/smileys/emojione/1f303.svg000066400000000000000000000044221415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f304.svg000066400000000000000000000140621415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f305.svg000066400000000000000000000113321415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f306.svg000066400000000000000000000030731415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f307.svg000066400000000000000000000036651415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f308.svg000066400000000000000000000043531415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f309.svg000066400000000000000000000033731415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f30a.svg000066400000000000000000000070371415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f30b.svg000066400000000000000000000070331415623743500166540ustar00rootroot00000000000000qTox/smileys/emojione/1f30c.svg000066400000000000000000000030721415623743500166540ustar00rootroot00000000000000qTox/smileys/emojione/1f30d.svg000066400000000000000000000076051415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f30e.svg000066400000000000000000000124171415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f30f.svg000066400000000000000000000064221415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f310.svg000066400000000000000000000035721415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f311.svg000066400000000000000000000012641415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f312.svg000066400000000000000000000031251415623743500165730ustar00rootroot00000000000000qTox/smileys/emojione/1f313.svg000066400000000000000000000027131415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f314.svg000066400000000000000000000034111415623743500165730ustar00rootroot00000000000000qTox/smileys/emojione/1f315.svg000066400000000000000000000012751415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f316.svg000066400000000000000000000031741415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f317.svg000066400000000000000000000027361415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f318.svg000066400000000000000000000034111415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f319.svg000066400000000000000000000004771415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f31a.svg000066400000000000000000000040541415623743500166540ustar00rootroot00000000000000qTox/smileys/emojione/1f31b.svg000066400000000000000000000054001415623743500166510ustar00rootroot00000000000000qTox/smileys/emojione/1f31c.svg000066400000000000000000000054031415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f31d.svg000066400000000000000000000025171415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f31e.svg000066400000000000000000000037371415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f31f.svg000066400000000000000000000010051415623743500166520ustar00rootroot00000000000000qTox/smileys/emojione/1f320.svg000066400000000000000000000011331415623743500165670ustar00rootroot00000000000000qTox/smileys/emojione/1f321.svg000066400000000000000000000016441415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f324.svg000066400000000000000000000051161415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f325.svg000066400000000000000000000050341415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f326.svg000066400000000000000000000072131415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f327.svg000066400000000000000000000053431415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f328.svg000066400000000000000000000053071415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f329.svg000066400000000000000000000033701415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f32a.svg000066400000000000000000000060011415623743500166470ustar00rootroot00000000000000qTox/smileys/emojione/1f32b.svg000066400000000000000000000020051415623743500166500ustar00rootroot00000000000000qTox/smileys/emojione/1f32c.svg000066400000000000000000000070751415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f32d.svg000066400000000000000000000060711415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f32e.svg000066400000000000000000000133371415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f32f.svg000066400000000000000000000132321415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f330.svg000066400000000000000000000041401415623743500165710ustar00rootroot00000000000000qTox/smileys/emojione/1f331.svg000066400000000000000000000023031415623743500165710ustar00rootroot00000000000000qTox/smileys/emojione/1f332.svg000066400000000000000000000010151415623743500165710ustar00rootroot00000000000000qTox/smileys/emojione/1f333.svg000066400000000000000000000070071415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f334.svg000066400000000000000000000047621415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f335.svg000066400000000000000000000042661415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f336.svg000066400000000000000000000014741415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f337.svg000066400000000000000000000016221415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f338.svg000066400000000000000000000074721415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f339.svg000066400000000000000000000027411415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f33a.svg000066400000000000000000000067421415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f33b.svg000066400000000000000000000101151415623743500166520ustar00rootroot00000000000000qTox/smileys/emojione/1f33c.svg000066400000000000000000000030271415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f33d.svg000066400000000000000000000133141415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f33e.svg000066400000000000000000000117571415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f33f.svg000066400000000000000000000056531415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f340.svg000066400000000000000000000046351415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f341.svg000066400000000000000000000056471415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f342.svg000066400000000000000000000016441415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f343.svg000066400000000000000000000036361415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f344.svg000066400000000000000000000023421415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f345.svg000066400000000000000000000022651415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f346.svg000066400000000000000000000020631415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f347.svg000066400000000000000000000031231415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f348.svg000066400000000000000000000130031415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f349.svg000066400000000000000000000030601415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f34a.svg000066400000000000000000000046431415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f34b.svg000066400000000000000000000027721415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f34c.svg000066400000000000000000000033511415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f34d.svg000066400000000000000000000136461415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f34e.svg000066400000000000000000000012501415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f34f.svg000066400000000000000000000012621415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f350.svg000066400000000000000000000020201415623743500165660ustar00rootroot00000000000000qTox/smileys/emojione/1f351.svg000066400000000000000000000036161415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f352.svg000066400000000000000000000027511415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f353.svg000066400000000000000000000065461415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f354.svg000066400000000000000000000066051415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f355.svg000066400000000000000000000063101415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f356.svg000066400000000000000000000047231415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f357.svg000066400000000000000000000035531415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f358.svg000066400000000000000000000114551415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f359.svg000066400000000000000000000036271415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f35a.svg000066400000000000000000000033001415623743500166510ustar00rootroot00000000000000qTox/smileys/emojione/1f35b.svg000066400000000000000000000070701415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f35c.svg000066400000000000000000000076731415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f35d.svg000066400000000000000000000105731415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f35e.svg000066400000000000000000000052131415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f35f.svg000066400000000000000000000105021415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f360.svg000066400000000000000000000016331415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f361.svg000066400000000000000000000017601415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f362.svg000066400000000000000000000021241415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f363.svg000066400000000000000000000124471415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f364.svg000066400000000000000000000112711415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f365.svg000066400000000000000000000064061415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f366.svg000066400000000000000000000065761415623743500166210ustar00rootroot00000000000000qTox/smileys/emojione/1f367.svg000066400000000000000000000074001415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f368.svg000066400000000000000000000056151415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f369.svg000066400000000000000000000043741415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f36a.svg000066400000000000000000000074661415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f36b.svg000066400000000000000000000046521415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f36c.svg000066400000000000000000000047121415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f36d.svg000066400000000000000000000252351415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f36e.svg000066400000000000000000000023141415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f36f.svg000066400000000000000000000027401415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f370.svg000066400000000000000000000102041415623743500165730ustar00rootroot00000000000000qTox/smileys/emojione/1f371.svg000066400000000000000000000144571415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f372.svg000066400000000000000000000066251415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f373.svg000066400000000000000000000013441415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f374.svg000066400000000000000000000015071415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f375.svg000066400000000000000000000031751415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f376.svg000066400000000000000000000023701415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f377.svg000066400000000000000000000014371415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f378.svg000066400000000000000000000027761415623743500166220ustar00rootroot00000000000000qTox/smileys/emojione/1f379.svg000066400000000000000000000062041415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f37a.svg000066400000000000000000000122031415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f37b.svg000066400000000000000000000103741415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f37c.svg000066400000000000000000000031021415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f37d.svg000066400000000000000000000022461415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f37e.svg000066400000000000000000000075641415623743500166770ustar00rootroot00000000000000qTox/smileys/emojione/1f37f.svg000066400000000000000000000111721415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f380.svg000066400000000000000000000033241415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f381.svg000066400000000000000000000044371415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f382.svg000066400000000000000000000101701415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f383.svg000066400000000000000000000056661415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f384.svg000066400000000000000000000032361415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f385-1f3fb.svg000066400000000000000000000054711415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f385-1f3fc.svg000066400000000000000000000053701415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f385-1f3fd.svg000066400000000000000000000053541415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f385-1f3fe.svg000066400000000000000000000053401415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f385-1f3ff.svg000066400000000000000000000053531415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f385.svg000066400000000000000000000054751415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f386.svg000066400000000000000000000071501415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f387.svg000066400000000000000000000017271415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f388.svg000066400000000000000000000017231415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f389.svg000066400000000000000000000057111415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f38a.svg000066400000000000000000000063351415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f38b.svg000066400000000000000000000036721415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f38c.svg000066400000000000000000000020211415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f38d.svg000066400000000000000000000112631415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f38e.svg000066400000000000000000000105271415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f38f.svg000066400000000000000000000113411415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f390.svg000066400000000000000000000043061415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f391.svg000066400000000000000000000134551415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f392.svg000066400000000000000000000103511415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f393.svg000066400000000000000000000016731415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f396.svg000066400000000000000000000023641415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f397.svg000066400000000000000000000010601415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f399.svg000066400000000000000000000075461415623743500166250ustar00rootroot00000000000000qTox/smileys/emojione/1f39a.svg000066400000000000000000000014251415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f39b.svg000066400000000000000000000016411415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f39e.svg000066400000000000000000000043721415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f39f.svg000066400000000000000000000157171415623743500167010ustar00rootroot00000000000000qTox/smileys/emojione/1f3a0.svg000066400000000000000000000075511415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f3a1.svg000066400000000000000000000072071415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f3a2.svg000066400000000000000000000115261415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f3a3.svg000066400000000000000000000065011415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f3a4.svg000066400000000000000000000045001415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f3a5.svg000066400000000000000000000017311415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f3a6.svg000066400000000000000000000007471415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f3a7.svg000066400000000000000000000026551415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f3a8.svg000066400000000000000000000032751415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f3a9.svg000066400000000000000000000015171415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f3aa.svg000066400000000000000000000035741415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f3ab.svg000066400000000000000000000121371415623743500167360ustar00rootroot00000000000000qTox/smileys/emojione/1f3ac.svg000066400000000000000000000043701415623743500167370ustar00rootroot00000000000000qTox/smileys/emojione/1f3ad.svg000066400000000000000000000126621415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f3ae.svg000066400000000000000000000036541415623743500167450ustar00rootroot00000000000000qTox/smileys/emojione/1f3af.svg000066400000000000000000000040501415623743500167350ustar00rootroot00000000000000qTox/smileys/emojione/1f3b0.svg000066400000000000000000000132201415623743500166470ustar00rootroot00000000000000qTox/smileys/emojione/1f3b1.svg000066400000000000000000000015311415623743500166520ustar00rootroot00000000000000qTox/smileys/emojione/1f3b2.svg000066400000000000000000000050711415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f3b3.svg000066400000000000000000000054501415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f3b4.svg000066400000000000000000000007671415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f3b5.svg000066400000000000000000000006461415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f3b6.svg000066400000000000000000000013771415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f3b7.svg000066400000000000000000000057211415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f3b8.svg000066400000000000000000000072661415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f3b9.svg000066400000000000000000000053011415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f3ba.svg000066400000000000000000000040521415623743500167330ustar00rootroot00000000000000qTox/smileys/emojione/1f3bb.svg000066400000000000000000000053741415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f3bc.svg000066400000000000000000000045211415623743500167360ustar00rootroot00000000000000qTox/smileys/emojione/1f3bd.svg000066400000000000000000000016231415623743500167370ustar00rootroot00000000000000qTox/smileys/emojione/1f3be.svg000066400000000000000000000067261415623743500167510ustar00rootroot00000000000000qTox/smileys/emojione/1f3bf.svg000066400000000000000000000064211415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f3c0.svg000066400000000000000000000066661415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f3c1.svg000066400000000000000000000043211415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f3c2.svg000066400000000000000000000133651415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f3c3-1f3fb.svg000066400000000000000000000053121415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f3c3-1f3fc.svg000066400000000000000000000052731415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f3c3-1f3fd.svg000066400000000000000000000052751415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f3c3-1f3fe.svg000066400000000000000000000053141415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f3c3-1f3ff.svg000066400000000000000000000053121415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f3c3.svg000066400000000000000000000052771415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f3c4-1f3fb.svg000066400000000000000000000121441415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f3c4-1f3fc.svg000066400000000000000000000121441415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f3c4-1f3fd.svg000066400000000000000000000121441415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f3c4-1f3fe.svg000066400000000000000000000121441415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f3c4-1f3ff.svg000066400000000000000000000121441415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f3c4.svg000066400000000000000000000121441415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f3c5.svg000066400000000000000000000043041415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f3c6.svg000066400000000000000000000033771415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f3c7-1f3fb.svg000066400000000000000000000143171415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f3c7-1f3fc.svg000066400000000000000000000143171415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f3c7-1f3fd.svg000066400000000000000000000143171415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f3c7-1f3fe.svg000066400000000000000000000143171415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f3c7-1f3ff.svg000066400000000000000000000143171415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f3c7.svg000066400000000000000000000143121415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f3c8.svg000066400000000000000000000037301415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f3c9.svg000066400000000000000000000035121415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f3ca-1f3fb.svg000066400000000000000000000072011415623743500176320ustar00rootroot00000000000000qTox/smileys/emojione/1f3ca-1f3fc.svg000066400000000000000000000072011415623743500176330ustar00rootroot00000000000000qTox/smileys/emojione/1f3ca-1f3fd.svg000066400000000000000000000072011415623743500176340ustar00rootroot00000000000000qTox/smileys/emojione/1f3ca-1f3fe.svg000066400000000000000000000072011415623743500176350ustar00rootroot00000000000000qTox/smileys/emojione/1f3ca-1f3ff.svg000066400000000000000000000072011415623743500176360ustar00rootroot00000000000000qTox/smileys/emojione/1f3ca.svg000066400000000000000000000072001415623743500167320ustar00rootroot00000000000000qTox/smileys/emojione/1f3cb-1f3fb.svg000066400000000000000000000072511415623743500176400ustar00rootroot00000000000000qTox/smileys/emojione/1f3cb-1f3fc.svg000066400000000000000000000072511415623743500176410ustar00rootroot00000000000000qTox/smileys/emojione/1f3cb-1f3fd.svg000066400000000000000000000072511415623743500176420ustar00rootroot00000000000000qTox/smileys/emojione/1f3cb-1f3fe.svg000066400000000000000000000072511415623743500176430ustar00rootroot00000000000000qTox/smileys/emojione/1f3cb-1f3ff.svg000066400000000000000000000072511415623743500176440ustar00rootroot00000000000000qTox/smileys/emojione/1f3cb.svg000066400000000000000000000072511415623743500167410ustar00rootroot00000000000000qTox/smileys/emojione/1f3cc.svg000066400000000000000000000066701415623743500167460ustar00rootroot00000000000000qTox/smileys/emojione/1f3cd.svg000066400000000000000000000106011415623743500167340ustar00rootroot00000000000000qTox/smileys/emojione/1f3ce.svg000066400000000000000000000076701415623743500167510ustar00rootroot00000000000000qTox/smileys/emojione/1f3cf.svg000066400000000000000000000041441415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f3d0.svg000066400000000000000000000025051415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f3d1.svg000066400000000000000000000017721415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f3d2.svg000066400000000000000000000025571415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f3d3.svg000066400000000000000000000025351415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f3d4.svg000066400000000000000000000066131415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f3d5.svg000066400000000000000000000043531415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f3d6.svg000066400000000000000000000031451415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f3d7.svg000066400000000000000000000030511415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f3d8.svg000066400000000000000000000075631415623743500166760ustar00rootroot00000000000000qTox/smileys/emojione/1f3d9.svg000066400000000000000000000054051415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f3da.svg000066400000000000000000000017471415623743500167450ustar00rootroot00000000000000qTox/smileys/emojione/1f3db.svg000066400000000000000000000062121415623743500167360ustar00rootroot00000000000000qTox/smileys/emojione/1f3dc.svg000066400000000000000000000104611415623743500167400ustar00rootroot00000000000000qTox/smileys/emojione/1f3dd.svg000066400000000000000000000047511415623743500167460ustar00rootroot00000000000000qTox/smileys/emojione/1f3de.svg000066400000000000000000000040721415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f3df.svg000066400000000000000000000077461415623743500167570ustar00rootroot00000000000000qTox/smileys/emojione/1f3e0.svg000066400000000000000000000100161415623743500166520ustar00rootroot00000000000000qTox/smileys/emojione/1f3e1.svg000066400000000000000000000134721415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f3e2.svg000066400000000000000000000051311415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f3e3.svg000066400000000000000000000057221415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f3e4.svg000066400000000000000000000050071415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f3e5.svg000066400000000000000000000067561415623743500166770ustar00rootroot00000000000000qTox/smileys/emojione/1f3e6.svg000066400000000000000000000065011415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f3e7.svg000066400000000000000000000044031415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f3e8.svg000066400000000000000000000113641415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f3e9.svg000066400000000000000000000035001415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f3ea.svg000066400000000000000000000025011415623743500167330ustar00rootroot00000000000000qTox/smileys/emojione/1f3eb.svg000066400000000000000000000120641415623743500167410ustar00rootroot00000000000000qTox/smileys/emojione/1f3ec.svg000066400000000000000000000055321415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f3ed.svg000066400000000000000000000051421415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f3ee.svg000066400000000000000000000072021415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f3ef.svg000066400000000000000000000071741415623743500167530ustar00rootroot00000000000000qTox/smileys/emojione/1f3f0.svg000066400000000000000000000065021415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f3f3.svg000066400000000000000000000006161415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f3f4.svg000066400000000000000000000006331415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f3f5.svg000066400000000000000000000026261415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f3f7.svg000066400000000000000000000020371415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f3f8.svg000066400000000000000000000110001415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f3f9.svg000066400000000000000000000060221415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f3fa.svg000066400000000000000000000116201415623743500167360ustar00rootroot00000000000000qTox/smileys/emojione/1f400.svg000066400000000000000000000051071415623743500165730ustar00rootroot00000000000000qTox/smileys/emojione/1f401.svg000066400000000000000000000056241415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f402.svg000066400000000000000000000076761415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f403.svg000066400000000000000000000067761415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f404.svg000066400000000000000000000122361415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f405.svg000066400000000000000000000127321415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f406.svg000066400000000000000000000106771415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f407.svg000066400000000000000000000131761415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f408.svg000066400000000000000000000121401415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f409.svg000066400000000000000000000110661415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f40a.svg000066400000000000000000000111241415623743500166500ustar00rootroot00000000000000qTox/smileys/emojione/1f40b.svg000066400000000000000000000054541415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f40c.svg000066400000000000000000000070641415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f40d.svg000066400000000000000000000135331415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f40e.svg000066400000000000000000000071331415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f40f.svg000066400000000000000000000130451415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f410.svg000066400000000000000000000041411415623743500165710ustar00rootroot00000000000000qTox/smileys/emojione/1f411.svg000066400000000000000000000067231415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f412.svg000066400000000000000000000056701415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f413.svg000066400000000000000000000046441415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f414.svg000066400000000000000000000045021415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f415.svg000066400000000000000000000076001415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f416.svg000066400000000000000000000076001415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f417.svg000066400000000000000000000033731415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f418.svg000066400000000000000000000070611415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f419.svg000066400000000000000000000064501415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f41a.svg000066400000000000000000000047301415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f41b.svg000066400000000000000000000104471415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f41c.svg000066400000000000000000000065211415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f41d.svg000066400000000000000000000053571415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f41e.svg000066400000000000000000000063201415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f41f.svg000066400000000000000000000024661415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f420.svg000066400000000000000000000147331415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f421.svg000066400000000000000000000072041415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f422.svg000066400000000000000000000056701415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f423.svg000066400000000000000000000063501415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f424.svg000066400000000000000000000053231415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f425.svg000066400000000000000000000070101415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f426.svg000066400000000000000000000026661415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f427.svg000066400000000000000000000035521415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f428.svg000066400000000000000000000063331415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f429.svg000066400000000000000000000051531415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f42a.svg000066400000000000000000000052561415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f42b.svg000066400000000000000000000067051415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f42c.svg000066400000000000000000000057461415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f42d.svg000066400000000000000000000030571415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f42e.svg000066400000000000000000000055201415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f42f.svg000066400000000000000000000067571415623743500166770ustar00rootroot00000000000000qTox/smileys/emojione/1f430.svg000066400000000000000000000111741415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f431.svg000066400000000000000000000052651415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f432.svg000066400000000000000000000106111415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f433.svg000066400000000000000000000107101415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f434.svg000066400000000000000000000061701415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f435.svg000066400000000000000000000033641415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f436.svg000066400000000000000000000047161415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f437.svg000066400000000000000000000027261415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f438.svg000066400000000000000000000026041415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f439.svg000066400000000000000000000106561415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f43a.svg000066400000000000000000000116321415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f43b.svg000066400000000000000000000042411415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f43c.svg000066400000000000000000000036071415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f43d.svg000066400000000000000000000013421415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f43e.svg000066400000000000000000000027501415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f43f.svg000066400000000000000000000063121415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f440.svg000066400000000000000000000014471415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f441-1f5e8.svg000066400000000000000000000010011415623743500174130ustar00rootroot00000000000000qTox/smileys/emojione/1f441.svg000066400000000000000000000012101415623743500165670ustar00rootroot00000000000000qTox/smileys/emojione/1f442-1f3fb.svg000066400000000000000000000046251415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f442-1f3fc.svg000066400000000000000000000046251415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f442-1f3fd.svg000066400000000000000000000046251415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f442-1f3fe.svg000066400000000000000000000046251415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f442-1f3ff.svg000066400000000000000000000046251415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f442.svg000066400000000000000000000046231415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f443-1f3fb.svg000066400000000000000000000025011415623743500174740ustar00rootroot00000000000000qTox/smileys/emojione/1f443-1f3fc.svg000066400000000000000000000025011415623743500174750ustar00rootroot00000000000000qTox/smileys/emojione/1f443-1f3fd.svg000066400000000000000000000025011415623743500174760ustar00rootroot00000000000000qTox/smileys/emojione/1f443-1f3fe.svg000066400000000000000000000025011415623743500174770ustar00rootroot00000000000000qTox/smileys/emojione/1f443-1f3ff.svg000066400000000000000000000025011415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f443.svg000066400000000000000000000025121415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f444.svg000066400000000000000000000013711415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f445.svg000066400000000000000000000007121415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f446-1f3fb.svg000066400000000000000000000053651415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f446-1f3fc.svg000066400000000000000000000053701415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f446-1f3fd.svg000066400000000000000000000053231415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f446-1f3fe.svg000066400000000000000000000053701415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f446-1f3ff.svg000066400000000000000000000053651415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f446.svg000066400000000000000000000054011415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f447-1f3fb.svg000066400000000000000000000053571415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f447-1f3fc.svg000066400000000000000000000053661415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f447-1f3fd.svg000066400000000000000000000053601415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f447-1f3fe.svg000066400000000000000000000053701415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f447-1f3ff.svg000066400000000000000000000053621415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f447.svg000066400000000000000000000053661415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f448-1f3fb.svg000066400000000000000000000053661415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f448-1f3fc.svg000066400000000000000000000053701415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f448-1f3fd.svg000066400000000000000000000053721415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f448-1f3fe.svg000066400000000000000000000053701415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f448-1f3ff.svg000066400000000000000000000053701415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f448.svg000066400000000000000000000053711415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f449-1f3fb.svg000066400000000000000000000053771415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f449-1f3fc.svg000066400000000000000000000054021415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f449-1f3fd.svg000066400000000000000000000054071415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f449-1f3fe.svg000066400000000000000000000054011415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f449-1f3ff.svg000066400000000000000000000053761415623743500175230ustar00rootroot00000000000000qTox/smileys/emojione/1f449.svg000066400000000000000000000054001415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f44a-1f3fb.svg000066400000000000000000000046461415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f44a-1f3fc.svg000066400000000000000000000046501415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f44a-1f3fd.svg000066400000000000000000000046501415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f44a-1f3fe.svg000066400000000000000000000046501415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f44a-1f3ff.svg000066400000000000000000000046501415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f44a.svg000066400000000000000000000046731415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f44b-1f3fb.svg000066400000000000000000000075631415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f44b-1f3fc.svg000066400000000000000000000075631415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f44b-1f3fd.svg000066400000000000000000000075331415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f44b-1f3fe.svg000066400000000000000000000075631415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f44b-1f3ff.svg000066400000000000000000000075631415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f44b.svg000066400000000000000000000075171415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f44c-1f3fb.svg000066400000000000000000000037331415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f44c-1f3fc.svg000066400000000000000000000037331415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f44c-1f3fd.svg000066400000000000000000000037331415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f44c-1f3fe.svg000066400000000000000000000037331415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f44c-1f3ff.svg000066400000000000000000000037331415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f44c.svg000066400000000000000000000037331415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f44d-1f3fb.svg000066400000000000000000000035421415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f44d-1f3fc.svg000066400000000000000000000035451415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f44d-1f3fd.svg000066400000000000000000000035451415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f44d-1f3fe.svg000066400000000000000000000035451415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f44d-1f3ff.svg000066400000000000000000000035451415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f44d.svg000066400000000000000000000035461415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f44e-1f3fb.svg000066400000000000000000000035421415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f44e-1f3fc.svg000066400000000000000000000035621415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f44e-1f3fd.svg000066400000000000000000000035621415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f44e-1f3fe.svg000066400000000000000000000035621415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f44e-1f3ff.svg000066400000000000000000000035621415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f44e.svg000066400000000000000000000035401415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f44f-1f3fb.svg000066400000000000000000000055541415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f44f-1f3fc.svg000066400000000000000000000055461415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f44f-1f3fd.svg000066400000000000000000000055461415623743500175750ustar00rootroot00000000000000qTox/smileys/emojione/1f44f-1f3fe.svg000066400000000000000000000055461415623743500175760ustar00rootroot00000000000000qTox/smileys/emojione/1f44f-1f3ff.svg000066400000000000000000000055461415623743500175770ustar00rootroot00000000000000qTox/smileys/emojione/1f44f.svg000066400000000000000000000055571415623743500166760ustar00rootroot00000000000000qTox/smileys/emojione/1f450-1f3fb.svg000066400000000000000000000076221415623743500175030ustar00rootroot00000000000000qTox/smileys/emojione/1f450-1f3fc.svg000066400000000000000000000076241415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f450-1f3fd.svg000066400000000000000000000076241415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f450-1f3fe.svg000066400000000000000000000076241415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f450-1f3ff.svg000066400000000000000000000076241415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f450.svg000066400000000000000000000076221415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f451.svg000066400000000000000000000107401415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f452.svg000066400000000000000000000057041415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f453.svg000066400000000000000000000032651415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f454.svg000066400000000000000000000016501415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f455.svg000066400000000000000000000024141415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f456.svg000066400000000000000000000072701415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f457.svg000066400000000000000000000044371415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f458.svg000066400000000000000000000043401415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f459.svg000066400000000000000000000115421415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f45a.svg000066400000000000000000000036631415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f45b.svg000066400000000000000000000064231415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f45c.svg000066400000000000000000000060761415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f45d.svg000066400000000000000000000100171415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f45e.svg000066400000000000000000000071441415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f45f.svg000066400000000000000000000052751415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f460.svg000066400000000000000000000020601415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f461.svg000066400000000000000000000027311415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f462.svg000066400000000000000000000014761415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f463.svg000066400000000000000000000034731415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f464.svg000066400000000000000000000011511415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f465.svg000066400000000000000000000020231415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f466-1f3fb.svg000066400000000000000000000017441415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f466-1f3fc.svg000066400000000000000000000017431415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f466-1f3fd.svg000066400000000000000000000017431415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f466-1f3fe.svg000066400000000000000000000017341415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f466-1f3ff.svg000066400000000000000000000017651415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f466.svg000066400000000000000000000017461415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f467-1f3fb.svg000066400000000000000000000022201415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f467-1f3fc.svg000066400000000000000000000023051415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f467-1f3fd.svg000066400000000000000000000023051415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f467-1f3fe.svg000066400000000000000000000023401415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f467-1f3ff.svg000066400000000000000000000023401415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f467.svg000066400000000000000000000022111415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f3fb.svg000066400000000000000000000031001415623743500174770ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f3fc.svg000066400000000000000000000031131415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f3fd.svg000066400000000000000000000030601415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f3fe.svg000066400000000000000000000030551415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f3ff.svg000066400000000000000000000031311415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f468-1f466-1f466.svg000066400000000000000000000070741415623743500206740ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f468-1f466.svg000066400000000000000000000054061415623743500201250ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f468-1f467-1f466.svg000066400000000000000000000075261415623743500206770ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f468-1f467-1f467.svg000066400000000000000000000103721415623743500206710ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f468-1f467.svg000066400000000000000000000062101415623743500201200ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f469-1f466-1f466.svg000066400000000000000000000067011415623743500206710ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f469-1f467-1f466.svg000066400000000000000000000074331415623743500206750ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f469-1f467-1f467.svg000066400000000000000000000102301415623743500206630ustar00rootroot00000000000000qTox/smileys/emojione/1f468-1f469-1f467.svg000066400000000000000000000060251415623743500201250ustar00rootroot00000000000000qTox/smileys/emojione/1f468-2764-1f468.svg000066400000000000000000000043551415623743500177630ustar00rootroot00000000000000qTox/smileys/emojione/1f468-2764-1f48b-1f468.svg000066400000000000000000000051761415623743500206070ustar00rootroot00000000000000qTox/smileys/emojione/1f468.svg000066400000000000000000000030671415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f3fb.svg000066400000000000000000000031411415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f3fc.svg000066400000000000000000000031661415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f3fd.svg000066400000000000000000000031661415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f3fe.svg000066400000000000000000000032071415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f3ff.svg000066400000000000000000000032041415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f469-1f466-1f466.svg000066400000000000000000000061501415623743500206700ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f469-1f466.svg000066400000000000000000000044741415623743500201330ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f469-1f467-1f466.svg000066400000000000000000000066151415623743500206770ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f469-1f467-1f467.svg000066400000000000000000000075061415623743500207000ustar00rootroot00000000000000qTox/smileys/emojione/1f469-1f469-1f467.svg000066400000000000000000000052461415623743500201320ustar00rootroot00000000000000qTox/smileys/emojione/1f469-2764-1f469.svg000066400000000000000000000035341415623743500177630ustar00rootroot00000000000000qTox/smileys/emojione/1f469-2764-1f48b-1f469.svg000066400000000000000000000050701415623743500206020ustar00rootroot00000000000000qTox/smileys/emojione/1f469.svg000066400000000000000000000031361415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f46a.svg000066400000000000000000000052461415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f46b.svg000066400000000000000000000077511415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f46c.svg000066400000000000000000000075221415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f46d.svg000066400000000000000000000071101415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f46e-1f3fb.svg000066400000000000000000000042071415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f46e-1f3fc.svg000066400000000000000000000041141415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f46e-1f3fd.svg000066400000000000000000000041141415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f46e-1f3fe.svg000066400000000000000000000042331415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f46e-1f3ff.svg000066400000000000000000000042661415623743500175760ustar00rootroot00000000000000qTox/smileys/emojione/1f46e.svg000066400000000000000000000040761415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f46f.svg000066400000000000000000000147641415623743500167000ustar00rootroot00000000000000qTox/smileys/emojione/1f470-1f3fb.svg000066400000000000000000000117401415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f470-1f3fc.svg000066400000000000000000000115201415623743500174760ustar00rootroot00000000000000qTox/smileys/emojione/1f470-1f3fd.svg000066400000000000000000000117501415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f470-1f3fe.svg000066400000000000000000000117401415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f470-1f3ff.svg000066400000000000000000000117371415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f470.svg000066400000000000000000000117341415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f471-1f3fb.svg000066400000000000000000000034141415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f471-1f3fc.svg000066400000000000000000000034261415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f471-1f3fd.svg000066400000000000000000000034261415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f471-1f3fe.svg000066400000000000000000000033651415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f471-1f3ff.svg000066400000000000000000000033471415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f471.svg000066400000000000000000000034521415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f472-1f3fb.svg000066400000000000000000000037371415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f472-1f3fc.svg000066400000000000000000000037331415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f472-1f3fd.svg000066400000000000000000000037431415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f472-1f3fe.svg000066400000000000000000000037511415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f472-1f3ff.svg000066400000000000000000000037371415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f472.svg000066400000000000000000000037321415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f473-1f3fb.svg000066400000000000000000000031641415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f473-1f3fc.svg000066400000000000000000000031501415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f473-1f3fd.svg000066400000000000000000000031641415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f473-1f3fe.svg000066400000000000000000000032161415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f473-1f3ff.svg000066400000000000000000000032551415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f473.svg000066400000000000000000000032041415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f474-1f3fb.svg000066400000000000000000000033201415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f474-1f3fc.svg000066400000000000000000000033251415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f474-1f3fd.svg000066400000000000000000000033251415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f474-1f3fe.svg000066400000000000000000000033211415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f474-1f3ff.svg000066400000000000000000000033121415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f474.svg000066400000000000000000000033221415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f475-1f3fb.svg000066400000000000000000000033471415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f475-1f3fc.svg000066400000000000000000000033521415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f475-1f3fd.svg000066400000000000000000000033501415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f475-1f3fe.svg000066400000000000000000000033451415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f475-1f3ff.svg000066400000000000000000000033261415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f475.svg000066400000000000000000000033541415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f476-1f3fb.svg000066400000000000000000000037211415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f476-1f3fc.svg000066400000000000000000000037041415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f476-1f3fd.svg000066400000000000000000000037041415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f476-1f3fe.svg000066400000000000000000000037771415623743500175250ustar00rootroot00000000000000qTox/smileys/emojione/1f476-1f3ff.svg000066400000000000000000000040371415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f476.svg000066400000000000000000000037261415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f477-1f3fb.svg000066400000000000000000000026471415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f477-1f3fc.svg000066400000000000000000000026261415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f477-1f3fd.svg000066400000000000000000000026261415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f477-1f3fe.svg000066400000000000000000000027241415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f477-1f3ff.svg000066400000000000000000000027051415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f477.svg000066400000000000000000000026611415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f478-1f3fb.svg000066400000000000000000000160471415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f478-1f3fc.svg000066400000000000000000000160631415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f478-1f3fd.svg000066400000000000000000000160631415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f478-1f3fe.svg000066400000000000000000000161301415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f478-1f3ff.svg000066400000000000000000000161271415623743500175210ustar00rootroot00000000000000qTox/smileys/emojione/1f478.svg000066400000000000000000000161011415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f479.svg000066400000000000000000000100621415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f47a.svg000066400000000000000000000077571415623743500167000ustar00rootroot00000000000000qTox/smileys/emojione/1f47b.svg000066400000000000000000000066531415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f47c-1f3fb.svg000066400000000000000000000161031415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f47c-1f3fc.svg000066400000000000000000000161321415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f47c-1f3fd.svg000066400000000000000000000161321415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f47c-1f3fe.svg000066400000000000000000000161271415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f47c-1f3ff.svg000066400000000000000000000161151415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f47c.svg000066400000000000000000000161171415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f47d.svg000066400000000000000000000032501415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f47e.svg000066400000000000000000000052231415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f47f.svg000066400000000000000000000110541415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f480.svg000066400000000000000000000026031415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f481-1f3fb.svg000066400000000000000000000043531415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f481-1f3fc.svg000066400000000000000000000045101415623743500175010ustar00rootroot00000000000000qTox/smileys/emojione/1f481-1f3fd.svg000066400000000000000000000045141415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f481-1f3fe.svg000066400000000000000000000043611415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f481-1f3ff.svg000066400000000000000000000045101415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f481.svg000066400000000000000000000043651415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f482-1f3fb.svg000066400000000000000000000063441415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f482-1f3fc.svg000066400000000000000000000063471415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f482-1f3fd.svg000066400000000000000000000063471415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f482-1f3fe.svg000066400000000000000000000063311415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f482-1f3ff.svg000066400000000000000000000063101415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f482.svg000066400000000000000000000063031415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f483-1f3fb.svg000066400000000000000000000057661415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f483-1f3fc.svg000066400000000000000000000057531415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f483-1f3fd.svg000066400000000000000000000057571415623743500175220ustar00rootroot00000000000000qTox/smileys/emojione/1f483-1f3fe.svg000066400000000000000000000057621415623743500175170ustar00rootroot00000000000000qTox/smileys/emojione/1f483-1f3ff.svg000066400000000000000000000057521415623743500175170ustar00rootroot00000000000000qTox/smileys/emojione/1f483.svg000066400000000000000000000060331415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f484.svg000066400000000000000000000015061415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f485-1f3fb.svg000066400000000000000000000061601415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f485-1f3fc.svg000066400000000000000000000061601415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f485-1f3fd.svg000066400000000000000000000061601415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f485-1f3fe.svg000066400000000000000000000061601415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f485-1f3ff.svg000066400000000000000000000061601415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f485.svg000066400000000000000000000062011415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f486-1f3fb.svg000066400000000000000000000064131415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f486-1f3fc.svg000066400000000000000000000064111415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f486-1f3fd.svg000066400000000000000000000064151415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f486-1f3fe.svg000066400000000000000000000064131415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f486-1f3ff.svg000066400000000000000000000064301415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f486.svg000066400000000000000000000064131415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f487-1f3fb.svg000066400000000000000000000062241415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f487-1f3fc.svg000066400000000000000000000062341415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f487-1f3fd.svg000066400000000000000000000062351415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f487-1f3fe.svg000066400000000000000000000062231415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f487-1f3ff.svg000066400000000000000000000062151415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f487.svg000066400000000000000000000062171415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f488.svg000066400000000000000000000033201415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f489.svg000066400000000000000000000015501415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f48a.svg000066400000000000000000000010201415623743500166520ustar00rootroot00000000000000qTox/smileys/emojione/1f48b.svg000066400000000000000000000070431415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f48c.svg000066400000000000000000000014361415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f48d.svg000066400000000000000000000031621415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f48e.svg000066400000000000000000000004231415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f48f.svg000066400000000000000000000051471415623743500166750ustar00rootroot00000000000000qTox/smileys/emojione/1f490.svg000066400000000000000000000055451415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f491.svg000066400000000000000000000040551415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f492.svg000066400000000000000000000120361415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f493.svg000066400000000000000000000057151415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f494.svg000066400000000000000000000007211415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f495.svg000066400000000000000000000006731415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f496.svg000066400000000000000000000013151415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f497.svg000066400000000000000000000011721415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f498.svg000066400000000000000000000014101415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f499.svg000066400000000000000000000004311415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f49a.svg000066400000000000000000000004311415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f49b.svg000066400000000000000000000004311415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f49c.svg000066400000000000000000000004271415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f49d.svg000066400000000000000000000045551415623743500166760ustar00rootroot00000000000000qTox/smileys/emojione/1f49e.svg000066400000000000000000000035501415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f49f.svg000066400000000000000000000005311415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f4a0.svg000066400000000000000000000021661415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f4a1.svg000066400000000000000000000047411415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f4a2.svg000066400000000000000000000027231415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f4a3.svg000066400000000000000000000024631415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f4a4.svg000066400000000000000000000021441415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f4a5.svg000066400000000000000000000015621415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f4a6.svg000066400000000000000000000011311415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f4a7.svg000066400000000000000000000003751415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f4a8.svg000066400000000000000000000053451415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f4a9.svg000066400000000000000000000020251415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f4aa-1f3fb.svg000066400000000000000000000052411415623743500176330ustar00rootroot00000000000000qTox/smileys/emojione/1f4aa-1f3fc.svg000066400000000000000000000052361415623743500176400ustar00rootroot00000000000000qTox/smileys/emojione/1f4aa-1f3fd.svg000066400000000000000000000052361415623743500176410ustar00rootroot00000000000000qTox/smileys/emojione/1f4aa-1f3fe.svg000066400000000000000000000052361415623743500176420ustar00rootroot00000000000000qTox/smileys/emojione/1f4aa-1f3ff.svg000066400000000000000000000052361415623743500176430ustar00rootroot00000000000000qTox/smileys/emojione/1f4aa.svg000066400000000000000000000052361415623743500167400ustar00rootroot00000000000000qTox/smileys/emojione/1f4ab.svg000066400000000000000000000010701415623743500167310ustar00rootroot00000000000000qTox/smileys/emojione/1f4ac.svg000066400000000000000000000006671415623743500167450ustar00rootroot00000000000000qTox/smileys/emojione/1f4ad.svg000066400000000000000000000020701415623743500167340ustar00rootroot00000000000000qTox/smileys/emojione/1f4ae.svg000066400000000000000000000117261415623743500167450ustar00rootroot00000000000000qTox/smileys/emojione/1f4af.svg000066400000000000000000000024261415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f4b0.svg000066400000000000000000000127211415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f4b1.svg000066400000000000000000000014121415623743500166510ustar00rootroot00000000000000qTox/smileys/emojione/1f4b2.svg000066400000000000000000000007711415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f4b3.svg000066400000000000000000000036151415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f4b4.svg000066400000000000000000000011221415623743500166520ustar00rootroot00000000000000qTox/smileys/emojione/1f4b5.svg000066400000000000000000000013201415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f4b6.svg000066400000000000000000000014361415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f4b7.svg000066400000000000000000000015221415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f4b8.svg000066400000000000000000000131661415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f4b9.svg000066400000000000000000000011031415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f4ba.svg000066400000000000000000000033151415623743500167350ustar00rootroot00000000000000qTox/smileys/emojione/1f4bb.svg000066400000000000000000000013771415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f4bc.svg000066400000000000000000000047351415623743500167460ustar00rootroot00000000000000qTox/smileys/emojione/1f4bd.svg000066400000000000000000000010761415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f4be.svg000066400000000000000000000012241415623743500167360ustar00rootroot00000000000000qTox/smileys/emojione/1f4bf.svg000066400000000000000000000013671415623743500167470ustar00rootroot00000000000000qTox/smileys/emojione/1f4c0.svg000066400000000000000000000014001415623743500166460ustar00rootroot00000000000000qTox/smileys/emojione/1f4c1.svg000066400000000000000000000016351415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f4c2.svg000066400000000000000000000011221415623743500166510ustar00rootroot00000000000000qTox/smileys/emojione/1f4c3.svg000066400000000000000000000025341415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f4c4.svg000066400000000000000000000017101415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f4c5.svg000066400000000000000000000055401415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f4c6.svg000066400000000000000000000057501415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f4c7.svg000066400000000000000000000062351415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f4c8.svg000066400000000000000000000015151415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f4c9.svg000066400000000000000000000015131415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f4ca.svg000066400000000000000000000007461415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f4cb.svg000066400000000000000000000027101415623743500167350ustar00rootroot00000000000000qTox/smileys/emojione/1f4cc.svg000066400000000000000000000010371415623743500167370ustar00rootroot00000000000000qTox/smileys/emojione/1f4cd.svg000066400000000000000000000003201415623743500167320ustar00rootroot00000000000000qTox/smileys/emojione/1f4ce.svg000066400000000000000000000016271415623743500167460ustar00rootroot00000000000000qTox/smileys/emojione/1f4cf.svg000066400000000000000000000045141415623743500167450ustar00rootroot00000000000000qTox/smileys/emojione/1f4d0.svg000066400000000000000000000017741415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f4d1.svg000066400000000000000000000031311415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f4d2.svg000066400000000000000000000037631415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f4d3.svg000066400000000000000000000017401415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f4d4.svg000066400000000000000000000046241415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f4d5.svg000066400000000000000000000015101415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f4d6.svg000066400000000000000000000113211415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f4d7.svg000066400000000000000000000015051415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f4d8.svg000066400000000000000000000015001415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f4d9.svg000066400000000000000000000015011415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f4da.svg000066400000000000000000000042101415623743500167320ustar00rootroot00000000000000qTox/smileys/emojione/1f4db.svg000066400000000000000000000121661415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f4dc.svg000066400000000000000000000110621415623743500167370ustar00rootroot00000000000000qTox/smileys/emojione/1f4dd.svg000066400000000000000000000131511415623743500167410ustar00rootroot00000000000000qTox/smileys/emojione/1f4de.svg000066400000000000000000000074051415623743500167470ustar00rootroot00000000000000qTox/smileys/emojione/1f4df.svg000066400000000000000000000050241415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f4e0.svg000066400000000000000000000073461415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f4e1.svg000066400000000000000000000037461415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f4e2.svg000066400000000000000000000034241415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f4e3.svg000066400000000000000000000121671415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f4e4.svg000066400000000000000000000016761415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f4e5.svg000066400000000000000000000016461415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f4e6.svg000066400000000000000000000013001415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f4e7.svg000066400000000000000000000015441415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f4e8.svg000066400000000000000000000025001415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f4e9.svg000066400000000000000000000014521415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f4ea.svg000066400000000000000000000031521415623743500167370ustar00rootroot00000000000000qTox/smileys/emojione/1f4eb.svg000066400000000000000000000031551415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f4ec.svg000066400000000000000000000024351415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f4ed.svg000066400000000000000000000011731415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f4ee.svg000066400000000000000000000041021415623743500167370ustar00rootroot00000000000000qTox/smileys/emojione/1f4ef.svg000066400000000000000000000050771415623743500167540ustar00rootroot00000000000000qTox/smileys/emojione/1f4f0.svg000066400000000000000000000041101415623743500166520ustar00rootroot00000000000000qTox/smileys/emojione/1f4f1.svg000066400000000000000000000073651415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f4f2.svg000066400000000000000000000075201415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f4f3.svg000066400000000000000000000023721415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f4f4.svg000066400000000000000000000013471415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f4f5.svg000066400000000000000000000013501415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f4f6.svg000066400000000000000000000003331415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f4f7.svg000066400000000000000000000037021415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f4f8.svg000066400000000000000000000046471415623743500167010ustar00rootroot00000000000000qTox/smileys/emojione/1f4f9.svg000066400000000000000000000043601415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f4fa.svg000066400000000000000000000017761415623743500167520ustar00rootroot00000000000000qTox/smileys/emojione/1f4fb.svg000066400000000000000000000037541415623743500167510ustar00rootroot00000000000000qTox/smileys/emojione/1f4fc.svg000066400000000000000000000036571415623743500167540ustar00rootroot00000000000000qTox/smileys/emojione/1f4fd.svg000066400000000000000000000027401415623743500167450ustar00rootroot00000000000000qTox/smileys/emojione/1f4ff.svg000066400000000000000000000074471415623743500167600ustar00rootroot00000000000000qTox/smileys/emojione/1f500.svg000066400000000000000000000013751415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f501.svg000066400000000000000000000011741415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f502.svg000066400000000000000000000020601415623743500165710ustar00rootroot00000000000000qTox/smileys/emojione/1f503.svg000066400000000000000000000011101415623743500165650ustar00rootroot00000000000000qTox/smileys/emojione/1f504.svg000066400000000000000000000010371415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f505.svg000066400000000000000000000010421415623743500165730ustar00rootroot00000000000000qTox/smileys/emojione/1f506.svg000066400000000000000000000010541415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f507.svg000066400000000000000000000011051415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f508.svg000066400000000000000000000021661415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f509.svg000066400000000000000000000025141415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f50a.svg000066400000000000000000000033301415623743500166510ustar00rootroot00000000000000qTox/smileys/emojione/1f50b.svg000066400000000000000000000012311415623743500166500ustar00rootroot00000000000000qTox/smileys/emojione/1f50c.svg000066400000000000000000000027231415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f50d.svg000066400000000000000000000031671415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f50e.svg000066400000000000000000000031631415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f50f.svg000066400000000000000000000030661415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f510.svg000066400000000000000000000027431415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f511.svg000066400000000000000000000024641415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f512.svg000066400000000000000000000022111415623743500165700ustar00rootroot00000000000000qTox/smileys/emojione/1f513.svg000066400000000000000000000021051415623743500165730ustar00rootroot00000000000000qTox/smileys/emojione/1f514.svg000066400000000000000000000042121415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f515.svg000066400000000000000000000011501415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f516.svg000066400000000000000000000023711415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f517.svg000066400000000000000000000020231415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f518.svg000066400000000000000000000003521415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f519.svg000066400000000000000000000014251415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f51a.svg000066400000000000000000000006361415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f51b.svg000066400000000000000000000007721415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f51c.svg000066400000000000000000000016521415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f51d.svg000066400000000000000000000010361415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f51e.svg000066400000000000000000000026471415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f51f.svg000066400000000000000000000022421415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f520.svg000066400000000000000000000027031415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f521.svg000066400000000000000000000031731415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f522.svg000066400000000000000000000021151415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f523.svg000066400000000000000000000033471415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f524.svg000066400000000000000000000024111415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f525.svg000066400000000000000000000024431415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f526.svg000066400000000000000000000026661415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f527.svg000066400000000000000000000010151415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f528.svg000066400000000000000000000023501415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f529.svg000066400000000000000000000021751415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f52a.svg000066400000000000000000000013241415623743500166540ustar00rootroot00000000000000qTox/smileys/emojione/1f52b.svg000066400000000000000000000116461415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f52c.svg000066400000000000000000000026221415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f52d.svg000066400000000000000000000041221415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f52e.svg000066400000000000000000000044641415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f52f.svg000066400000000000000000000010501415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f530.svg000066400000000000000000000005451415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f531.svg000066400000000000000000000013511415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f532.svg000066400000000000000000000002511415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f533.svg000066400000000000000000000002511415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f534.svg000066400000000000000000000002101415623743500165710ustar00rootroot00000000000000qTox/smileys/emojione/1f535.svg000066400000000000000000000002101415623743500165720ustar00rootroot00000000000000qTox/smileys/emojione/1f536.svg000066400000000000000000000002171415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f537.svg000066400000000000000000000002171415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f538.svg000066400000000000000000000002211415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f539.svg000066400000000000000000000002211415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f53a.svg000066400000000000000000000002021415623743500166470ustar00rootroot00000000000000qTox/smileys/emojione/1f53b.svg000066400000000000000000000002021415623743500166500ustar00rootroot00000000000000qTox/smileys/emojione/1f53c.svg000066400000000000000000000002631415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f53d.svg000066400000000000000000000002731415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f549.svg000066400000000000000000000030321415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f54a.svg000066400000000000000000000040101415623743500166510ustar00rootroot00000000000000qTox/smileys/emojione/1f54b.svg000066400000000000000000000116441415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f54c.svg000066400000000000000000000073441415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f54d.svg000066400000000000000000000122401415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f54e.svg000066400000000000000000000145021415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f550.svg000066400000000000000000000005101415623743500165720ustar00rootroot00000000000000qTox/smileys/emojione/1f551.svg000066400000000000000000000005071415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f552.svg000066400000000000000000000004631415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f553.svg000066400000000000000000000005071415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f554.svg000066400000000000000000000005071415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f555.svg000066400000000000000000000004621415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f556.svg000066400000000000000000000005071415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f557.svg000066400000000000000000000005101415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f558.svg000066400000000000000000000004561415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f559.svg000066400000000000000000000005101415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f55a.svg000066400000000000000000000005071415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f55b.svg000066400000000000000000000004071415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f55c.svg000066400000000000000000000005231415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f55d.svg000066400000000000000000000005151415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f55e.svg000066400000000000000000000005151415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f55f.svg000066400000000000000000000005261415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f560.svg000066400000000000000000000005151415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f561.svg000066400000000000000000000005151415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f562.svg000066400000000000000000000005251415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f563.svg000066400000000000000000000005151415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f564.svg000066400000000000000000000005151415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f565.svg000066400000000000000000000005101415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f566.svg000066400000000000000000000005151415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f567.svg000066400000000000000000000005111415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f56f.svg000066400000000000000000000031501415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f570.svg000066400000000000000000000010371415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f573.svg000066400000000000000000000040701415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f574.svg000066400000000000000000000067621415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f575-1f3fb.svg000066400000000000000000000054121415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f575-1f3fc.svg000066400000000000000000000054121415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f575-1f3fd.svg000066400000000000000000000055761415623743500175230ustar00rootroot00000000000000qTox/smileys/emojione/1f575-1f3fe.svg000066400000000000000000000055241415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f575-1f3ff.svg000066400000000000000000000055241415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f575.svg000066400000000000000000000054201415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f576.svg000066400000000000000000000040301415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f577.svg000066400000000000000000000103421415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f578.svg000066400000000000000000000042151415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f579.svg000066400000000000000000000040151415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f57a-1f3fb.svg000066400000000000000000000105141415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f57a-1f3fc.svg000066400000000000000000000105331415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f57a-1f3fd.svg000066400000000000000000000105301415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f57a-1f3fe.svg000066400000000000000000000105331415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f57a-1f3ff.svg000066400000000000000000000105141415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f57a.svg000066400000000000000000000105601415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f587.svg000066400000000000000000000036161415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f58a.svg000066400000000000000000000027321415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f58b.svg000066400000000000000000000014051415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f58c.svg000066400000000000000000000034571415623743500166750ustar00rootroot00000000000000qTox/smileys/emojione/1f58d.svg000066400000000000000000000015531415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f590-1f3fb.svg000066400000000000000000000041531415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f590-1f3fc.svg000066400000000000000000000041531415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f590-1f3fd.svg000066400000000000000000000041531415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f590-1f3fe.svg000066400000000000000000000041531415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f590-1f3ff.svg000066400000000000000000000041531415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f590.svg000066400000000000000000000041541415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f595-1f3fb.svg000066400000000000000000000042041415623743500175060ustar00rootroot00000000000000qTox/smileys/emojione/1f595-1f3fc.svg000066400000000000000000000042041415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f595-1f3fd.svg000066400000000000000000000042041415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f595-1f3fe.svg000066400000000000000000000042041415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f595-1f3ff.svg000066400000000000000000000042041415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f595.svg000066400000000000000000000042051415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f596-1f3fb.svg000066400000000000000000000040161415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f596-1f3fc.svg000066400000000000000000000040161415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f596-1f3fd.svg000066400000000000000000000040161415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f596-1f3fe.svg000066400000000000000000000040161415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f596-1f3ff.svg000066400000000000000000000040161415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f596.svg000066400000000000000000000040101415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f5a4.svg000066400000000000000000000004311415623743500166540ustar00rootroot00000000000000qTox/smileys/emojione/1f5a5.svg000066400000000000000000000006461415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f5a8.svg000066400000000000000000000050751415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f5b1.svg000066400000000000000000000024751415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f5b2.svg000066400000000000000000000015371415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f5bc.svg000066400000000000000000000015751415623743500167460ustar00rootroot00000000000000qTox/smileys/emojione/1f5c2.svg000066400000000000000000000016421415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f5c3.svg000066400000000000000000000021261415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f5c4.svg000066400000000000000000000052751415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f5d1.svg000066400000000000000000000102661415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f5d2.svg000066400000000000000000000104711415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f5d3.svg000066400000000000000000000133501415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f5dc.svg000066400000000000000000000056371415623743500167530ustar00rootroot00000000000000qTox/smileys/emojione/1f5dd.svg000066400000000000000000000031621415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f5de.svg000066400000000000000000000035071415623743500167470ustar00rootroot00000000000000qTox/smileys/emojione/1f5e1.svg000066400000000000000000000027311415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f5e3.svg000066400000000000000000000013141415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f5e8.svg000066400000000000000000000006651415623743500166750ustar00rootroot00000000000000qTox/smileys/emojione/1f5ef.svg000066400000000000000000000015371415623743500167520ustar00rootroot00000000000000qTox/smileys/emojione/1f5f3.svg000066400000000000000000000037361415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f5fa.svg000066400000000000000000000102231415623743500167360ustar00rootroot00000000000000qTox/smileys/emojione/1f5fb.svg000066400000000000000000000070221415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f5fc.svg000066400000000000000000000043201415623743500167410ustar00rootroot00000000000000qTox/smileys/emojione/1f5fd.svg000066400000000000000000000072031415623743500167450ustar00rootroot00000000000000qTox/smileys/emojione/1f5fe.svg000066400000000000000000000060741415623743500167530ustar00rootroot00000000000000qTox/smileys/emojione/1f5ff.svg000066400000000000000000000077261415623743500167610ustar00rootroot00000000000000qTox/smileys/emojione/1f600.svg000066400000000000000000000017411415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f601.svg000066400000000000000000000023171415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f602.svg000066400000000000000000000030431415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f603.svg000066400000000000000000000014171415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f604.svg000066400000000000000000000045061415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f605.svg000066400000000000000000000023411415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f606.svg000066400000000000000000000025511415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f607.svg000066400000000000000000000023601415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f608.svg000066400000000000000000000035151415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f609.svg000066400000000000000000000015361415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f60a.svg000066400000000000000000000015761415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f60b.svg000066400000000000000000000021601415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f60c.svg000066400000000000000000000017161415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f60d.svg000066400000000000000000000026061415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f60e.svg000066400000000000000000000023161415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f60f.svg000066400000000000000000000020671415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f610.svg000066400000000000000000000005261415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f611.svg000066400000000000000000000005611415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f612.svg000066400000000000000000000014651415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f613.svg000066400000000000000000000014731415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f614.svg000066400000000000000000000015441415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f615.svg000066400000000000000000000011111415623743500165720ustar00rootroot00000000000000qTox/smileys/emojione/1f616.svg000066400000000000000000000027271415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f617.svg000066400000000000000000000015071415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f618.svg000066400000000000000000000020111415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f619.svg000066400000000000000000000015721415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f61a.svg000066400000000000000000000021371415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f61b.svg000066400000000000000000000011571415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f61c.svg000066400000000000000000000016271415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f61d.svg000066400000000000000000000017601415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f61e.svg000066400000000000000000000016271415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f61f.svg000066400000000000000000000013401415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f620.svg000066400000000000000000000026301415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f621.svg000066400000000000000000000026261415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f622.svg000066400000000000000000000024601415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f623.svg000066400000000000000000000020231415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/1f624.svg000066400000000000000000000054441415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f625.svg000066400000000000000000000016501415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f626.svg000066400000000000000000000012541415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f627.svg000066400000000000000000000017101415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f628.svg000066400000000000000000000020701415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f629.svg000066400000000000000000000022431415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f62a.svg000066400000000000000000000025201415623743500166540ustar00rootroot00000000000000qTox/smileys/emojione/1f62b.svg000066400000000000000000000033221415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f62c.svg000066400000000000000000000015471415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f62d.svg000066400000000000000000000031301415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f62e.svg000066400000000000000000000004621415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f62f.svg000066400000000000000000000011241415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f630.svg000066400000000000000000000016031415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/1f631.svg000066400000000000000000000044241415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f632.svg000066400000000000000000000016601415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f633.svg000066400000000000000000000016121415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f634.svg000066400000000000000000000037401415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f635.svg000066400000000000000000000014021415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/1f636.svg000066400000000000000000000003601415623743500166020ustar00rootroot00000000000000qTox/smileys/emojione/1f637.svg000066400000000000000000000027601415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f638.svg000066400000000000000000000070351415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f639.svg000066400000000000000000000070641415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f63a.svg000066400000000000000000000120441415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f63b.svg000066400000000000000000000066531415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f63c.svg000066400000000000000000000121011415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f63d.svg000066400000000000000000000063261415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f63e.svg000066400000000000000000000121061415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f63f.svg000066400000000000000000000123611415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f640.svg000066400000000000000000000124771415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f641.svg000066400000000000000000000006621415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f642.svg000066400000000000000000000006721415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f643.svg000066400000000000000000000007021415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/1f644.svg000066400000000000000000000016361415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f645-1f3fb.svg000066400000000000000000000044141415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f645-1f3fc.svg000066400000000000000000000044671415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f645-1f3fd.svg000066400000000000000000000044721415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f645-1f3fe.svg000066400000000000000000000044101415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f645-1f3ff.svg000066400000000000000000000044041415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f645.svg000066400000000000000000000044201415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f646-1f3fb.svg000066400000000000000000000047501415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f646-1f3fc.svg000066400000000000000000000050141415623743500175040ustar00rootroot00000000000000qTox/smileys/emojione/1f646-1f3fd.svg000066400000000000000000000050161415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f646-1f3fe.svg000066400000000000000000000047401415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f646-1f3ff.svg000066400000000000000000000047551415623743500175220ustar00rootroot00000000000000qTox/smileys/emojione/1f646.svg000066400000000000000000000047701415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f647-1f3fb.svg000066400000000000000000000047471415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f647-1f3fc.svg000066400000000000000000000047501415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f647-1f3fd.svg000066400000000000000000000047461415623743500175210ustar00rootroot00000000000000qTox/smileys/emojione/1f647-1f3fe.svg000066400000000000000000000047471415623743500175230ustar00rootroot00000000000000qTox/smileys/emojione/1f647-1f3ff.svg000066400000000000000000000047541415623743500175220ustar00rootroot00000000000000qTox/smileys/emojione/1f647.svg000066400000000000000000000047551415623743500166200ustar00rootroot00000000000000qTox/smileys/emojione/1f648.svg000066400000000000000000000044701415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f649.svg000066400000000000000000000035661415623743500166210ustar00rootroot00000000000000qTox/smileys/emojione/1f64a.svg000066400000000000000000000050701415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f64b-1f3fb.svg000066400000000000000000000047011415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f64b-1f3fc.svg000066400000000000000000000047271415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f64b-1f3fd.svg000066400000000000000000000047351415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f64b-1f3fe.svg000066400000000000000000000047011415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f64b-1f3ff.svg000066400000000000000000000047011415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f64b.svg000066400000000000000000000046621415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f64c-1f3fb.svg000066400000000000000000000073171415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f64c-1f3fc.svg000066400000000000000000000073261415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f64c-1f3fd.svg000066400000000000000000000073261415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f64c-1f3fe.svg000066400000000000000000000073261415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f64c-1f3ff.svg000066400000000000000000000073261415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f64c.svg000066400000000000000000000074211415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f64d-1f3fb.svg000066400000000000000000000034131415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f64d-1f3fc.svg000066400000000000000000000034151415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f64d-1f3fd.svg000066400000000000000000000034301415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f64d-1f3fe.svg000066400000000000000000000034131415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f64d-1f3ff.svg000066400000000000000000000034141415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f64d.svg000066400000000000000000000034141415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f64e-1f3fb.svg000066400000000000000000000030631415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f64e-1f3fc.svg000066400000000000000000000031361415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f64e-1f3fd.svg000066400000000000000000000031331415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f64e-1f3fe.svg000066400000000000000000000030631415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f64e-1f3ff.svg000066400000000000000000000030641415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f64e.svg000066400000000000000000000030671415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f64f-1f3fb.svg000066400000000000000000000043441415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f64f-1f3fc.svg000066400000000000000000000043421415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f64f-1f3fd.svg000066400000000000000000000043451415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f64f-1f3fe.svg000066400000000000000000000043421415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f64f-1f3ff.svg000066400000000000000000000043421415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f64f.svg000066400000000000000000000043501415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f680.svg000066400000000000000000000062451415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f681.svg000066400000000000000000000046661415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f682.svg000066400000000000000000000130061415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f683.svg000066400000000000000000000071471415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f684.svg000066400000000000000000000053661415623743500166200ustar00rootroot00000000000000qTox/smileys/emojione/1f685.svg000066400000000000000000000066051415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f686.svg000066400000000000000000000052451415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f687.svg000066400000000000000000000024231415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f688.svg000066400000000000000000000021401415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f689.svg000066400000000000000000000040731415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f68a.svg000066400000000000000000000033621415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f68b.svg000066400000000000000000000045151415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f68c.svg000066400000000000000000000046521415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f68d.svg000066400000000000000000000114641415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f68e.svg000066400000000000000000000052331415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f68f.svg000066400000000000000000000034241415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f690.svg000066400000000000000000000057631415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f691.svg000066400000000000000000000057031415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f692.svg000066400000000000000000000120241415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f693.svg000066400000000000000000000107051415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f694.svg000066400000000000000000000066071415623743500166200ustar00rootroot00000000000000qTox/smileys/emojione/1f695.svg000066400000000000000000000120711415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f696.svg000066400000000000000000000122161415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f697.svg000066400000000000000000000076771415623743500166330ustar00rootroot00000000000000qTox/smileys/emojione/1f698.svg000066400000000000000000000067161415623743500166250ustar00rootroot00000000000000qTox/smileys/emojione/1f699.svg000066400000000000000000000107141415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f69a.svg000066400000000000000000000103071415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f69b.svg000066400000000000000000000120051415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f69c.svg000066400000000000000000000031301415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f69d.svg000066400000000000000000000034561415623743500166770ustar00rootroot00000000000000qTox/smileys/emojione/1f69e.svg000066400000000000000000000076531415623743500167030ustar00rootroot00000000000000qTox/smileys/emojione/1f69f.svg000066400000000000000000000031541415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f6a0.svg000066400000000000000000000022071415623743500166540ustar00rootroot00000000000000qTox/smileys/emojione/1f6a1.svg000066400000000000000000000017031415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f6a2.svg000066400000000000000000000052301415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f6a3-1f3fb.svg000066400000000000000000000075711415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f6a3-1f3fc.svg000066400000000000000000000075711415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f6a3-1f3fd.svg000066400000000000000000000075711415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f6a3-1f3fe.svg000066400000000000000000000075711415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f6a3-1f3ff.svg000066400000000000000000000075711415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f6a3.svg000066400000000000000000000075621415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f6a4.svg000066400000000000000000000066051415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f6a5.svg000066400000000000000000000010241415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f6a6.svg000066400000000000000000000010251415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f6a7.svg000066400000000000000000000045561415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f6a8.svg000066400000000000000000000034551415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f6a9.svg000066400000000000000000000006741415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f6aa.svg000066400000000000000000000022761415623743500167430ustar00rootroot00000000000000qTox/smileys/emojione/1f6ab.svg000066400000000000000000000006631415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f6ac.svg000066400000000000000000000005721415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f6ad.svg000066400000000000000000000051241415623743500167410ustar00rootroot00000000000000qTox/smileys/emojione/1f6ae.svg000066400000000000000000000006171415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f6af.svg000066400000000000000000000014011415623743500167350ustar00rootroot00000000000000qTox/smileys/emojione/1f6b0.svg000066400000000000000000000023031415623743500166520ustar00rootroot00000000000000qTox/smileys/emojione/1f6b1.svg000066400000000000000000000024231415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f6b2.svg000066400000000000000000000066541415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f6b3.svg000066400000000000000000000043311415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f6b4-1f3fb.svg000066400000000000000000000120041415623743500175540ustar00rootroot00000000000000qTox/smileys/emojione/1f6b4-1f3fc.svg000066400000000000000000000120041415623743500175550ustar00rootroot00000000000000qTox/smileys/emojione/1f6b4-1f3fd.svg000066400000000000000000000120041415623743500175560ustar00rootroot00000000000000qTox/smileys/emojione/1f6b4-1f3fe.svg000066400000000000000000000120041415623743500175570ustar00rootroot00000000000000qTox/smileys/emojione/1f6b4-1f3ff.svg000066400000000000000000000120041415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f6b4.svg000066400000000000000000000120041415623743500166550ustar00rootroot00000000000000qTox/smileys/emojione/1f6b5-1f3fb.svg000066400000000000000000000134071415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f6b5-1f3fc.svg000066400000000000000000000134071415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f6b5-1f3fd.svg000066400000000000000000000134071415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f6b5-1f3fe.svg000066400000000000000000000134071415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f6b5-1f3ff.svg000066400000000000000000000134071415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f6b5.svg000066400000000000000000000134071415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f6b6-1f3fb.svg000066400000000000000000000052111415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f6b6-1f3fc.svg000066400000000000000000000052111415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f6b6-1f3fd.svg000066400000000000000000000052211415623743500175630ustar00rootroot00000000000000qTox/smileys/emojione/1f6b6-1f3fe.svg000066400000000000000000000052121415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f6b6-1f3ff.svg000066400000000000000000000052271415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f6b6.svg000066400000000000000000000052121415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f6b7.svg000066400000000000000000000016261415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f6b8.svg000066400000000000000000000046241415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f6b9.svg000066400000000000000000000004761415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f6ba.svg000066400000000000000000000005131415623743500167340ustar00rootroot00000000000000qTox/smileys/emojione/1f6bb.svg000066400000000000000000000010031415623743500167300ustar00rootroot00000000000000qTox/smileys/emojione/1f6bc.svg000066400000000000000000000006521415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f6bd.svg000066400000000000000000000026511415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f6be.svg000066400000000000000000000010461415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f6bf.svg000066400000000000000000000062571415623743500167540ustar00rootroot00000000000000qTox/smileys/emojione/1f6c0-1f3fb.svg000066400000000000000000000067031415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f6c0-1f3fc.svg000066400000000000000000000067001415623743500175600ustar00rootroot00000000000000qTox/smileys/emojione/1f6c0-1f3fd.svg000066400000000000000000000067001415623743500175610ustar00rootroot00000000000000qTox/smileys/emojione/1f6c0-1f3fe.svg000066400000000000000000000067071415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f6c0-1f3ff.svg000066400000000000000000000067071415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f6c0.svg000066400000000000000000000067011415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f6c1.svg000066400000000000000000000021711415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f6c2.svg000066400000000000000000000010441415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f6c3.svg000066400000000000000000000007601415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f6c4.svg000066400000000000000000000013151415623743500166610ustar00rootroot00000000000000qTox/smileys/emojione/1f6c5.svg000066400000000000000000000015201415623743500166600ustar00rootroot00000000000000qTox/smileys/emojione/1f6cb.svg000066400000000000000000000074171415623743500167500ustar00rootroot00000000000000qTox/smileys/emojione/1f6cc.svg000066400000000000000000000031371415623743500167440ustar00rootroot00000000000000qTox/smileys/emojione/1f6cd.svg000066400000000000000000000053031415623743500167420ustar00rootroot00000000000000qTox/smileys/emojione/1f6ce.svg000066400000000000000000000021151415623743500167410ustar00rootroot00000000000000qTox/smileys/emojione/1f6cf.svg000066400000000000000000000052671415623743500167550ustar00rootroot00000000000000qTox/smileys/emojione/1f6d0.svg000066400000000000000000000013211415623743500166530ustar00rootroot00000000000000qTox/smileys/emojione/1f6d1.svg000066400000000000000000000004331415623743500166570ustar00rootroot00000000000000qTox/smileys/emojione/1f6d2.svg000066400000000000000000000060661415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f6e0.svg000066400000000000000000000033211415623743500166560ustar00rootroot00000000000000qTox/smileys/emojione/1f6e1.svg000066400000000000000000000166531415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f6e2.svg000066400000000000000000000016371415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f6e3.svg000066400000000000000000000021701415623743500166620ustar00rootroot00000000000000qTox/smileys/emojione/1f6e4.svg000066400000000000000000000042641415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f6e5.svg000066400000000000000000000031661415623743500166720ustar00rootroot00000000000000qTox/smileys/emojione/1f6e9.svg000066400000000000000000000034351415623743500166750ustar00rootroot00000000000000qTox/smileys/emojione/1f6eb.svg000066400000000000000000000033771415623743500167530ustar00rootroot00000000000000qTox/smileys/emojione/1f6ec.svg000066400000000000000000000055311415623743500167460ustar00rootroot00000000000000qTox/smileys/emojione/1f6f0.svg000066400000000000000000000035621415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f6f3.svg000066400000000000000000000102651415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f6f4.svg000066400000000000000000000054001415623743500166630ustar00rootroot00000000000000qTox/smileys/emojione/1f6f5.svg000066400000000000000000000072501415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f6f6.svg000066400000000000000000000031661415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f910.svg000066400000000000000000000026201415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/1f911.svg000066400000000000000000000034671415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f912.svg000066400000000000000000000026551415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f913.svg000066400000000000000000000026311415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f914.svg000066400000000000000000000057251415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f915.svg000066400000000000000000000021471415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f916.svg000066400000000000000000000047121415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f917.svg000066400000000000000000000066501415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f918-1f3fb.svg000066400000000000000000000064521415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f918-1f3fc.svg000066400000000000000000000064651415623743500175210ustar00rootroot00000000000000qTox/smileys/emojione/1f918-1f3fd.svg000066400000000000000000000064521415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f918-1f3fe.svg000066400000000000000000000064631415623743500175210ustar00rootroot00000000000000qTox/smileys/emojione/1f918-1f3ff.svg000066400000000000000000000064471415623743500175240ustar00rootroot00000000000000qTox/smileys/emojione/1f918.svg000066400000000000000000000065221415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f919-1f3fb.svg000066400000000000000000000056601415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f919-1f3fc.svg000066400000000000000000000056761415623743500175250ustar00rootroot00000000000000qTox/smileys/emojione/1f919-1f3fd.svg000066400000000000000000000056671415623743500175260ustar00rootroot00000000000000qTox/smileys/emojione/1f919-1f3fe.svg000066400000000000000000000056641415623743500175240ustar00rootroot00000000000000qTox/smileys/emojione/1f919-1f3ff.svg000066400000000000000000000056701415623743500175220ustar00rootroot00000000000000qTox/smileys/emojione/1f919.svg000066400000000000000000000056571415623743500166240ustar00rootroot00000000000000qTox/smileys/emojione/1f91a-1f3fb.svg000066400000000000000000000056731415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f91a-1f3fc.svg000066400000000000000000000057011415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f91a-1f3fd.svg000066400000000000000000000057071415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f91a-1f3fe.svg000066400000000000000000000057011415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f91a-1f3ff.svg000066400000000000000000000057011415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f91a.svg000066400000000000000000000056771415623743500166760ustar00rootroot00000000000000qTox/smileys/emojione/1f91b-1f3fb.svg000066400000000000000000000064271415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f91b-1f3fc.svg000066400000000000000000000064461415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f91b-1f3fd.svg000066400000000000000000000064271415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f91b-1f3fe.svg000066400000000000000000000064271415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f91b-1f3ff.svg000066400000000000000000000064351415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f91b.svg000066400000000000000000000064501415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f91c-1f3fb.svg000066400000000000000000000064401415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f91c-1f3fc.svg000066400000000000000000000064351415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f91c-1f3fd.svg000066400000000000000000000064311415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f91c-1f3fe.svg000066400000000000000000000064311415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f91c-1f3ff.svg000066400000000000000000000064311415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f91c.svg000066400000000000000000000064561415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f91d-1f3fb.svg000066400000000000000000000071231415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f91d-1f3fc.svg000066400000000000000000000071251415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f91d-1f3fd.svg000066400000000000000000000071301415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f91d-1f3fe.svg000066400000000000000000000071151415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f91d-1f3ff.svg000066400000000000000000000071261415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f91d.svg000066400000000000000000000070771415623743500166750ustar00rootroot00000000000000qTox/smileys/emojione/1f91e-1f3fb.svg000066400000000000000000000073721415623743500175730ustar00rootroot00000000000000qTox/smileys/emojione/1f91e-1f3fc.svg000066400000000000000000000073551415623743500175750ustar00rootroot00000000000000qTox/smileys/emojione/1f91e-1f3fd.svg000066400000000000000000000073651415623743500175770ustar00rootroot00000000000000qTox/smileys/emojione/1f91e-1f3fe.svg000066400000000000000000000073671415623743500176020ustar00rootroot00000000000000qTox/smileys/emojione/1f91e-1f3ff.svg000066400000000000000000000073651415623743500176010ustar00rootroot00000000000000qTox/smileys/emojione/1f91e.svg000066400000000000000000000073501415623743500166700ustar00rootroot00000000000000qTox/smileys/emojione/1f920.svg000066400000000000000000000035061415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f921.svg000066400000000000000000000060471415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f922.svg000066400000000000000000000022641415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f923.svg000066400000000000000000000061071415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f924.svg000066400000000000000000000024041415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f925.svg000066400000000000000000000024011415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f926-1f3fb.svg000066400000000000000000000035431415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f926-1f3fc.svg000066400000000000000000000035331415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f926-1f3fd.svg000066400000000000000000000035431415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f926-1f3fe.svg000066400000000000000000000035371415623743500175170ustar00rootroot00000000000000qTox/smileys/emojione/1f926-1f3ff.svg000066400000000000000000000035431415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f926.svg000066400000000000000000000035371415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f927.svg000066400000000000000000000027121415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f930-1f3fb.svg000066400000000000000000000064421415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f930-1f3fc.svg000066400000000000000000000064451415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f930-1f3fd.svg000066400000000000000000000064251415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f930-1f3fe.svg000066400000000000000000000064511415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f930-1f3ff.svg000066400000000000000000000064411415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f930.svg000066400000000000000000000064431415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f933-1f3fb.svg000066400000000000000000000056151415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f933-1f3fc.svg000066400000000000000000000056101415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f933-1f3fd.svg000066400000000000000000000056161415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f933-1f3fe.svg000066400000000000000000000056101415623743500175070ustar00rootroot00000000000000qTox/smileys/emojione/1f933-1f3ff.svg000066400000000000000000000056051415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f933.svg000066400000000000000000000056131415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f934-1f3fb.svg000066400000000000000000000066501415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f934-1f3fc.svg000066400000000000000000000066451415623743500175170ustar00rootroot00000000000000qTox/smileys/emojione/1f934-1f3fd.svg000066400000000000000000000066331415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f934-1f3fe.svg000066400000000000000000000066451415623743500175210ustar00rootroot00000000000000qTox/smileys/emojione/1f934-1f3ff.svg000066400000000000000000000066411415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f934.svg000066400000000000000000000067041415623743500166130ustar00rootroot00000000000000qTox/smileys/emojione/1f935-1f3fb.svg000066400000000000000000000051001415623743500175000ustar00rootroot00000000000000qTox/smileys/emojione/1f935-1f3fc.svg000066400000000000000000000051171415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f935-1f3fd.svg000066400000000000000000000050771415623743500175170ustar00rootroot00000000000000qTox/smileys/emojione/1f935-1f3fe.svg000066400000000000000000000051171415623743500175130ustar00rootroot00000000000000qTox/smileys/emojione/1f935-1f3ff.svg000066400000000000000000000051101415623743500175050ustar00rootroot00000000000000qTox/smileys/emojione/1f935.svg000066400000000000000000000051171415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f936-1f3fb.svg000066400000000000000000000062051415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f936-1f3fc.svg000066400000000000000000000063301415623743500175100ustar00rootroot00000000000000qTox/smileys/emojione/1f936-1f3fd.svg000066400000000000000000000063211415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f936-1f3fe.svg000066400000000000000000000061671415623743500175220ustar00rootroot00000000000000qTox/smileys/emojione/1f936-1f3ff.svg000066400000000000000000000064431415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f936.svg000066400000000000000000000100131415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/1f937-1f3fb.svg000066400000000000000000000076231415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f937-1f3fc.svg000066400000000000000000000076041415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f937-1f3fd.svg000066400000000000000000000076111415623743500175150ustar00rootroot00000000000000qTox/smileys/emojione/1f937-1f3fe.svg000066400000000000000000000076041415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f937-1f3ff.svg000066400000000000000000000076231415623743500175220ustar00rootroot00000000000000qTox/smileys/emojione/1f937.svg000066400000000000000000000076041415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f938-1f3fb.svg000066400000000000000000000064071415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f938-1f3fc.svg000066400000000000000000000064201415623743500175120ustar00rootroot00000000000000qTox/smileys/emojione/1f938-1f3fd.svg000066400000000000000000000064071415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f938-1f3fe.svg000066400000000000000000000064071415623743500175210ustar00rootroot00000000000000qTox/smileys/emojione/1f938-1f3ff.svg000066400000000000000000000064071415623743500175220ustar00rootroot00000000000000qTox/smileys/emojione/1f938.svg000066400000000000000000000064201415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f939-1f3fb.svg000066400000000000000000000051171415623743500175140ustar00rootroot00000000000000qTox/smileys/emojione/1f939-1f3fc.svg000066400000000000000000000050501415623743500175110ustar00rootroot00000000000000qTox/smileys/emojione/1f939-1f3fd.svg000066400000000000000000000051171415623743500175160ustar00rootroot00000000000000qTox/smileys/emojione/1f939-1f3fe.svg000066400000000000000000000051171415623743500175170ustar00rootroot00000000000000qTox/smileys/emojione/1f939-1f3ff.svg000066400000000000000000000051171415623743500175200ustar00rootroot00000000000000qTox/smileys/emojione/1f939.svg000066400000000000000000000050501415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f93a.svg000066400000000000000000000067031415623743500166670ustar00rootroot00000000000000qTox/smileys/emojione/1f93b-1f3fb.svg000066400000000000000000000141711415623743500175650ustar00rootroot00000000000000qTox/smileys/emojione/1f93b-1f3fc.svg000066400000000000000000000142211415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f93b-1f3fd.svg000066400000000000000000000141711415623743500175670ustar00rootroot00000000000000qTox/smileys/emojione/1f93b-1f3fe.svg000066400000000000000000000141711415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f93b-1f3ff.svg000066400000000000000000000141711415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f93b.svg000066400000000000000000000115751415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f93c-1f3fb.svg000066400000000000000000000115721415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f93c-1f3fc.svg000066400000000000000000000115711415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f93c-1f3fd.svg000066400000000000000000000116161415623743500175710ustar00rootroot00000000000000qTox/smileys/emojione/1f93c-1f3fe.svg000066400000000000000000000116161415623743500175720ustar00rootroot00000000000000qTox/smileys/emojione/1f93c-1f3ff.svg000066400000000000000000000115721415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f93c.svg000066400000000000000000000053231415623743500166660ustar00rootroot00000000000000qTox/smileys/emojione/1f93d-1f3fb.svg000066400000000000000000000110451415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f93d-1f3fc.svg000066400000000000000000000107641415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f93d-1f3fd.svg000066400000000000000000000111131415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f93d-1f3fe.svg000066400000000000000000000107351415623743500175740ustar00rootroot00000000000000qTox/smileys/emojione/1f93d-1f3ff.svg000066400000000000000000000107351415623743500175750ustar00rootroot00000000000000qTox/smileys/emojione/1f93d.svg000066400000000000000000000070711415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f93e-1f3fb.svg000066400000000000000000000103001415623743500175560ustar00rootroot00000000000000qTox/smileys/emojione/1f93e-1f3fc.svg000066400000000000000000000103271415623743500175700ustar00rootroot00000000000000qTox/smileys/emojione/1f93e-1f3fd.svg000066400000000000000000000103061415623743500175660ustar00rootroot00000000000000qTox/smileys/emojione/1f93e-1f3fe.svg000066400000000000000000000103031415623743500175640ustar00rootroot00000000000000qTox/smileys/emojione/1f93e-1f3ff.svg000066400000000000000000000103001415623743500175620ustar00rootroot00000000000000qTox/smileys/emojione/1f93e.svg000066400000000000000000000113311415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f93f.svg000066400000000000000000000103051415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f940.svg000066400000000000000000000043531415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f942.svg000066400000000000000000000066111415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f943.svg000066400000000000000000000077651415623743500166230ustar00rootroot00000000000000qTox/smileys/emojione/1f944.svg000066400000000000000000000017201415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f945.svg000066400000000000000000000125241415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f946.svg000066400000000000000000000031421415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/1f947.svg000066400000000000000000000034351415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f948.svg000066400000000000000000000041021415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f949.svg000066400000000000000000000045611415623743500166200ustar00rootroot00000000000000qTox/smileys/emojione/1f950.svg000066400000000000000000000076571415623743500166210ustar00rootroot00000000000000qTox/smileys/emojione/1f951.svg000066400000000000000000000042321415623743500166040ustar00rootroot00000000000000qTox/smileys/emojione/1f952.svg000066400000000000000000000057241415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f953.svg000066400000000000000000000065161415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f954.svg000066400000000000000000000051761415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f955.svg000066400000000000000000000061361415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f956.svg000066400000000000000000000051571415623743500166200ustar00rootroot00000000000000qTox/smileys/emojione/1f957.svg000066400000000000000000000116251415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f958.svg000066400000000000000000000053231415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f959.svg000066400000000000000000000117641415623743500166240ustar00rootroot00000000000000qTox/smileys/emojione/1f95a.svg000066400000000000000000000010551415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f95b.svg000066400000000000000000000100631415623743500166640ustar00rootroot00000000000000qTox/smileys/emojione/1f95c.svg000066400000000000000000000402221415623743500166650ustar00rootroot00000000000000qTox/smileys/emojione/1f95d.svg000066400000000000000000000050531415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f95e.svg000066400000000000000000000075111415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f960.svg000066400000000000000000000142211415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/1f961.svg000066400000000000000000000057211415623743500166110ustar00rootroot00000000000000qTox/smileys/emojione/1f980.svg000066400000000000000000000141611415623743500166100ustar00rootroot00000000000000qTox/smileys/emojione/1f981.svg000066400000000000000000000072001415623743500166050ustar00rootroot00000000000000qTox/smileys/emojione/1f982.svg000066400000000000000000000133271415623743500166150ustar00rootroot00000000000000qTox/smileys/emojione/1f983.svg000066400000000000000000000047421415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f984.svg000066400000000000000000000070221415623743500166120ustar00rootroot00000000000000qTox/smileys/emojione/1f985.svg000066400000000000000000000071251415623743500166170ustar00rootroot00000000000000qTox/smileys/emojione/1f986.svg000066400000000000000000000105161415623743500166160ustar00rootroot00000000000000qTox/smileys/emojione/1f987.svg000066400000000000000000000102611415623743500166140ustar00rootroot00000000000000qTox/smileys/emojione/1f988.svg000066400000000000000000000073301415623743500166200ustar00rootroot00000000000000qTox/smileys/emojione/1f989.svg000066400000000000000000000064361415623743500166270ustar00rootroot00000000000000qTox/smileys/emojione/1f98a.svg000066400000000000000000000061651415623743500166760ustar00rootroot00000000000000qTox/smileys/emojione/1f98b.svg000066400000000000000000000150441415623743500166730ustar00rootroot00000000000000qTox/smileys/emojione/1f98c.svg000066400000000000000000000076441415623743500167030ustar00rootroot00000000000000qTox/smileys/emojione/1f98d.svg000066400000000000000000000051401415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/1f98e.svg000066400000000000000000000120721415623743500166740ustar00rootroot00000000000000qTox/smileys/emojione/1f98f.svg000066400000000000000000000056601415623743500167020ustar00rootroot00000000000000qTox/smileys/emojione/1f990.svg000066400000000000000000000063101415623743500166060ustar00rootroot00000000000000qTox/smileys/emojione/1f991.svg000066400000000000000000000064371415623743500166210ustar00rootroot00000000000000qTox/smileys/emojione/1f9c0.svg000066400000000000000000000161471415623743500166710ustar00rootroot00000000000000qTox/smileys/emojione/203c.svg000066400000000000000000000004671415623743500165140ustar00rootroot00000000000000qTox/smileys/emojione/2049.svg000066400000000000000000000012601415623743500164330ustar00rootroot00000000000000qTox/smileys/emojione/2122.svg000066400000000000000000000003631415623743500164260ustar00rootroot00000000000000qTox/smileys/emojione/2139.svg000066400000000000000000000003341415623743500164340ustar00rootroot00000000000000qTox/smileys/emojione/2194.svg000066400000000000000000000003611415623743500164350ustar00rootroot00000000000000qTox/smileys/emojione/2195.svg000066400000000000000000000003561415623743500164420ustar00rootroot00000000000000qTox/smileys/emojione/2196.svg000066400000000000000000000003711415623743500164400ustar00rootroot00000000000000qTox/smileys/emojione/2197.svg000066400000000000000000000003721415623743500164420ustar00rootroot00000000000000qTox/smileys/emojione/2198.svg000066400000000000000000000003701415623743500164410ustar00rootroot00000000000000qTox/smileys/emojione/2199.svg000066400000000000000000000003721415623743500164440ustar00rootroot00000000000000qTox/smileys/emojione/21a9.svg000066400000000000000000000006001415623743500165060ustar00rootroot00000000000000qTox/smileys/emojione/21aa.svg000066400000000000000000000006011415623743500165570ustar00rootroot00000000000000qTox/smileys/emojione/231a.svg000066400000000000000000000051341415623743500165070ustar00rootroot00000000000000qTox/smileys/emojione/231b.svg000066400000000000000000000043401415623743500165060ustar00rootroot00000000000000qTox/smileys/emojione/2328.svg000066400000000000000000000131071415623743500164360ustar00rootroot00000000000000qTox/smileys/emojione/23cf.svg000066400000000000000000000003411415623743500165710ustar00rootroot00000000000000qTox/smileys/emojione/23e9.svg000066400000000000000000000003411415623743500165160ustar00rootroot00000000000000qTox/smileys/emojione/23ea.svg000066400000000000000000000003411415623743500165660ustar00rootroot00000000000000qTox/smileys/emojione/23eb.svg000066400000000000000000000003641415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/23ec.svg000066400000000000000000000003451415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/23ed.svg000066400000000000000000000003711415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/23ee.svg000066400000000000000000000004401415623743500165720ustar00rootroot00000000000000qTox/smileys/emojione/23ef.svg000066400000000000000000000003631415623743500165770ustar00rootroot00000000000000qTox/smileys/emojione/23f0.svg000066400000000000000000000020761415623743500165150ustar00rootroot00000000000000qTox/smileys/emojione/23f1.svg000066400000000000000000000032041415623743500165100ustar00rootroot00000000000000qTox/smileys/emojione/23f2.svg000066400000000000000000000040251415623743500165130ustar00rootroot00000000000000qTox/smileys/emojione/23f3.svg000066400000000000000000000045471415623743500165250ustar00rootroot00000000000000qTox/smileys/emojione/23f8.svg000066400000000000000000000003111415623743500165130ustar00rootroot00000000000000qTox/smileys/emojione/23f9.svg000066400000000000000000000002601415623743500165170ustar00rootroot00000000000000qTox/smileys/emojione/23fa.svg000066400000000000000000000002641415623743500165730ustar00rootroot00000000000000qTox/smileys/emojione/24c2.svg000066400000000000000000000003131415623743500165050ustar00rootroot00000000000000qTox/smileys/emojione/25aa.svg000066400000000000000000000002011415623743500165570ustar00rootroot00000000000000qTox/smileys/emojione/25ab.svg000066400000000000000000000002041415623743500165630ustar00rootroot00000000000000qTox/smileys/emojione/25b6.svg000066400000000000000000000002771415623743500165220ustar00rootroot00000000000000qTox/smileys/emojione/25c0.svg000066400000000000000000000002631415623743500165100ustar00rootroot00000000000000qTox/smileys/emojione/25fb.svg000066400000000000000000000002011415623743500165650ustar00rootroot00000000000000qTox/smileys/emojione/25fc.svg000066400000000000000000000001761415623743500166010ustar00rootroot00000000000000qTox/smileys/emojione/25fd.svg000066400000000000000000000002041415623743500165720ustar00rootroot00000000000000qTox/smileys/emojione/25fe.svg000066400000000000000000000002011415623743500165700ustar00rootroot00000000000000qTox/smileys/emojione/2600.svg000066400000000000000000000025541415623743500164330ustar00rootroot00000000000000qTox/smileys/emojione/2601.svg000066400000000000000000000030511415623743500164250ustar00rootroot00000000000000qTox/smileys/emojione/2602.svg000066400000000000000000000020111415623743500164210ustar00rootroot00000000000000qTox/smileys/emojione/2603.svg000066400000000000000000000070551415623743500164370ustar00rootroot00000000000000qTox/smileys/emojione/2604.svg000066400000000000000000000033041415623743500164310ustar00rootroot00000000000000qTox/smileys/emojione/260e.svg000066400000000000000000000104641415623743500165170ustar00rootroot00000000000000qTox/smileys/emojione/2611.svg000066400000000000000000000007221415623743500164300ustar00rootroot00000000000000qTox/smileys/emojione/2614.svg000066400000000000000000000033611415623743500164350ustar00rootroot00000000000000qTox/smileys/emojione/2615.svg000066400000000000000000000051641415623743500164410ustar00rootroot00000000000000qTox/smileys/emojione/2618.svg000066400000000000000000000027611415623743500164440ustar00rootroot00000000000000qTox/smileys/emojione/261d-1f3fb.svg000066400000000000000000000057711415623743500174230ustar00rootroot00000000000000qTox/smileys/emojione/261d-1f3fc.svg000066400000000000000000000057651415623743500174270ustar00rootroot00000000000000qTox/smileys/emojione/261d-1f3fd.svg000066400000000000000000000057711415623743500174250ustar00rootroot00000000000000qTox/smileys/emojione/261d-1f3fe.svg000066400000000000000000000057711415623743500174260ustar00rootroot00000000000000qTox/smileys/emojione/261d-1f3ff.svg000066400000000000000000000057711415623743500174270ustar00rootroot00000000000000qTox/smileys/emojione/261d.svg000066400000000000000000000057651415623743500165270ustar00rootroot00000000000000qTox/smileys/emojione/2620.svg000066400000000000000000000046241415623743500164350ustar00rootroot00000000000000qTox/smileys/emojione/2622.svg000066400000000000000000000011521415623743500164300ustar00rootroot00000000000000qTox/smileys/emojione/2623.svg000066400000000000000000000036211415623743500164340ustar00rootroot00000000000000qTox/smileys/emojione/2626.svg000066400000000000000000000003061415623743500164340ustar00rootroot00000000000000qTox/smileys/emojione/262a.svg000066400000000000000000000007641415623743500165170ustar00rootroot00000000000000qTox/smileys/emojione/262e.svg000066400000000000000000000030041415623743500165110ustar00rootroot00000000000000qTox/smileys/emojione/262f.svg000066400000000000000000000012561415623743500165210ustar00rootroot00000000000000qTox/smileys/emojione/2638.svg000066400000000000000000000061741415623743500164500ustar00rootroot00000000000000qTox/smileys/emojione/2639.svg000066400000000000000000000006721415623743500164460ustar00rootroot00000000000000qTox/smileys/emojione/263a.svg000066400000000000000000000007311415623743500165120ustar00rootroot00000000000000qTox/smileys/emojione/2648.svg000066400000000000000000000012151415623743500164400ustar00rootroot00000000000000qTox/smileys/emojione/2649.svg000066400000000000000000000014721415623743500164460ustar00rootroot00000000000000qTox/smileys/emojione/264a.svg000066400000000000000000000012041415623743500165070ustar00rootroot00000000000000qTox/smileys/emojione/264b.svg000066400000000000000000000022051415623743500165120ustar00rootroot00000000000000qTox/smileys/emojione/264c.svg000066400000000000000000000017021415623743500165140ustar00rootroot00000000000000qTox/smileys/emojione/264d.svg000066400000000000000000000021711415623743500165160ustar00rootroot00000000000000qTox/smileys/emojione/264e.svg000066400000000000000000000015201415623743500165140ustar00rootroot00000000000000qTox/smileys/emojione/264f.svg000066400000000000000000000021561415623743500165230ustar00rootroot00000000000000qTox/smileys/emojione/2650.svg000066400000000000000000000015111415623743500164300ustar00rootroot00000000000000qTox/smileys/emojione/2651.svg000066400000000000000000000020051415623743500164300ustar00rootroot00000000000000qTox/smileys/emojione/2652.svg000066400000000000000000000020301415623743500164270ustar00rootroot00000000000000qTox/smileys/emojione/2653.svg000066400000000000000000000013401415623743500164330ustar00rootroot00000000000000qTox/smileys/emojione/2660.svg000066400000000000000000000006551415623743500164410ustar00rootroot00000000000000qTox/smileys/emojione/2663.svg000066400000000000000000000010411415623743500164320ustar00rootroot00000000000000qTox/smileys/emojione/2665.svg000066400000000000000000000004421415623743500164400ustar00rootroot00000000000000qTox/smileys/emojione/2666.svg000066400000000000000000000002171415623743500164410ustar00rootroot00000000000000qTox/smileys/emojione/2668.svg000066400000000000000000000016341415623743500164470ustar00rootroot00000000000000qTox/smileys/emojione/267b.svg000066400000000000000000000021731415623743500165210ustar00rootroot00000000000000qTox/smileys/emojione/267f.svg000066400000000000000000000010521415623743500165200ustar00rootroot00000000000000qTox/smileys/emojione/2692.svg000066400000000000000000000032321415623743500164400ustar00rootroot00000000000000qTox/smileys/emojione/2693.svg000066400000000000000000000027271415623743500164510ustar00rootroot00000000000000qTox/smileys/emojione/2694.svg000066400000000000000000000110651415623743500164450ustar00rootroot00000000000000qTox/smileys/emojione/2696.svg000066400000000000000000000030531415623743500164450ustar00rootroot00000000000000qTox/smileys/emojione/2697.svg000066400000000000000000000014161415623743500164470ustar00rootroot00000000000000qTox/smileys/emojione/2699.svg000066400000000000000000000045411415623743500164530ustar00rootroot00000000000000qTox/smileys/emojione/269b.svg000066400000000000000000000057731415623743500165340ustar00rootroot00000000000000qTox/smileys/emojione/269c.svg000066400000000000000000000026311415623743500165230ustar00rootroot00000000000000qTox/smileys/emojione/26a0.svg000066400000000000000000000006631415623743500165130ustar00rootroot00000000000000qTox/smileys/emojione/26a1.svg000066400000000000000000000003071415623743500165070ustar00rootroot00000000000000qTox/smileys/emojione/26aa.svg000066400000000000000000000002101415623743500165600ustar00rootroot00000000000000qTox/smileys/emojione/26ab.svg000066400000000000000000000002101415623743500165610ustar00rootroot00000000000000qTox/smileys/emojione/26b0.svg000066400000000000000000000104251415623743500165110ustar00rootroot00000000000000qTox/smileys/emojione/26b1.svg000066400000000000000000000042761415623743500165210ustar00rootroot00000000000000qTox/smileys/emojione/26bd.svg000066400000000000000000000047651415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/26be.svg000066400000000000000000000046101415623743500165750ustar00rootroot00000000000000qTox/smileys/emojione/26c4.svg000066400000000000000000000037171415623743500165240ustar00rootroot00000000000000qTox/smileys/emojione/26c5.svg000066400000000000000000000057441415623743500165270ustar00rootroot00000000000000qTox/smileys/emojione/26c8.svg000066400000000000000000000051441415623743500165240ustar00rootroot00000000000000qTox/smileys/emojione/26ce.svg000066400000000000000000000011541415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/26cf.svg000066400000000000000000000026431415623743500166030ustar00rootroot00000000000000qTox/smileys/emojione/26d1.svg000066400000000000000000000016311415623743500165130ustar00rootroot00000000000000qTox/smileys/emojione/26d3.svg000066400000000000000000000042011415623743500165110ustar00rootroot00000000000000qTox/smileys/emojione/26d4.svg000066400000000000000000000002561415623743500165200ustar00rootroot00000000000000qTox/smileys/emojione/26e9.svg000066400000000000000000000026021415623743500165230ustar00rootroot00000000000000qTox/smileys/emojione/26ea.svg000066400000000000000000000116141415623743500165760ustar00rootroot00000000000000qTox/smileys/emojione/26f0.svg000066400000000000000000000056021415623743500165160ustar00rootroot00000000000000qTox/smileys/emojione/26f1.svg000066400000000000000000000044471415623743500165250ustar00rootroot00000000000000qTox/smileys/emojione/26f2.svg000066400000000000000000000050371415623743500165220ustar00rootroot00000000000000qTox/smileys/emojione/26f3.svg000066400000000000000000000015141415623743500165170ustar00rootroot00000000000000qTox/smileys/emojione/26f4.svg000066400000000000000000000034731415623743500165260ustar00rootroot00000000000000qTox/smileys/emojione/26f5.svg000066400000000000000000000053331415623743500165240ustar00rootroot00000000000000qTox/smileys/emojione/26f7.svg000066400000000000000000000101121415623743500165150ustar00rootroot00000000000000qTox/smileys/emojione/26f8.svg000066400000000000000000000100651415623743500165250ustar00rootroot00000000000000qTox/smileys/emojione/26f9-1f3fb.svg000066400000000000000000000112431415623743500174240ustar00rootroot00000000000000qTox/smileys/emojione/26f9-1f3fc.svg000066400000000000000000000112431415623743500174250ustar00rootroot00000000000000qTox/smileys/emojione/26f9-1f3fd.svg000066400000000000000000000112431415623743500174260ustar00rootroot00000000000000qTox/smileys/emojione/26f9-1f3fe.svg000066400000000000000000000112431415623743500174270ustar00rootroot00000000000000qTox/smileys/emojione/26f9-1f3ff.svg000066400000000000000000000112431415623743500174300ustar00rootroot00000000000000qTox/smileys/emojione/26f9.svg000066400000000000000000000112431415623743500165250ustar00rootroot00000000000000qTox/smileys/emojione/26fa.svg000066400000000000000000000041721415623743500166000ustar00rootroot00000000000000qTox/smileys/emojione/26fd.svg000066400000000000000000000042101415623743500165740ustar00rootroot00000000000000qTox/smileys/emojione/2702.svg000066400000000000000000000026611415623743500164350ustar00rootroot00000000000000qTox/smileys/emojione/2705.svg000066400000000000000000000003621415623743500164340ustar00rootroot00000000000000qTox/smileys/emojione/2708.svg000066400000000000000000000025301415623743500164360ustar00rootroot00000000000000qTox/smileys/emojione/2709.svg000066400000000000000000000012771415623743500164460ustar00rootroot00000000000000qTox/smileys/emojione/270a-1f3fb.svg000066400000000000000000000065201415623743500174110ustar00rootroot00000000000000qTox/smileys/emojione/270a-1f3fc.svg000066400000000000000000000065161415623743500174170ustar00rootroot00000000000000qTox/smileys/emojione/270a-1f3fd.svg000066400000000000000000000065161415623743500174200ustar00rootroot00000000000000qTox/smileys/emojione/270a-1f3fe.svg000066400000000000000000000065161415623743500174210ustar00rootroot00000000000000qTox/smileys/emojione/270a-1f3ff.svg000066400000000000000000000065161415623743500174220ustar00rootroot00000000000000qTox/smileys/emojione/270a.svg000066400000000000000000000065051415623743500165150ustar00rootroot00000000000000qTox/smileys/emojione/270b-1f3fb.svg000066400000000000000000000035571415623743500174210ustar00rootroot00000000000000qTox/smileys/emojione/270b-1f3fc.svg000066400000000000000000000035571415623743500174220ustar00rootroot00000000000000qTox/smileys/emojione/270b-1f3fd.svg000066400000000000000000000035571415623743500174230ustar00rootroot00000000000000qTox/smileys/emojione/270b-1f3fe.svg000066400000000000000000000035571415623743500174240ustar00rootroot00000000000000qTox/smileys/emojione/270b-1f3ff.svg000066400000000000000000000035571415623743500174250ustar00rootroot00000000000000qTox/smileys/emojione/270b.svg000066400000000000000000000035571415623743500165220ustar00rootroot00000000000000qTox/smileys/emojione/270c-1f3fb.svg000066400000000000000000000065371415623743500174230ustar00rootroot00000000000000qTox/smileys/emojione/270c-1f3fc.svg000066400000000000000000000065371415623743500174240ustar00rootroot00000000000000qTox/smileys/emojione/270c-1f3fd.svg000066400000000000000000000065371415623743500174250ustar00rootroot00000000000000qTox/smileys/emojione/270c-1f3fe.svg000066400000000000000000000065371415623743500174260ustar00rootroot00000000000000qTox/smileys/emojione/270c-1f3ff.svg000066400000000000000000000065371415623743500174270ustar00rootroot00000000000000qTox/smileys/emojione/270c.svg000066400000000000000000000065411415623743500165170ustar00rootroot00000000000000qTox/smileys/emojione/270d-1f3fb.svg000066400000000000000000000043021415623743500174100ustar00rootroot00000000000000qTox/smileys/emojione/270d-1f3fc.svg000066400000000000000000000043021415623743500174110ustar00rootroot00000000000000qTox/smileys/emojione/270d-1f3fd.svg000066400000000000000000000043021415623743500174120ustar00rootroot00000000000000qTox/smileys/emojione/270d-1f3fe.svg000066400000000000000000000043021415623743500174130ustar00rootroot00000000000000qTox/smileys/emojione/270d-1f3ff.svg000066400000000000000000000043021415623743500174140ustar00rootroot00000000000000qTox/smileys/emojione/270d.svg000066400000000000000000000042721415623743500165170ustar00rootroot00000000000000qTox/smileys/emojione/270f.svg000066400000000000000000000014711415623743500165170ustar00rootroot00000000000000qTox/smileys/emojione/2712.svg000066400000000000000000000014141415623743500164310ustar00rootroot00000000000000qTox/smileys/emojione/2714.svg000066400000000000000000000002421415623743500164310ustar00rootroot00000000000000qTox/smileys/emojione/2716.svg000066400000000000000000000003511415623743500164340ustar00rootroot00000000000000qTox/smileys/emojione/271d.svg000066400000000000000000000022131415623743500165110ustar00rootroot00000000000000qTox/smileys/emojione/2721.svg000066400000000000000000000010261415623743500164300ustar00rootroot00000000000000qTox/smileys/emojione/2728.svg000066400000000000000000000007271415623743500164460ustar00rootroot00000000000000qTox/smileys/emojione/2733.svg000066400000000000000000000004541415623743500164370ustar00rootroot00000000000000qTox/smileys/emojione/2734.svg000066400000000000000000000004531415623743500164370ustar00rootroot00000000000000qTox/smileys/emojione/2744.svg000066400000000000000000000036101415623743500164360ustar00rootroot00000000000000qTox/smileys/emojione/2747.svg000066400000000000000000000020101415623743500164320ustar00rootroot00000000000000qTox/smileys/emojione/274c.svg000066400000000000000000000003511415623743500165140ustar00rootroot00000000000000qTox/smileys/emojione/274e.svg000066400000000000000000000005731415623743500165240ustar00rootroot00000000000000qTox/smileys/emojione/2753.svg000066400000000000000000000011261415623743500164360ustar00rootroot00000000000000qTox/smileys/emojione/2754.svg000066400000000000000000000011261415623743500164370ustar00rootroot00000000000000qTox/smileys/emojione/2755.svg000066400000000000000000000003201415623743500164330ustar00rootroot00000000000000qTox/smileys/emojione/2757.svg000066400000000000000000000003201415623743500164350ustar00rootroot00000000000000qTox/smileys/emojione/2763.svg000066400000000000000000000006101415623743500164340ustar00rootroot00000000000000qTox/smileys/emojione/2764.svg000066400000000000000000000004301415623743500164350ustar00rootroot00000000000000qTox/smileys/emojione/2795.svg000066400000000000000000000002321415623743500164410ustar00rootroot00000000000000qTox/smileys/emojione/2796.svg000066400000000000000000000002021415623743500164370ustar00rootroot00000000000000qTox/smileys/emojione/2797.svg000066400000000000000000000003161415623743500164460ustar00rootroot00000000000000qTox/smileys/emojione/27a1.svg000066400000000000000000000003231415623743500165060ustar00rootroot00000000000000qTox/smileys/emojione/27b0.svg000066400000000000000000000012521415623743500165100ustar00rootroot00000000000000qTox/smileys/emojione/27bf.svg000066400000000000000000000016661415623743500166070ustar00rootroot00000000000000qTox/smileys/emojione/2934.svg000066400000000000000000000005531415623743500164420ustar00rootroot00000000000000qTox/smileys/emojione/2935.svg000066400000000000000000000005551415623743500164450ustar00rootroot00000000000000qTox/smileys/emojione/2b05.svg000066400000000000000000000003231415623743500165040ustar00rootroot00000000000000qTox/smileys/emojione/2b06.svg000066400000000000000000000003231415623743500165050ustar00rootroot00000000000000qTox/smileys/emojione/2b07.svg000066400000000000000000000003231415623743500165060ustar00rootroot00000000000000qTox/smileys/emojione/2b1b.svg000066400000000000000000000001761415623743500165700ustar00rootroot00000000000000qTox/smileys/emojione/2b1c.svg000066400000000000000000000002011415623743500165560ustar00rootroot00000000000000qTox/smileys/emojione/2b50.svg000066400000000000000000000003621415623743500165070ustar00rootroot00000000000000qTox/smileys/emojione/2b55.svg000066400000000000000000000004251415623743500165140ustar00rootroot00000000000000qTox/smileys/emojione/3030.svg000066400000000000000000000013301415623743500164200ustar00rootroot00000000000000qTox/smileys/emojione/303d.svg000066400000000000000000000012661415623743500165140ustar00rootroot00000000000000qTox/smileys/emojione/3297.svg000066400000000000000000000015411415623743500164430ustar00rootroot00000000000000qTox/smileys/emojione/3299.svg000066400000000000000000000025361415623743500164520ustar00rootroot00000000000000qTox/smileys/emojione/LICENSE.md000066400000000000000000000010241415623743500167160ustar00rootroot00000000000000#### Emoji One Artwork * Applies to all PNG and SVG files as well as any adaptations made. * License: Creative Commons Attribution 4.0 International * Human Readable License: http://creativecommons.org/licenses/by/4.0/ * Complete Legal Terms: http://creativecommons.org/licenses/by/4.0/legalcode #### Emoji One Non-Artwork * Applies to the Javascript, JSON, PHP, CSS, HTML files, and everything else not covered under the artwork license above. * License: MIT * Complete Legal Terms: http://opensource.org/licenses/MIT qTox/smileys/emojione/README.md000066400000000000000000000000521415623743500165710ustar00rootroot00000000000000Emoji art supplied by http://emojione.com qTox/smileys/emojione/emoticons.xml000066400000000000000000001757101415623743500200520ustar00rootroot00000000000000 :-) :) :smile: 😀 :grinning: 😁 😂 :tearsofjoy: 😃 :-D :D 😄 :happy: 😅 😆 😇 O:) O:-) 😈 3:) 3:-) 😉 ;-) ;) 😊 ^_^ 😋 :-P :P :-p :p 😌 😍 (L) (l) 😎 :cool: (H) (h) 8-) 8) 😏 😐 :| :-| :disappointed: 😑 -_- 😒 ಠ_ಠ 😓 😔 😕 :-/ :/ :-\ :\ 😖 :-s :s :oops: 😗 😘 :-* :* 😙 😚 😛 😜 ;P ;-P 😝 😞 :-( :( :sad: 😟 😠 :angry: >:-( >:( 😡 😢 :cry: :'( :,( 😣 😤 😥 😦 😧 😨 😩 😪 😫 😬 😭 😮 😯 😰 😱 😲 :-o :o :-O :O 😳 :$ :blush: 😴 :sleep: :sleeping: |-) #) 😵 😶 😷 😸 :3 =^_^= =(^_^)= 😹 😺 😻 😼 😽 😾 😿 🙀 🙅 🙆 🙇 🙈 🙉 🙊 :silence: :-x :-X :x :X 🙋 :hi: 🙌 🙍 🙎 🙏 💩 🌀 🌁 🌂 🌃 🌄 🌅 🌆 🌇 🌈 🌉 🌊 🌋 🌌 🌍 🌎 🌏 🌐 🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘 🌙 🌚 🌛 🌜 🌝 🌞 🌟 🌠 🌰 🌱 🌲 🌳 🌴 🌵 🌷 🌸 🌹 🌺 🌻 🌼 🌽 🌾 🌿 🍀 🍁 🍂 🍃 🍄 🍅 🍆 🍇 🍈 🍉 🍊 🍋 🍌 🍍 🍎 🍏 🍐 🍑 🍒 🍓 🍔 🍕 🍖 🍗 🍘 🍙 🍚 🍛 🍜 🍝 🍞 🍟 🍠 🍡 🍢 🍣 🍤 🍥 🍦 🍧 🍨 🍩 🍪 :cookie: 🍫 🍬 🍭 🍮 🍯 🍰 🍱 🍲 🍳 🍴 🍵 🍶 🍷 🍸 🍹 🍺 :beer: 🍻 :beers: 🍼 🎀 🎁 🎂 🎃 🎄 🎅 🎆 🎇 🎈 🎉 🎊 🎋 🎌 🎍 🎎 🎏 🎐 🎑 🎒 🎓 🎠 🎡 🎢 🎣 🎤 🎥 🎦 🎧 🎨 🎩 🎪 🎫 🎬 🎭 🎮 🎯 🎰 🎱 🎲 🎳 🎴 🎵 🎶 🎷 🎸 🎹 🎺 🎻 🎼 🎽 🎾 🎿 🏀 🏁 🏂 🏃 🏄 🏆 🏇 🏈 🏉 🏊 🏠 🏡 🏢 🏣 🏤 🏥 🏦 🏧 🏨 🏩 🏪 🏫 🏬 🏭 🏮 🏯 🏰 🐀 🐁 🐂 🐃 🐄 🐅 🐆 🐇 🐈 🐉 🐊 🐋 🐌 🐍 🐎 🐏 🐐 🐑 🐒 🐓 🐔 🐕 🐖 🐗 🐘 🐙 🐚 🐛 🐜 🐝 🐞 🐟 🐠 🐡 🐢 🐣 🐤 🐥 🐦 🐧 🐨 🐩 🐪 🐫 🐬 🐭 🐮 🐯 🐰 🐱 🐲 🐳 🐴 🐵 🐶 🐷 🐸 🐹 🐺 🐻 🐼 🐽 🐾 👀 👂 👃 👄 👅 🖕 :finger: :fuck: 👆 👇 👈 👉 👊 👋 👌 👍 👎 👏 👐 👑 👒 👓 👔 👕 👖 👗 👘 👙 👚 👛 👜 👝 👞 👟 👠 👡 👢 👣 👤 👥 👦 👧 👨 👩 👪 👫 👬 👭 👮 👯 👰 👱 👲 👳 👴 👵 👶 👷 👸 👹 👺 👻 👼 :angel: 0:) O:) 0:-) O:-) 👽 👾 👿 :devil: :666: }:-) }:-> ]:-) ]:-> >:-> 💀 :skull: 💁 💂 💃 💄 💅 💆 💇 💈 💉 :syringe: :injection: 💊 💋 💌 💍 💎 💏 💐 💑 💒 💓 💔 💕 💖 💗 💘 💙 💚 💛 💜 💝 💞 💟 💠 💡 💢 💣 :bomb: 💤 💥 💦 💧 💨 💪 💫 💬 💭 💮 💯 💰 💱 💲 💳 💴 💵 💶 💷 💸 💹 💺 💻 💼 💽 💾 💿 📀 📁 📂 📃 📄 📅 📆 📇 📈 📉 📊 📋 📌 📍 📎 📏 📐 📑 📒 📓 📔 📕 📖 📗 📘 📙 📚 📛 📜 📝 📞 📟 📠 📡 📢 📣 📤 📥 📦 📧 📨 📩 📪 📫 📬 📭 📮 📯 📰 📱 📲 📳 📴 📵 📶 📷 📹 📺 📻 📼 🔀 🔁 🔂 🔃 🔄 🔅 🔆 🔇 🔈 🔉 🔊 🔋 🔌 🔍 🔎 🔏 🔐 🔑 🔒 🔓 🔔 🔕 🔖 🔗 🔘 🔙 🔚 🔛 🔜 🔝 🔞 🔟 🔠 🔡 🔢 🔣 🔤 🔥 🔦 🔧 🔨 🔩 🔪 🔫 🔬 🔭 🔮 🔯 🔰 🔱 🔲 🔳 🔴 🔵 🔶 🔷 🔸 🔹 🔺 🔻 🔼 🔽 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧 🗻 🗼 🗽 🗾 🗿 🀄 🃏 🚀 🚁 🚂 🚃 🚄 🚅 🚆 🚇 🚈 🚉 🚊 🚋 🚌 🚍 🚎 🚏 🚐 🚑 🚒 🚓 🚔 🚕 🚖 🚗 🚘 🚙 🚚 🚛 🚜 🚝 🚞 🚟 🚠 🚡 🚢 🚣 🚤 🚥 🚦 🚧 🚨 🚩 🚪 🚫 🚬 🚭 🚮 🚯 🚰 🚱 🚲 🚳 🚴 🚵 🚶 🚷 🚸 🚹 🚺 🚻 🚼 🚽 🚾 🚿 🛀 🛁 🛂 🛃 🛄 🛅 <3 :heart: :love: © ® 🈚 🈯 🈺 🈁 🈂 🈲 🈳 🈴 🈵 🈶 🈷 🈸 🈹 🉐 🉑 🅰 🅱 🅾 🅿 🆎 🆑 🆒 🆓 🆔 🆕 🆖 🆗 🆘 🆙 🆚 🇦 🇧 🇨 🇩 🇪 🇫 🇬 🇭 🇮 🇯 🇰 🇱 🇲 🇳 🇴 🇵 🇶 🇷 🇸 🇹 🇺 🇻 🇼 🇽 🇾 🇿 🇨🇳 🇩🇪 🇪🇸 🇫🇷 🇬🇧 🇮🇹 🇯🇵 🇰🇷 🇷🇺 🇺🇸 qTox/smileys/smileys.qrc000066400000000000000000001016441415623743500157120ustar00rootroot00000000000000 Classic/angry.png Classic/cool.png Classic/crying.png Classic/emoticons.xml Classic/happy.png Classic/laugh_closed_eyes.png Classic/laugh.png Classic/plain.png Classic/sad.png Classic/scared.png Classic/smile.png Classic/stunned.png Classic/tongue.png Classic/uncertain.png Classic/wink.png Universe/263a.svg Universe/1f600.svg Universe/1f601.svg Universe/1f602.svg Universe/1f603.svg Universe/1f604.svg Universe/1f605.svg Universe/1f606.svg Universe/1f607.svg Universe/1f608.svg Universe/1f609.svg Universe/1f60a.svg Universe/1f60b.svg Universe/1f60c.svg Universe/1f60d.svg Universe/1f60e.svg Universe/1f60f.svg Universe/1f610.svg Universe/1f611.svg Universe/1f612.svg Universe/1f613.svg Universe/1f614.svg Universe/1f615.svg Universe/1f616.svg Universe/1f617.svg Universe/1f618.svg Universe/1f619.svg Universe/1f61a.svg Universe/1f61b.svg Universe/1f61c.svg Universe/1f61d.svg Universe/1f61e.svg Universe/1f61f.svg Universe/1f620.svg Universe/1f621.svg Universe/1f622.svg Universe/1f623.svg Universe/1f624.svg Universe/1f625.svg Universe/1f626.svg Universe/1f627.svg Universe/1f628.svg Universe/1f629.svg Universe/1f62a.svg Universe/1f62b.svg Universe/1f62c.svg Universe/1f62d.svg Universe/1f62e.svg Universe/1f62f.svg Universe/1f630.svg Universe/1f631.svg Universe/1f632.svg Universe/1f633.svg Universe/1f634.svg Universe/1f635.svg Universe/1f636.svg Universe/1f637.svg Universe/1f638.svg Universe/1f639.svg Universe/1f63a.svg Universe/1f63b.svg Universe/1f63c.svg Universe/1f63d.svg Universe/1f63e.svg Universe/1f63f.svg Universe/1f640.svg Universe/1f645.svg Universe/1f646.svg Universe/1f647.svg Universe/1f648.svg Universe/1f649.svg Universe/1f64a.svg Universe/1f64b.svg Universe/1f64c.svg Universe/1f64d.svg Universe/1f64e.svg Universe/1f64f.svg Universe/1f4a9.svg Universe/1f300.svg Universe/1f301.svg Universe/1f302.svg Universe/1f303.svg Universe/1f304.svg Universe/1f305.svg Universe/1f306.svg Universe/1f307.svg Universe/1f308.svg Universe/1f309.svg Universe/1f30a.svg Universe/1f30b.svg Universe/1f30c.svg Universe/1f30d.svg Universe/1f30e.svg Universe/1f30f.svg Universe/1f310.svg Universe/1f311.svg Universe/1f312.svg Universe/1f313.svg Universe/1f314.svg Universe/1f315.svg Universe/1f316.svg Universe/1f317.svg Universe/1f318.svg Universe/1f319.svg Universe/1f31a.svg Universe/1f31b.svg Universe/1f31c.svg Universe/1f31d.svg Universe/1f31e.svg Universe/1f31f.svg Universe/1f320.svg Universe/1f330.svg Universe/1f331.svg Universe/1f332.svg Universe/1f333.svg Universe/1f334.svg Universe/1f335.svg Universe/1f337.svg Universe/1f338.svg Universe/1f339.svg Universe/1f33a.svg Universe/1f33b.svg Universe/1f33c.svg Universe/1f33d.svg Universe/1f33e.svg Universe/1f33f.svg Universe/1f340.svg Universe/1f341.svg Universe/1f342.svg Universe/1f343.svg Universe/1f344.svg Universe/1f345.svg Universe/1f346.svg Universe/1f347.svg Universe/1f348.svg Universe/1f349.svg Universe/1f34a.svg Universe/1f34b.svg Universe/1f34c.svg Universe/1f34d.svg Universe/1f34e.svg Universe/1f34f.svg Universe/1f350.svg Universe/1f351.svg Universe/1f352.svg Universe/1f353.svg Universe/1f354.svg Universe/1f355.svg Universe/1f356.svg Universe/1f357.svg Universe/1f358.svg Universe/1f359.svg Universe/1f35a.svg Universe/1f35b.svg Universe/1f35c.svg Universe/1f35d.svg Universe/1f35e.svg Universe/1f35f.svg Universe/1f360.svg Universe/1f361.svg Universe/1f362.svg Universe/1f363.svg Universe/1f364.svg Universe/1f365.svg Universe/1f366.svg Universe/1f367.svg Universe/1f368.svg Universe/1f369.svg Universe/1f36a.svg Universe/1f36b.svg Universe/1f36c.svg Universe/1f36d.svg Universe/1f36e.svg Universe/1f36f.svg Universe/1f370.svg Universe/1f371.svg Universe/1f372.svg Universe/1f373.svg Universe/1f374.svg Universe/1f375.svg Universe/1f376.svg Universe/1f377.svg Universe/1f378.svg Universe/1f379.svg Universe/1f37a.svg Universe/1f37b.svg Universe/1f37c.svg Universe/1f380.svg Universe/1f381.svg Universe/1f382.svg Universe/1f383.svg Universe/1f384.svg Universe/1f385.svg Universe/1f386.svg Universe/1f387.svg Universe/1f388.svg Universe/1f389.svg Universe/1f38a.svg Universe/1f38b.svg Universe/1f38c.svg Universe/1f38d.svg Universe/1f38e.svg Universe/1f38f.svg Universe/1f390.svg Universe/1f391.svg Universe/1f392.svg Universe/1f393.svg Universe/1f3a0.svg Universe/1f3a1.svg Universe/1f3a2.svg Universe/1f3a3.svg Universe/1f3a4.svg Universe/1f3a5.svg Universe/1f3a6.svg Universe/1f3a7.svg Universe/1f3a8.svg Universe/1f3a9.svg Universe/1f3aa.svg Universe/1f3ab.svg Universe/1f3ac.svg Universe/1f3ad.svg Universe/1f3ae.svg Universe/1f3af.svg Universe/1f3b0.svg Universe/1f3b1.svg Universe/1f3b2.svg Universe/1f3b3.svg Universe/1f3b4.svg Universe/1f3b5.svg Universe/1f3b6.svg Universe/1f3b7.svg Universe/1f3b8.svg Universe/1f3b9.svg Universe/1f3ba.svg Universe/1f3bb.svg Universe/1f3bc.svg Universe/1f3bd.svg Universe/1f3be.svg Universe/1f3bf.svg Universe/1f3c0.svg Universe/1f3c1.svg Universe/1f3c2.svg Universe/1f3c3.svg Universe/1f3c4.svg Universe/1f3c6.svg Universe/1f3c7.svg Universe/1f3c8.svg Universe/1f3c9.svg Universe/1f3ca.svg Universe/1f3e0.svg Universe/1f3e1.svg Universe/1f3e2.svg Universe/1f3e3.svg Universe/1f3e4.svg Universe/1f3e5.svg Universe/1f3e6.svg Universe/1f3e7.svg Universe/1f3e8.svg Universe/1f3e9.svg Universe/1f3ea.svg Universe/1f3eb.svg Universe/1f3ec.svg Universe/1f3ed.svg Universe/1f3ee.svg Universe/1f3ef.svg Universe/1f3f0.svg Universe/1f400.svg Universe/1f401.svg Universe/1f402.svg Universe/1f403.svg Universe/1f404.svg Universe/1f405.svg Universe/1f406.svg Universe/1f407.svg Universe/1f408.svg Universe/1f409.svg Universe/1f40a.svg Universe/1f40b.svg Universe/1f40c.svg Universe/1f40d.svg Universe/1f40e.svg Universe/1f40f.svg Universe/1f410.svg Universe/1f411.svg Universe/1f412.svg Universe/1f413.svg Universe/1f414.svg Universe/1f415.svg Universe/1f416.svg Universe/1f417.svg Universe/1f418.svg Universe/1f419.svg Universe/1f41a.svg Universe/1f41b.svg Universe/1f41c.svg Universe/1f41d.svg Universe/1f41e.svg Universe/1f41f.svg Universe/1f420.svg Universe/1f421.svg Universe/1f422.svg Universe/1f423.svg Universe/1f424.svg Universe/1f425.svg Universe/1f426.svg Universe/1f427.svg Universe/1f428.svg Universe/1f429.svg Universe/1f42a.svg Universe/1f42b.svg Universe/1f42c.svg Universe/1f42d.svg Universe/1f42e.svg Universe/1f42f.svg Universe/1f430.svg Universe/1f431.svg Universe/1f432.svg Universe/1f433.svg Universe/1f434.svg Universe/1f435.svg Universe/1f436.svg Universe/1f437.svg Universe/1f438.svg Universe/1f439.svg Universe/1f43a.svg Universe/1f43b.svg Universe/1f43c.svg Universe/1f43d.svg Universe/1f43e.svg Universe/1f440.svg Universe/1f442.svg Universe/1f443.svg Universe/1f444.svg Universe/1f445.svg Universe/1f446.svg Universe/1f447.svg Universe/1f448.svg Universe/1f449.svg Universe/1f44a.svg Universe/1f44b.svg Universe/1f44c.svg Universe/1f44d.svg Universe/1f44e.svg Universe/1f44f.svg Universe/1f450.svg Universe/1f451.svg Universe/1f452.svg Universe/1f453.svg Universe/1f454.svg Universe/1f455.svg Universe/1f456.svg Universe/1f457.svg Universe/1f458.svg Universe/1f459.svg Universe/1f45a.svg Universe/1f45b.svg Universe/1f45c.svg Universe/1f45d.svg Universe/1f45e.svg Universe/1f45f.svg Universe/1f460.svg Universe/1f461.svg Universe/1f462.svg Universe/1f463.svg Universe/1f464.svg Universe/1f465.svg Universe/1f466.svg Universe/1f467.svg Universe/1f468.svg Universe/1f469.svg Universe/1f46a.svg Universe/1f46b.svg Universe/1f46c.svg Universe/1f46d.svg Universe/1f46e.svg Universe/1f46f.svg Universe/1f470.svg Universe/1f471.svg Universe/1f472.svg Universe/1f473.svg Universe/1f474.svg Universe/1f475.svg Universe/1f476.svg Universe/1f477.svg Universe/1f478.svg Universe/1f479.svg Universe/1f47a.svg Universe/1f47b.svg Universe/1f47c.svg Universe/1f47d.svg Universe/1f47e.svg Universe/1f47f.svg Universe/1f480.svg Universe/1f481.svg Universe/1f482.svg Universe/1f483.svg Universe/1f484.svg Universe/1f485.svg Universe/1f486.svg Universe/1f487.svg Universe/1f488.svg Universe/1f489.svg Universe/1f48a.svg Universe/1f48b.svg Universe/1f48c.svg Universe/1f48d.svg Universe/1f48e.svg Universe/1f48f.svg Universe/1f490.svg Universe/1f491.svg Universe/1f492.svg Universe/1f493.svg Universe/1f494.svg Universe/1f495.svg Universe/1f496.svg Universe/1f497.svg Universe/1f498.svg Universe/1f499.svg Universe/1f49a.svg Universe/1f49b.svg Universe/1f49c.svg Universe/1f49d.svg Universe/1f49e.svg Universe/1f49f.svg Universe/1f4a0.svg Universe/1f4a1.svg Universe/1f4a2.svg Universe/1f4a3.svg Universe/1f4a4.svg Universe/1f4a5.svg Universe/1f4a6.svg Universe/1f4a7.svg Universe/1f4a8.svg Universe/1f4aa.svg Universe/1f4ab.svg Universe/1f4ac.svg Universe/1f4ad.svg Universe/1f4ae.svg Universe/1f4af.svg Universe/1f4b0.svg Universe/1f4b1.svg Universe/1f4b2.svg Universe/1f4b3.svg Universe/1f4b4.svg Universe/1f4b5.svg Universe/1f4b6.svg Universe/1f4b7.svg Universe/1f4b8.svg Universe/1f4b9.svg Universe/1f4ba.svg Universe/1f4bb.svg Universe/1f4bc.svg Universe/1f4bd.svg Universe/1f4be.svg Universe/1f4bf.svg Universe/1f4c0.svg Universe/1f4c1.svg Universe/1f4c2.svg Universe/1f4c3.svg Universe/1f4c4.svg Universe/1f4c5.svg Universe/1f4c6.svg Universe/1f4c7.svg Universe/1f4c8.svg Universe/1f4c9.svg Universe/1f4ca.svg Universe/1f4cb.svg Universe/1f4cc.svg Universe/1f4cd.svg Universe/1f4ce.svg Universe/1f4cf.svg Universe/1f4d0.svg Universe/1f4d1.svg Universe/1f4d2.svg Universe/1f4d3.svg Universe/1f4d4.svg Universe/1f4d5.svg Universe/1f4d6.svg Universe/1f4d7.svg Universe/1f4d8.svg Universe/1f4d9.svg Universe/1f4da.svg Universe/1f4db.svg Universe/1f4dc.svg Universe/1f4dd.svg Universe/1f4de.svg Universe/1f4df.svg Universe/1f4e0.svg Universe/1f4e1.svg Universe/1f4e2.svg Universe/1f4e3.svg Universe/1f4e4.svg Universe/1f4e5.svg Universe/1f4e6.svg Universe/1f4e7.svg Universe/1f4e8.svg Universe/1f4e9.svg Universe/1f4ea.svg Universe/1f4eb.svg Universe/1f4ec.svg Universe/1f4ed.svg Universe/1f4ee.svg Universe/1f4ef.svg Universe/1f4f0.svg Universe/1f4f1.svg Universe/1f4f2.svg Universe/1f4f3.svg Universe/1f4f4.svg Universe/1f4f5.svg Universe/1f4f6.svg Universe/1f4f7.svg Universe/1f4f9.svg Universe/1f4fa.svg Universe/1f4fb.svg Universe/1f4fc.svg Universe/1f500.svg Universe/1f501.svg Universe/1f502.svg Universe/1f503.svg Universe/1f504.svg Universe/1f505.svg Universe/1f506.svg Universe/1f507.svg Universe/1f508.svg Universe/1f509.svg Universe/1f50a.svg Universe/1f50b.svg Universe/1f50c.svg Universe/1f50d.svg Universe/1f50e.svg Universe/1f50f.svg Universe/1f510.svg Universe/1f511.svg Universe/1f512.svg Universe/1f513.svg Universe/1f514.svg Universe/1f515.svg Universe/1f516.svg Universe/1f517.svg Universe/1f518.svg Universe/1f519.svg Universe/1f51a.svg Universe/1f51b.svg Universe/1f51c.svg Universe/1f51d.svg Universe/1f51e.svg Universe/1f51f.svg Universe/1f520.svg Universe/1f521.svg Universe/1f522.svg Universe/1f523.svg Universe/1f524.svg Universe/1f525.svg Universe/1f526.svg Universe/1f527.svg Universe/1f528.svg Universe/1f529.svg Universe/1f52a.svg Universe/1f52b.svg Universe/1f52c.svg Universe/1f52d.svg Universe/1f52e.svg Universe/1f52f.svg Universe/1f530.svg Universe/1f531.svg Universe/1f532.svg Universe/1f533.svg Universe/1f534.svg Universe/1f535.svg Universe/1f536.svg Universe/1f537.svg Universe/1f538.svg Universe/1f539.svg Universe/1f53a.svg Universe/1f53b.svg Universe/1f53c.svg Universe/1f53d.svg Universe/1f550.svg Universe/1f551.svg Universe/1f552.svg Universe/1f553.svg Universe/1f554.svg Universe/1f555.svg Universe/1f556.svg Universe/1f557.svg Universe/1f558.svg Universe/1f559.svg Universe/1f55a.svg Universe/1f55b.svg Universe/1f55c.svg Universe/1f55d.svg Universe/1f55e.svg Universe/1f55f.svg Universe/1f560.svg Universe/1f561.svg Universe/1f562.svg Universe/1f563.svg Universe/1f564.svg Universe/1f565.svg Universe/1f566.svg Universe/1f567.svg Universe/1f595.svg Universe/1f5fb.svg Universe/1f5fc.svg Universe/1f5fd.svg Universe/1f5fe.svg Universe/1f5ff.svg Universe/1f004.svg Universe/1f0cf.svg Universe/2600.svg Universe/2601.svg Universe/260e.svg Universe/2611.svg Universe/2614.svg Universe/2615.svg Universe/261d.svg Universe/2648.svg Universe/2649.svg Universe/264a.svg Universe/264b.svg Universe/264c.svg Universe/264d.svg Universe/264e.svg Universe/264f.svg Universe/2650.svg Universe/2651.svg Universe/2652.svg Universe/2653.svg Universe/2660.svg Universe/2663.svg Universe/2665.svg Universe/2666.svg Universe/2668.svg Universe/267b.svg Universe/267f.svg Universe/2693.svg Universe/26a0.svg Universe/26a1.svg Universe/26aa.svg Universe/26ab.svg Universe/26bd.svg Universe/26be.svg Universe/26c4.svg Universe/26c5.svg Universe/26ce.svg Universe/26d4.svg Universe/26ea.svg Universe/26f2.svg Universe/26f3.svg Universe/26f5.svg Universe/26fa.svg Universe/26fd.svg Universe/1f680.svg Universe/1f681.svg Universe/1f682.svg Universe/1f683.svg Universe/1f684.svg Universe/1f685.svg Universe/1f686.svg Universe/1f687.svg Universe/1f688.svg Universe/1f689.svg Universe/1f68a.svg Universe/1f68b.svg Universe/1f68c.svg Universe/1f68d.svg Universe/1f68e.svg Universe/1f68f.svg Universe/1f690.svg Universe/1f691.svg Universe/1f692.svg Universe/1f693.svg Universe/1f694.svg Universe/1f695.svg Universe/1f696.svg Universe/1f697.svg Universe/1f698.svg Universe/1f699.svg Universe/1f69a.svg Universe/1f69b.svg Universe/1f69c.svg Universe/1f69d.svg Universe/1f69e.svg Universe/1f69f.svg Universe/1f6a0.svg Universe/1f6a1.svg Universe/1f6a2.svg Universe/1f6a3.svg Universe/1f6a4.svg Universe/1f6a5.svg Universe/1f6a6.svg Universe/1f6a7.svg Universe/1f6a8.svg Universe/1f6a9.svg Universe/1f6aa.svg Universe/1f6ab.svg Universe/1f6ac.svg Universe/1f6ad.svg Universe/1f6ae.svg Universe/1f6af.svg Universe/1f6b0.svg Universe/1f6b1.svg Universe/1f6b2.svg Universe/1f6b3.svg Universe/1f6b4.svg Universe/1f6b5.svg Universe/1f6b6.svg Universe/1f6b7.svg Universe/1f6b8.svg Universe/1f6b9.svg Universe/1f6ba.svg Universe/1f6bb.svg Universe/1f6bc.svg Universe/1f6bd.svg Universe/1f6be.svg Universe/1f6bf.svg Universe/1f6c0.svg Universe/1f6c1.svg Universe/1f6c2.svg Universe/1f6c3.svg Universe/1f6c4.svg Universe/1f6c5.svg Universe/2702.svg Universe/2705.svg Universe/2708.svg Universe/2709.svg Universe/270a.svg Universe/270b.svg Universe/270c.svg Universe/270f.svg Universe/2712.svg Universe/2714.svg Universe/2716.svg Universe/2728.svg Universe/2733.svg Universe/2734.svg Universe/2744.svg Universe/2747.svg Universe/274c.svg Universe/274e.svg Universe/2753.svg Universe/2754.svg Universe/2755.svg Universe/2757.svg Universe/2764.svg Universe/2795.svg Universe/2796.svg Universe/2797.svg Universe/27a1.svg Universe/27b0.svg Universe/27bf.svg Universe/2934.svg Universe/2935.svg Universe/3030.svg Universe/3297.svg Universe/3299.svg Universe/a9.svg Universe/ae.svg Universe/1f21a.svg Universe/1f22f.svg Universe/1f23a.svg Universe/1f201.svg Universe/1f202.svg Universe/1f232.svg Universe/1f233.svg Universe/1f234.svg Universe/1f235.svg Universe/1f236.svg Universe/1f237.svg Universe/1f238.svg Universe/1f239.svg Universe/1f250.svg Universe/1f251.svg Universe/1f170.svg Universe/1f171.svg Universe/1f17e.svg Universe/1f17f.svg Universe/1f18e.svg Universe/1f191.svg Universe/1f192.svg Universe/1f193.svg Universe/1f194.svg Universe/1f195.svg Universe/1f196.svg Universe/1f197.svg Universe/1f198.svg Universe/1f199.svg Universe/1f19a.svg Universe/1f1e6.svg Universe/1f1e7.svg Universe/1f1e8.svg Universe/1f1e9.svg Universe/1f1ea.svg Universe/1f1eb.svg Universe/1f1ec.svg Universe/1f1ed.svg Universe/1f1ee.svg Universe/1f1ef.svg Universe/1f1f0.svg Universe/1f1f1.svg Universe/1f1f2.svg Universe/1f1f3.svg Universe/1f1f4.svg Universe/1f1f5.svg Universe/1f1f6.svg Universe/1f1f7.svg Universe/1f1f8.svg Universe/1f1f9.svg Universe/1f1fa.svg Universe/1f1fb.svg Universe/1f1fc.svg Universe/1f1fd.svg Universe/1f1fe.svg Universe/1f1ff.svg Universe/1f1e8-1f1f3.svg Universe/1f1e9-1f1ea.svg Universe/1f1ea-1f1f8.svg Universe/1f1eb-1f1f7.svg Universe/1f1ec-1f1e7.svg Universe/1f1ee-1f1f9.svg Universe/1f1ef-1f1f5.svg Universe/1f1f0-1f1f7.svg Universe/1f1f7-1f1fa.svg Universe/1f1fa-1f1f8.svg Universe/emoticons.xml ASCII+Universe/emoticons.xml qTox/src/000077500000000000000000000000001415623743500126125ustar00rootroot00000000000000qTox/src/audio/000077500000000000000000000000001415623743500137135ustar00rootroot00000000000000qTox/src/audio/audio.cpp000066400000000000000000000022161415623743500155210ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/audio/audio.h" #include "src/audio/iaudiosettings.h" #include "src/audio/backend/openal.h" /** * @brief Select the audio backend * @param settings Audio settings to use * @return Audio backend selection based on settings */ std::unique_ptr Audio::makeAudio(IAudioSettings& settings) { return std::unique_ptr(new OpenAL()); } qTox/src/audio/audio.h000066400000000000000000000017341415623743500151720ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef AUDIO_H #define AUDIO_H #include class IAudioControl; class IAudioSettings; class Audio { public: static std::unique_ptr makeAudio(IAudioSettings& settings); }; #endif // AUDIO_H qTox/src/audio/backend/000077500000000000000000000000001415623743500153025ustar00rootroot00000000000000qTox/src/audio/backend/alsink.cpp000066400000000000000000000050571415623743500172760ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/audio/backend/alsink.h" #include "src/audio/backend/openal.h" #include #include /** * @brief Can play audio via the speakers or some other audio device. Allocates * and frees the audio ressources internally. */ AlSink::~AlSink() { QMutexLocker{&killLock}; // unsubscribe only if not already killed if (!killed) { audio.destroySink(*this); killed = true; } } void AlSink::playAudioBuffer(const int16_t* data, int samples, unsigned channels, int sampleRate) const { QMutexLocker{&killLock}; if (killed) { qCritical() << "Trying to play audio on an invalid sink"; } else { audio.playAudioBuffer(sourceId, data, samples, channels, sampleRate); } } void AlSink::playMono16Sound(const IAudioSink::Sound& sound) { QMutexLocker{&killLock}; if (killed) { qCritical() << "Trying to play sound on an invalid sink"; } else { audio.playMono16Sound(*this, sound); } } void AlSink::startLoop() { QMutexLocker{&killLock}; if (killed) { qCritical() << "Trying to start loop on an invalid sink"; } else { audio.startLoop(sourceId); } } void AlSink::stopLoop() { QMutexLocker{&killLock}; if (killed) { qCritical() << "Trying to stop loop on an invalid sink"; } else { audio.stopLoop(sourceId); } } uint AlSink::getSourceId() const { uint tmp = sourceId; return tmp; } void AlSink::kill() { killLock.lock(); // this flag is only set once here, afterwards the object is considered dead killed = true; killLock.unlock(); emit invalidated(); } AlSink::AlSink(OpenAL& al, uint sourceId) : audio(al) , sourceId{sourceId} {} AlSink::operator bool() const { QMutexLocker{&killLock}; return !killed; } qTox/src/audio/backend/alsink.h000066400000000000000000000034401415623743500167350ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ALSINK_H #define ALSINK_H #include #include #include "src/model/interface.h" #include "src/audio/iaudiosink.h" #include "src/util/compatiblerecursivemutex.h" class OpenAL; class QMutex; class AlSink : public QObject, public IAudioSink { Q_OBJECT public: AlSink(OpenAL& al, uint sourceId); AlSink(const AlSink& src) = delete; AlSink& operator=(const AlSink&) = delete; AlSink(AlSink&& other) = delete; AlSink& operator=(AlSink&& other) = delete; ~AlSink(); void playAudioBuffer(const int16_t* data, int samples, unsigned channels, int sampleRate) const override; void playMono16Sound(const IAudioSink::Sound& sound) override; void startLoop() override; void stopLoop() override; operator bool() const override; uint getSourceId() const; void kill(); SIGNAL_IMPL(AlSink, finishedPlaying) SIGNAL_IMPL(AlSink, invalidated) private: OpenAL& audio; uint sourceId; bool killed = false; mutable CompatibleRecursiveMutex killLock; }; #endif // ALSINK_H qTox/src/audio/backend/alsource.cpp000066400000000000000000000030631415623743500176250ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/audio/backend/alsource.h" #include "src/audio/backend/openal.h" /** * @brief Emits audio frames captured by an input device or other audio source. */ /** * @brief Reserves ressources for an audio source * @param audio Main audio object, must have longer lifetime than this object. */ AlSource::AlSource(OpenAL& al) : audio(al) {} AlSource::~AlSource() { QMutexLocker{&killLock}; // unsubscribe only if not already killed if (!killed) { audio.destroySource(*this); killed = true; } } AlSource::operator bool() const { QMutexLocker{&killLock}; return !killed; } void AlSource::kill() { killLock.lock(); // this flag is only set once here, afterwards the object is considered dead killed = true; killLock.unlock(); emit invalidated(); } qTox/src/audio/backend/alsource.h000066400000000000000000000025631415623743500172760ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ALSOURCE_H #define ALSOURCE_H #include "src/audio/iaudiosource.h" #include "src/util/compatiblerecursivemutex.h" #include #include class OpenAL; class AlSource : public IAudioSource { Q_OBJECT public: AlSource(OpenAL& al); AlSource(AlSource& src) = delete; AlSource& operator=(const AlSource&) = delete; AlSource(AlSource&& other) = delete; AlSource& operator=(AlSource&& other) = delete; ~AlSource(); operator bool() const; void kill(); private: OpenAL& audio; bool killed = false; mutable CompatibleRecursiveMutex killLock; }; #endif // ALSOURCE_H qTox/src/audio/backend/openal.cpp000066400000000000000000000521271415623743500172730ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "openal.h" #include "src/persistence/settings.h" #include #include #include #include #include #include #include #include namespace { void applyGain(int16_t* buffer, uint32_t bufferSize, qreal gainFactor) { for (quint32 i = 0; i < bufferSize; ++i) { // gain amplification with clipping to 16-bit boundaries buffer[i] = qBound(std::numeric_limits::min(), qRound(buffer[i] * gainFactor), std::numeric_limits::max()); } } } // namespace /** * @class OpenAL * @brief Provides the OpenAL audio backend * * @var BUFFER_COUNT * @brief Number of buffers to use per audio source * * @var AUDIO_CHANNELS * @brief Ideally, we'd auto-detect, but that's a sane default */ static const unsigned int BUFFER_COUNT = 16; static const uint32_t AUDIO_CHANNELS = 2; OpenAL::OpenAL() : audioThread{new QThread} { // initialize OpenAL error stack alGetError(); alcGetError(nullptr); audioThread->setObjectName("qTox Audio"); QObject::connect(audioThread, &QThread::finished, &voiceTimer, &QTimer::stop); QObject::connect(audioThread, &QThread::finished, &captureTimer, &QTimer::stop); QObject::connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); moveToThread(audioThread); voiceTimer.setSingleShot(true); voiceTimer.moveToThread(audioThread); connect(this, &OpenAL::startActive, &voiceTimer, static_cast(&QTimer::start)); connect(&voiceTimer, &QTimer::timeout, this, &OpenAL::stopActive); connect(&captureTimer, &QTimer::timeout, this, &OpenAL::doAudio); captureTimer.setInterval(AUDIO_FRAME_DURATION / 2); captureTimer.setSingleShot(false); captureTimer.moveToThread(audioThread); // TODO for Qt 5.6+: use qOverload connect(audioThread, &QThread::started, &captureTimer, static_cast(&QTimer::start)); cleanupTimer.setInterval(1000); cleanupTimer.setSingleShot(false); connect(&cleanupTimer, &QTimer::timeout, this, &OpenAL::cleanupSound); // TODO for Qt 5.6+: use qOverload connect(audioThread, &QThread::started, &cleanupTimer, static_cast(&QTimer::start)); audioThread->start(); } OpenAL::~OpenAL() { audioThread->exit(); audioThread->wait(); cleanupInput(); cleanupOutput(); } void OpenAL::checkAlError() noexcept { const ALenum al_err = alGetError(); if (al_err != AL_NO_ERROR) qWarning("OpenAL error: %d", al_err); } void OpenAL::checkAlcError(ALCdevice* device) noexcept { const ALCenum alc_err = alcGetError(device); if (alc_err) qWarning("OpenAL error: %d", alc_err); } /** * @brief Returns the current output volume (between 0 and 1) */ qreal OpenAL::outputVolume() const { QMutexLocker locker(&audioLock); ALfloat volume = 0.0; if (alOutDev) { alGetListenerf(AL_GAIN, &volume); checkAlError(); } return volume; } /** * @brief Set the master output volume. * * @param[in] volume the master volume (between 0 and 1) */ void OpenAL::setOutputVolume(qreal volume) { QMutexLocker locker(&audioLock); volume = std::max(0.0, std::min(volume, 1.0)); alListenerf(AL_GAIN, static_cast(volume)); checkAlError(); } /** * @brief The minimum gain value for an input device. * * @return minimum gain value in dB */ qreal OpenAL::minInputGain() const { QMutexLocker locker(&audioLock); return minInGain; } /** * @brief Set the minimum allowed gain value in dB. * * @note Default is -30dB; usually you don't need to alter this value; */ void OpenAL::setMinInputGain(qreal dB) { QMutexLocker locker(&audioLock); minInGain = dB; } /** * @brief The maximum gain value for an input device. * * @return maximum gain value in dB */ qreal OpenAL::maxInputGain() const { QMutexLocker locker(&audioLock); return maxInGain; } /** * @brief Set the maximum allowed gain value in dB. * * @note Default is 30dB; usually you don't need to alter this value. */ void OpenAL::setMaxInputGain(qreal dB) { QMutexLocker locker(&audioLock); maxInGain = dB; } /** * @brief The minimum threshold value for an input device. * * @return minimum normalized threshold */ qreal OpenAL::minInputThreshold() const { QMutexLocker locker(&audioLock); return minInThreshold; } /** * @brief The maximum normalized threshold value for an input device. * * @return maximum normalized threshold */ qreal OpenAL::maxInputThreshold() const { QMutexLocker locker(&audioLock); return maxInThreshold; } void OpenAL::reinitInput(const QString& inDevDesc) { QMutexLocker locker(&audioLock); const auto bakSources = sources; sources.clear(); cleanupInput(); initInput(inDevDesc); locker.unlock(); // this must happen outside `audioLock`, to avoid a deadlock when // a slot on AlSource::invalidate tries to create a new source immedeately. for (auto& source : bakSources) { source->kill(); } } bool OpenAL::reinitOutput(const QString& outDevDesc) { QMutexLocker locker(&audioLock); const auto bakSinks = sinks; sinks.clear(); cleanupOutput(); const bool result = initOutput(outDevDesc); locker.unlock(); // this must happen outside `audioLock`, to avoid a deadlock when // a slot on AlSink::invalidate tries to create a new source immedeately. for (auto& sink : bakSinks) { sink->kill(); } return result; } /** * @brief Allocates ressources for a new audio output * @return AudioSink on success, nullptr on failure */ std::unique_ptr OpenAL::makeSink() { QMutexLocker locker(&audioLock); if (!autoInitOutput()) { qWarning("Failed to subscribe to audio output device."); return {}; } ALuint sid; alGenSources(1, &sid); auto const sink = new AlSink(*this, sid); if (sink == nullptr) { return {}; } sinks.insert(sink); qDebug() << "Audio source" << sid << "created. Sources active:" << sinks.size(); return std::unique_ptr{sink}; } /** * @brief Must be called by the destructor of AlSink to remove the internal ressources. * If no sinks are opened, the output is closed afterwards. * @param sink Audio sink to remove. */ void OpenAL::destroySink(AlSink& sink) { QMutexLocker locker(&audioLock); const auto sinksErased = sinks.erase(&sink); const auto soundSinksErased = soundSinks.erase(&sink); if (sinksErased == 0 && soundSinksErased == 0) { qWarning() << "Destroying non-existant source"; return; } const uint sid = sink.getSourceId(); if (alIsSource(sid)) { // stop playing, marks all buffers as processed alSourceStop(sid); cleanupBuffers(sid); qDebug() << "Audio source" << sid << "deleted. Sources active:" << sinks.size(); } else { qWarning() << "Trying to delete invalid audio source" << sid; } if (sinks.empty() && soundSinks.empty()) { cleanupOutput(); } } /** * @brief Subscribe to capture sound from the opened input device. * * If the input device is not open, it will be opened before capturing. */ std::unique_ptr OpenAL::makeSource() { QMutexLocker locker(&audioLock); if (!autoInitInput()) { qWarning("Failed to subscribe to audio input device."); return {}; } auto const source = new AlSource(*this); if (source == nullptr) { return {}; } sources.insert(source); qDebug() << "Subscribed to audio input device [" << sources.size() << "subscriptions ]"; return std::unique_ptr{source}; } /** * @brief Unsubscribe from capturing from an opened input device. * * If the input device has no more subscriptions, it will be closed. */ void OpenAL::destroySource(AlSource& source) { QMutexLocker locker(&audioLock); const auto s = sources.find(&source); if (s == sources.end()) { qWarning() << "Destroyed non-existant source"; return; } sources.erase(s); qDebug() << "Unsubscribed from audio input device [" << sources.size() << "subscriptions left ]"; if (sources.empty()) { cleanupInput(); } } /** * @brief Initialize audio input device, if not initialized. * * @return true, if device was initialized; false otherwise */ bool OpenAL::autoInitInput() { return alInDev ? true : initInput(Settings::getInstance().getInDev()); } /** * @brief Initialize audio output device, if not initialized. * * @return true, if device was initialized; false otherwise */ bool OpenAL::autoInitOutput() { return alOutDev ? true : initOutput(Settings::getInstance().getOutDev()); } bool OpenAL::initInput(const QString& deviceName) { return initInput(deviceName, AUDIO_CHANNELS); } bool OpenAL::initInput(const QString& deviceName, uint32_t channels) { if (!Settings::getInstance().getAudioInDevEnabled()) { return false; } qDebug() << "Opening audio input" << deviceName; assert(!alInDev); // TODO: Try to actually detect if our audio source is stereo this->channels = channels; int stereoFlag = channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; const int bytesPerSample = 2; const int safetyFactor = 2; // internal OpenAL ring buffer. must be larger than our inputBuffer // to avoid the ring from overwriting itself between captures. AUDIO_FRAME_SAMPLE_COUNT_TOTAL = AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL * channels; const ALCsizei ringBufSize = AUDIO_FRAME_SAMPLE_COUNT_TOTAL * bytesPerSample * safetyFactor; const QByteArray qDevName = deviceName.toUtf8(); const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData(); alInDev = alcCaptureOpenDevice(tmpDevName, AUDIO_SAMPLE_RATE, stereoFlag, ringBufSize); // Restart the capture if necessary if (!alInDev) { qWarning() << "Failed to initialize audio input device:" << deviceName; return false; } inputBuffer = new int16_t[AUDIO_FRAME_SAMPLE_COUNT_TOTAL]; setInputGain(Settings::getInstance().getAudioInGainDecibel()); setInputThreshold(Settings::getInstance().getAudioThreshold()); qDebug() << "Opened audio input" << deviceName; alcCaptureStart(alInDev); return true; } /** * @brief Open an audio output device */ bool OpenAL::initOutput(const QString& deviceName) { // there should be no sinks when initializing the output assert(sinks.size() == 0); outputInitialized = false; if (!Settings::getInstance().getAudioOutDevEnabled()) return false; qDebug() << "Opening audio output" << deviceName; assert(!alOutDev); const QByteArray qDevName = deviceName.toUtf8(); const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData(); alOutDev = alcOpenDevice(tmpDevName); if (!alOutDev) { qWarning() << "Cannot open output audio device" << deviceName; return false; } qDebug() << "Opened audio output" << deviceName; alOutContext = alcCreateContext(alOutDev, nullptr); checkAlcError(alOutDev); if (!alcMakeContextCurrent(alOutContext)) { qWarning() << "Cannot create output audio context"; return false; } // init master volume alListenerf(AL_GAIN, Settings::getInstance().getOutVolume() * 0.01f); checkAlError(); outputInitialized = true; return true; } /** * @brief Play a 48kHz mono 16bit PCM sound */ void OpenAL::playMono16Sound(AlSink& sink, const IAudioSink::Sound& sound) { const uint sourceId = sink.getSourceId(); QFile sndFile(IAudioSink::getSound(sound)); if (!sndFile.exists()) { qDebug() << "Trying to open non existent sound file"; return; } sndFile.open(QIODevice::ReadOnly); const QByteArray data{sndFile.readAll()}; if (data.isEmpty()) { qDebug() << "Sound file contained no data"; return; } QMutexLocker locker(&audioLock); // interrupt possibly playing sound, we don't buffer here alSourceStop(sourceId); ALint processed = 0; alGetSourcei(sourceId, AL_BUFFERS_PROCESSED, &processed); alSourcei(sourceId, AL_LOOPING, AL_FALSE); ALuint bufid; if (processed == 0) { // create new buffer alGenBuffers(1, &bufid); } else { // we only reserve space for one buffer assert(processed == 1); // unqueue all processed buffers alSourceUnqueueBuffers(sourceId, processed, &bufid); } alBufferData(bufid, AL_FORMAT_MONO16, data.constData(), data.size(), AUDIO_SAMPLE_RATE); alSourcei(sourceId, AL_BUFFER, bufid); alSourcePlay(sourceId); soundSinks.insert(&sink); } void OpenAL::cleanupSound() { QMutexLocker locker(&audioLock); auto sinkIt = soundSinks.begin(); while (sinkIt != soundSinks.end()) { auto sink = *sinkIt; ALuint sourceId = sink->getSourceId(); ALint state = 0; alGetSourcei(sourceId, AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { sinkIt = soundSinks.erase(sinkIt); emit sink->finishedPlaying(); } else { ++sinkIt; } } } void OpenAL::playAudioBuffer(uint sourceId, const int16_t* data, int samples, unsigned channels, int sampleRate) { assert(channels == 1 || channels == 2); QMutexLocker locker(&audioLock); if (!(alOutDev && outputInitialized)) return; ALuint bufids[BUFFER_COUNT]; ALint processed = 0, queued = 0; alGetSourcei(sourceId, AL_BUFFERS_PROCESSED, &processed); alGetSourcei(sourceId, AL_BUFFERS_QUEUED, &queued); alSourcei(sourceId, AL_LOOPING, AL_FALSE); if (processed == 0) { if (static_cast(queued) >= BUFFER_COUNT) { // reached limit, drop audio return; } // create new buffer if none got free and we're below the limit alGenBuffers(1, bufids); } else { // unqueue all processed buffers alSourceUnqueueBuffers(sourceId, processed, bufids); // delete all but the first buffer, reuse first for new data alDeleteBuffers(processed - 1, bufids + 1); } alBufferData(bufids[0], (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, data, samples * 2 * channels, sampleRate); alSourceQueueBuffers(sourceId, 1, bufids); ALint state; alGetSourcei(sourceId, AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { alSourcePlay(sourceId); } } /** * @brief Close active audio input device. */ void OpenAL::cleanupInput() { if (!alInDev) return; qDebug() << "Closing audio input"; alcCaptureStop(alInDev); if (alcCaptureCloseDevice(alInDev) == ALC_TRUE) { alInDev = nullptr; } else { qWarning() << "Failed to close input"; } delete[] inputBuffer; } /** * @brief Close active audio output device */ void OpenAL::cleanupOutput() { outputInitialized = false; if (alOutDev) { if (!alcMakeContextCurrent(nullptr)) { qWarning("Failed to clear audio context."); } alcDestroyContext(alOutContext); alOutContext = nullptr; qDebug() << "Closing audio output"; if (alcCloseDevice(alOutDev)) { alOutDev = nullptr; } else { qWarning("Failed to close output."); } } } /** * @brief Called by doCapture to calculate volume of the audio buffer * * @param[in] buf the current audio buffer * * @return normalized volume between 0-1 */ float OpenAL::getVolume() { const quint32 samples = AUDIO_FRAME_SAMPLE_COUNT_TOTAL; const float rootTwo = 1.414213562; // sqrt(2), but sqrt is not constexpr // calculate volume as the root mean squared of amplitudes in the sample float sumOfSquares = 0; for (quint32 i = 0; i < samples; i++) { float sample = static_cast(inputBuffer[i]) / std::numeric_limits::max(); sumOfSquares += std::pow(sample, 2); } const float rms = std::sqrt(sumOfSquares / samples); // our calculated normalized volume could possibly be above 1 because our RMS assumes a sinusoidal wave const float normalizedVolume = std::min(rms * rootTwo, 1.0f); return normalizedVolume; } /** * @brief Called by voiceTimer's timeout to disable audio broadcasting */ void OpenAL::stopActive() { isActive = false; } /** * @brief handles recording of audio frames */ void OpenAL::doInput() { ALint curSamples = 0; alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(curSamples), &curSamples); if (curSamples < static_cast(AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL)) { return; } captureSamples(alInDev, inputBuffer, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL); applyGain(inputBuffer, AUDIO_FRAME_SAMPLE_COUNT_TOTAL, gainFactor); float volume = getVolume(); if (volume >= inputThreshold) { isActive = true; emit startActive(voiceHold); } else if (!isActive) { volume = 0; } // NOTE(sudden6): this loop probably doesn't scale too well with many sources for (auto source : sources) { emit source->volumeAvailable(volume); } if (!isActive) { return; } // NOTE(sudden6): this loop probably doesn't scale too well with many sources for (auto source : sources) { emit source->frameAvailable(inputBuffer, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL, channels, AUDIO_SAMPLE_RATE); } } void OpenAL::doOutput() { // Nothing } /** * @brief Called on the captureTimer events to capture audio */ void OpenAL::doAudio() { QMutexLocker lock(&audioLock); // Output section does nothing // Input section if (alInDev && !sources.empty()) { doInput(); } } void OpenAL::captureSamples(ALCdevice* device, int16_t* buffer, ALCsizei samples) { alcCaptureSamples(device, buffer, samples); } /** * @brief Returns true if the output device is open */ bool OpenAL::isOutputReady() const { QMutexLocker locker(&audioLock); return alOutDev && outputInitialized; } QStringList OpenAL::outDeviceNames() { QStringList list; const ALchar* pDeviceList = (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE) ? alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER) : alcGetString(nullptr, ALC_DEVICE_SPECIFIER); if (pDeviceList) { while (*pDeviceList) { int len = static_cast(strlen(pDeviceList)); list << QString::fromUtf8(pDeviceList, len); pDeviceList += len + 1; } } return list; } QStringList OpenAL::inDeviceNames() { QStringList list; const ALchar* pDeviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); if (pDeviceList) { while (*pDeviceList) { int len = static_cast(strlen(pDeviceList)); list << QString::fromUtf8(pDeviceList, len); pDeviceList += len + 1; } } return list; } /** * @brief Free all buffers that finished playing on a source * @param sourceId where to remove the buffers from */ void OpenAL::cleanupBuffers(uint sourceId) { // unqueue all buffers from the source ALint processed = 0; alGetSourcei(sourceId, AL_BUFFERS_PROCESSED, &processed); std::vector bufids; // should never be out of range, just to be sure assert(processed >= 0); assert(processed <= SIZE_MAX); bufids.resize(processed); alSourceUnqueueBuffers(sourceId, processed, bufids.data()); // delete all buffers alDeleteBuffers(processed, bufids.data()); } void OpenAL::startLoop(uint sourceId) { QMutexLocker locker(&audioLock); alSourcei(sourceId, AL_LOOPING, AL_TRUE); } void OpenAL::stopLoop(uint sourceId) { QMutexLocker locker(&audioLock); alSourcei(sourceId, AL_LOOPING, AL_FALSE); alSourceStop(sourceId); cleanupBuffers(sourceId); } qreal OpenAL::inputGain() const { return gain; } qreal OpenAL::getInputThreshold() const { return inputThreshold; } qreal OpenAL::inputGainFactor() const { return gainFactor; } void OpenAL::setInputGain(qreal dB) { gain = qBound(minInGain, dB, maxInGain); gainFactor = qPow(10.0, (gain / 20.0)); } void OpenAL::setInputThreshold(qreal normalizedThreshold) { inputThreshold = normalizedThreshold; } qTox/src/audio/backend/openal.h000066400000000000000000000102411415623743500167270ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef OPENAL_H #define OPENAL_H #include "src/audio/iaudiocontrol.h" #include "src/audio/backend/alsink.h" #include "src/audio/backend/alsource.h" #include "src/util/compatiblerecursivemutex.h" #include #include #include #include #include #include #include #include #include #include #ifndef ALC_ALL_DEVICES_SPECIFIER // compatibility with older versions of OpenAL #include #endif class OpenAL : public IAudioControl { Q_OBJECT public: OpenAL(); virtual ~OpenAL(); qreal maxOutputVolume() const { return 1; } qreal minOutputVolume() const { return 0; } qreal outputVolume() const; void setOutputVolume(qreal volume); qreal minInputGain() const; void setMinInputGain(qreal dB); qreal maxInputGain() const; void setMaxInputGain(qreal dB); qreal inputGain() const; void setInputGain(qreal dB); qreal minInputThreshold() const; qreal maxInputThreshold() const; qreal getInputThreshold() const; void setInputThreshold(qreal normalizedThreshold); void reinitInput(const QString& inDevDesc); bool reinitOutput(const QString& outDevDesc); bool isOutputReady() const; QStringList outDeviceNames(); QStringList inDeviceNames(); std::unique_ptr makeSink(); void destroySink(AlSink& sink); std::unique_ptr makeSource(); void destroySource(AlSource& source); void startLoop(uint sourceId); void stopLoop(uint sourceId); void playMono16Sound(AlSink& sink, const IAudioSink::Sound& sound); void stopActive(); void playAudioBuffer(uint sourceId, const int16_t* data, int samples, unsigned channels, int sampleRate); signals: void startActive(qreal msec); protected: static void checkAlError() noexcept; static void checkAlcError(ALCdevice* device) noexcept; qreal inputGainFactor() const; virtual void cleanupInput(); virtual void cleanupOutput(); bool autoInitInput(); bool autoInitOutput(); bool initInput(const QString& deviceName, uint32_t channels); void doAudio(); virtual void doInput(); virtual void doOutput(); virtual void captureSamples(ALCdevice* device, int16_t* buffer, ALCsizei samples); private: virtual bool initInput(const QString& deviceName); virtual bool initOutput(const QString& outDevDescr); void cleanupBuffers(uint sourceId); void cleanupSound(); float getVolume(); protected: QThread* audioThread; mutable CompatibleRecursiveMutex audioLock; QString inDev{}; QString outDev{}; ALCdevice* alInDev = nullptr; QTimer captureTimer; QTimer cleanupTimer; ALCdevice* alOutDev = nullptr; ALCcontext* alOutContext = nullptr; bool outputInitialized = false; // Qt containers need copy operators, so use stdlib containers std::unordered_set sinks; std::unordered_set soundSinks; std::unordered_set sources; int channels = 0; qreal gain = 0; qreal gainFactor = 1; qreal minInGain = -30; qreal maxInGain = 30; qreal inputThreshold = 0; qreal voiceHold = 250; bool isActive = false; QTimer voiceTimer; const qreal minInThreshold = 0.0; const qreal maxInThreshold = 0.4; int16_t* inputBuffer = nullptr; }; #endif // OPENAL_H qTox/src/audio/iaudiocontrol.h000066400000000000000000000110311415623743500167330ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IAUDIOCONTROL_H #define IAUDIOCONTROL_H #include #include #include /** * @class IAudioControl * * @var IAudioControl::AUDIO_SAMPLE_RATE * @brief The next best Opus would take is 24k * * @var IAudioControl::AUDIO_FRAME_DURATION * @brief In milliseconds * * @var IAudioControl::AUDIO_FRAME_SAMPLE_COUNT * @brief Frame sample count * * @fn qreal IAudioControl::outputVolume() const * @brief Returns the current output volume (between 0 and 1) * * @fn void IAudioControl::setOutputVolume(qreal volume) * @brief Set the master output volume. * * @param[in] volume the master volume (between 0 and 1) * * @fn qreal IAudioControl::minInputGain() const * @brief The minimum gain value for an input device. * * @return minimum gain value in dB * * @fn void IAudioControl::setMinInputGain(qreal dB) * @brief Set the minimum allowed gain value in dB. * * @note Default is -30dB; usually you don't need to alter this value; * * @fn qreal IAudioControl::maxInputGain() const * @brief The maximum gain value for an input device. * * @return maximum gain value in dB * * @fn void IAudioControl::setMaxInputGain(qreal dB) * @brief Set the maximum allowed gain value in dB. * * @note Default is 30dB; usually you don't need to alter this value. * * @fn bool IAudioControl::isOutputReady() const * @brief check if the output is ready to play audio * * @return true if the output device is open, false otherwise * * @fn QStringList IAudioControl::outDeviceNames() * @brief Get the names of available output devices * * @return list of output devices * * @fn QStringList IAudioControl::inDeviceNames() * @brief Get the names of available input devices * * @return list of input devices * * @fn qreal IAudioControl::inputGain() const * @brief get the current input gain * * @return current input gain in dB * * @fn void IAudioControl::setInputGain(qreal dB) * @brief set the input gain * * @fn void IAudioControl::getInputThreshold() * @brief get the current input threshold * * @return current input threshold percentage * * @fn void IAudioControl::setInputThreshold(qreal percent) * @brief set the input threshold * * @param[in] percent the new input threshold percentage */ class IAudioSink; class IAudioSource; class IAudioControl : public QObject { Q_OBJECT public: virtual ~IAudioControl() = default; virtual qreal outputVolume() const = 0; virtual void setOutputVolume(qreal volume) = 0; virtual qreal maxOutputVolume() const = 0; virtual qreal minOutputVolume() const = 0; virtual qreal minInputGain() const = 0; virtual void setMinInputGain(qreal dB) = 0; virtual qreal maxInputGain() const = 0; virtual void setMaxInputGain(qreal dB) = 0; virtual qreal inputGain() const = 0; virtual void setInputGain(qreal dB) = 0; virtual qreal minInputThreshold() const = 0; virtual qreal maxInputThreshold() const = 0; virtual qreal getInputThreshold() const = 0; virtual void setInputThreshold(qreal percent) = 0; virtual void reinitInput(const QString& inDevDesc) = 0; virtual bool reinitOutput(const QString& outDevDesc) = 0; virtual bool isOutputReady() const = 0; virtual QStringList outDeviceNames() = 0; virtual QStringList inDeviceNames() = 0; virtual std::unique_ptr makeSink() = 0; virtual std::unique_ptr makeSource() = 0; protected: // Public default audio settings // Samplerate for Tox calls and sounds static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000; static constexpr uint32_t AUDIO_FRAME_DURATION = 20; static constexpr uint32_t AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE / 1000; uint32_t AUDIO_FRAME_SAMPLE_COUNT_TOTAL = 0; }; #endif // IAUDIOCONTROL_H qTox/src/audio/iaudiosettings.h000066400000000000000000000047301415623743500171230ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef I_AUDIO_SETTINGS_H #define I_AUDIO_SETTINGS_H #include "src/model/interface.h" #include class IAudioSettings { public: virtual ~IAudioSettings() = default; virtual QString getInDev() const = 0; virtual void setInDev(const QString& deviceSpecifier) = 0; virtual bool getAudioInDevEnabled() const = 0; virtual void setAudioInDevEnabled(bool enabled) = 0; virtual QString getOutDev() const = 0; virtual void setOutDev(const QString& deviceSpecifier) = 0; virtual bool getAudioOutDevEnabled() const = 0; virtual void setAudioOutDevEnabled(bool enabled) = 0; virtual qreal getAudioInGainDecibel() const = 0; virtual void setAudioInGainDecibel(qreal dB) = 0; virtual qreal getAudioThreshold() const = 0; virtual void setAudioThreshold(qreal percent) = 0; virtual int getOutVolume() const = 0; virtual int getOutVolumeMin() const = 0; virtual int getOutVolumeMax() const = 0; virtual void setOutVolume(int volume) = 0; virtual int getAudioBitrate() const = 0; virtual void setAudioBitrate(int bitrate) = 0; virtual bool getEnableTestSound() const = 0; virtual void setEnableTestSound(bool newValue) = 0; DECLARE_SIGNAL(inDevChanged, const QString& device); DECLARE_SIGNAL(audioInDevEnabledChanged, bool enabled); DECLARE_SIGNAL(outDevChanged, const QString& device); DECLARE_SIGNAL(audioOutDevEnabledChanged, bool enabled); DECLARE_SIGNAL(audioInGainDecibelChanged, qreal dB); DECLARE_SIGNAL(audioThresholdChanged, qreal dB); DECLARE_SIGNAL(outVolumeChanged, int volume); DECLARE_SIGNAL(audioBitrateChanged, int bitrate); DECLARE_SIGNAL(enableTestSoundChanged, bool newValue); }; #endif // I_AUDIO_SETTINGS_H qTox/src/audio/iaudiosink.h000066400000000000000000000071031415623743500162240ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IAUDIOSINK_H #define IAUDIOSINK_H #include #include #include "src/model/interface.h" /** * @brief The IAudioSink class represents an interface to devices that can play audio. * * @enum IAudioSink::Sound * @brief Provides the different sounds for use in the getSound function. * @see getSound * * @value NewMessage Returns the new message notification sound. * @value Test Returns the test sound. * @value IncomingCall Returns the incoming call sound. * @value OutgoingCall Returns the outgoing call sound. * * @fn QString IAudioSink::getSound(Sound s) * @brief Function to get the path of the requested sound. * * @param s Name of the sound to get the path of. * @return The path of the requested sound. * * @fn void IAudioSink::playAudioBuffer(const int16_t* data, int samples, * unsigned channels, int sampleRate) * @brief adds a number of audio frames to the play buffer * * @param[in] data 16bit mono or stereo PCM data with alternating channel * mapping for stereo (LRLR) * @param[in] samples number of samples per channel * @param[in] channels number of channels, currently 1 or 2 is supported * @param[in] sampleRate sample rate in Hertz * * @fn void IAudioSink::playMono16Sound(const Sound& sound) * @brief Play a 44100Hz mono 16bit PCM sound from the builtin sounds. * * @param[in] sound The sound to play * * @fn void IAudioSink::startLoop() * @brief starts looping the sound played with playMono16Sound(...) * * @fn void IAudioSink::stopLoop() * @brief stops looping the sound played with playMono16Sound(...) * */ class IAudioSink { public: enum class Sound { NewMessage, Test, IncomingCall, OutgoingCall, CallEnd }; inline static QString getSound(Sound s) { switch (s) { case Sound::Test: return QStringLiteral(":/audio/notification.s16le.pcm"); case Sound::NewMessage: return QStringLiteral(":/audio/notification.s16le.pcm"); case Sound::IncomingCall: return QStringLiteral(":/audio/ToxIncomingCall.s16le.pcm"); case Sound::OutgoingCall: return QStringLiteral(":/audio/ToxOutgoingCall.s16le.pcm"); case Sound::CallEnd: return QStringLiteral(":/audio/ToxEndCall.s16le.pcm"); } assert(false); return {}; } virtual ~IAudioSink() = default; virtual void playAudioBuffer(const int16_t* data, int samples, unsigned channels, int sampleRate) const = 0; virtual void playMono16Sound(const Sound& sound) = 0; virtual void startLoop() = 0; virtual void stopLoop() = 0; virtual operator bool() const = 0; signals: DECLARE_SIGNAL(finishedPlaying); DECLARE_SIGNAL(invalidated); }; #endif // IAUDIOSINK_H qTox/src/audio/iaudiosource.h000066400000000000000000000027621415623743500165660ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IAUDIOSOURCE_H #define IAUDIOSOURCE_H #include /** * @fn void Audio::frameAvailable(const int16_t *pcm, size_t sample_count, uint8_t channels, * uint32_t sampling_rate); * * When there are input subscribers, we regularly emit captured audio frames with this signal * Always connect with a blocking queued connection lambda, else the behaviour is undefined */ class IAudioSource : public QObject { Q_OBJECT public: virtual ~IAudioSource() = default; virtual operator bool() const = 0; signals: void frameAvailable(const int16_t* pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate); void volumeAvailable(float value); void invalidated(); }; #endif // IAUDIOSOURCE_H qTox/src/chatlog/000077500000000000000000000000001415623743500142335ustar00rootroot00000000000000qTox/src/chatlog/chatline.cpp000066400000000000000000000143041415623743500165300ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chatline.h" #include "chatlinecontent.h" #include #include ChatLine::ChatLine() { } ChatLine::~ChatLine() { for (ChatLineContent* c : content) { if (c->scene()) c->scene()->removeItem(c); delete c; } } void ChatLine::setRow(int idx) { row = idx; for (int c = 0; c < static_cast(content.size()); ++c) content[c]->setIndex(row, c); } void ChatLine::visibilityChanged(bool visible) { if (isVisible != visible) { for (ChatLineContent* c : content) c->visibilityChanged(visible); } isVisible = visible; } int ChatLine::getRow() const { return row; } ChatLineContent* ChatLine::getContent(int col) const { if (col < static_cast(content.size()) && col >= 0) return content[col]; return nullptr; } ChatLineContent* ChatLine::getContent(QPointF scenePos) const { for (ChatLineContent* c : content) { if (c->sceneBoundingRect().contains(scenePos)) return c; } return nullptr; } void ChatLine::removeFromScene() { for (ChatLineContent* c : content) { if (c->scene()) c->scene()->removeItem(c); } } void ChatLine::addToScene(QGraphicsScene* scene) { if (!scene) return; for (ChatLineContent* c : content) scene->addItem(c); } void ChatLine::setVisible(bool visible) { for (ChatLineContent* c : content) c->setVisible(visible); } void ChatLine::selectionCleared() { for (ChatLineContent* c : content) c->selectionCleared(); } void ChatLine::selectionFocusChanged(bool focusIn) { for (ChatLineContent* c : content) c->selectionFocusChanged(focusIn); } void ChatLine::fontChanged(const QFont& font) { for (ChatLineContent* c : content) c->fontChanged(font); } void ChatLine::reloadTheme() { for (ChatLineContent* c : content) { c->reloadTheme(); } } int ChatLine::getColumnCount() { return content.size(); } void ChatLine::updateBBox() { bbox.setHeight(0); bbox.setWidth(width); for (ChatLineContent* c : content) bbox.setHeight(qMax(c->sceneBoundingRect().top() - bbox.top() + c->sceneBoundingRect().height(), bbox.height())); } QRectF ChatLine::sceneBoundingRect() const { return bbox; } void ChatLine::addColumn(ChatLineContent* item, ColumnFormat fmt) { if (!item) return; format.push_back(fmt); content.push_back(item); } void ChatLine::replaceContent(int col, ChatLineContent* lineContent) { if (col >= 0 && col < static_cast(content.size()) && lineContent) { QGraphicsScene* scene = content[col]->scene(); delete content[col]; content[col] = lineContent; lineContent->setIndex(row, col); if (scene) scene->addItem(content[col]); layout(width, bbox.topLeft()); content[col]->visibilityChanged(isVisible); content[col]->update(); } } void ChatLine::layout(qreal w, QPointF scenePos) { if (!content.size()) return; width = w; bbox.setTopLeft(scenePos); qreal fixedWidth = (content.size() - 1) * columnSpacing; qreal varWidth = 0.0; // used for normalisation for (int i = 0; i < format.size(); ++i) { if (format[i].policy == ColumnFormat::FixedSize) fixedWidth += format[i].size; else varWidth += format[i].size; } if (varWidth == 0.0) varWidth = 1.0; qreal leftover = qMax(0.0, width - fixedWidth); qreal maxVOffset = 0.0; qreal xOffset = 0.0; QVector xPos(content.size()); for (int i = 0; i < content.size(); ++i) { // calculate the effective width of the current column qreal width; if (format[i].policy == ColumnFormat::FixedSize) width = format[i].size; else width = format[i].size / varWidth * leftover; // set the width of the current column content[i]->setWidth(width); // calculate horizontal alignment qreal xAlign = 0.0; switch (format[i].hAlign) { case ColumnFormat::Left: break; case ColumnFormat::Right: xAlign = width - content[i]->boundingRect().width(); break; case ColumnFormat::Center: xAlign = (width - content[i]->boundingRect().width()) / 2.0; break; } // reposition xPos[i] = scenePos.x() + xOffset + xAlign; xOffset += width + columnSpacing; maxVOffset = qMax(maxVOffset, content[i]->getAscent()); } for (int i = 0; i < content.size(); ++i) { // calculate vertical alignment // vertical alignment may depend on width, so we do it in a second pass qreal yOffset = maxVOffset - content[i]->getAscent(); // reposition content[i]->setPos(xPos[i], scenePos.y() + yOffset); } updateBBox(); } void ChatLine::moveBy(qreal deltaY) { // reposition only for (ChatLineContent* c : content) c->moveBy(0, deltaY); bbox.moveTop(bbox.top() + deltaY); } bool ChatLine::lessThanBSRectTop(const ChatLine::Ptr& lhs, const qreal& rhs) { return lhs->sceneBoundingRect().top() < rhs; } bool ChatLine::lessThanBSRectBottom(const ChatLine::Ptr& lhs, const qreal& rhs) { return lhs->sceneBoundingRect().bottom() < rhs; } bool ChatLine::lessThanRowIndex(const ChatLine::Ptr& lhs, const ChatLine::Ptr& rhs) { return lhs->getRow() < rhs->getRow(); } qTox/src/chatlog/chatline.h000066400000000000000000000055731415623743500162050ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHATLINE_H #define CHATLINE_H #include #include #include #include class ChatLog; class ChatLineContent; class QGraphicsScene; class QStyleOptionGraphicsItem; class QFont; struct ColumnFormat { enum Policy { FixedSize, VariableSize, }; enum Align { Left, Center, Right, }; ColumnFormat() { } ColumnFormat(qreal s, Policy p, Align halign = Left) : size(s) , policy(p) , hAlign(halign) { } qreal size = 1.0; Policy policy = VariableSize; Align hAlign = Left; }; using ColumnFormats = QVector; class ChatLine { public: using Ptr = std::shared_ptr; ChatLine(); virtual ~ChatLine(); QRectF sceneBoundingRect() const; void replaceContent(int col, ChatLineContent* lineContent); void layout(qreal width, QPointF scenePos); void moveBy(qreal deltaY); void removeFromScene(); void addToScene(QGraphicsScene* scene); void setVisible(bool visible); void selectionCleared(); void selectionFocusChanged(bool focusIn); void fontChanged(const QFont& font); void reloadTheme(); int getColumnCount(); int getRow() const; ChatLineContent* getContent(int col) const; ChatLineContent* getContent(QPointF scenePos) const; bool isOverSelection(QPointF scenePos); // comparators static bool lessThanBSRectTop(const ChatLine::Ptr& lhs, const qreal& rhs); static bool lessThanBSRectBottom(const ChatLine::Ptr& lhs, const qreal& rhs); static bool lessThanRowIndex(const ChatLine::Ptr& lhs, const ChatLine::Ptr& rhs); protected: friend class ChatLog; QPointF mapToContent(ChatLineContent* c, QPointF pos); void addColumn(ChatLineContent* item, ColumnFormat fmt); void updateBBox(); void setRow(int idx); void visibilityChanged(bool visible); private: int row = -1; QVector content; QVector format; qreal width = 100.0; qreal columnSpacing = 15.0; QRectF bbox; bool isVisible = false; }; #endif // CHATLINE_H qTox/src/chatlog/chatlinecontent.cpp000066400000000000000000000034771415623743500201340ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chatlinecontent.h" void ChatLineContent::setIndex(int r, int c) { row = r; col = c; } int ChatLineContent::getColumn() const { return col; } int ChatLineContent::getRow() const { return row; } int ChatLineContent::type() const { return GraphicsItemType::ChatLineContentType; } void ChatLineContent::selectionMouseMove(QPointF) { } void ChatLineContent::selectionStarted(QPointF) { } void ChatLineContent::selectionCleared() { } void ChatLineContent::selectionDoubleClick(QPointF) { } void ChatLineContent::selectionTripleClick(QPointF) { } void ChatLineContent::selectionFocusChanged(bool) { } bool ChatLineContent::isOverSelection(QPointF) const { return false; } QString ChatLineContent::getSelectedText() const { return QString(); } void ChatLineContent::fontChanged(const QFont& font) { Q_UNUSED(font); } qreal ChatLineContent::getAscent() const { return 0.0; } void ChatLineContent::visibilityChanged(bool) { } void ChatLineContent::reloadTheme() { } QString ChatLineContent::getText() const { return QString(); } qTox/src/chatlog/chatlinecontent.h000066400000000000000000000041341415623743500175700ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHATLINECONTENT_H #define CHATLINECONTENT_H #include class ChatLine; class ChatLineContent : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES(QGraphicsItem) public: enum GraphicsItemType { ChatLineContentType = QGraphicsItem::UserType + 1, }; int getColumn() const; int getRow() const; virtual void setWidth(qreal width) = 0; virtual int type() const final; virtual void selectionMouseMove(QPointF scenePos); virtual void selectionStarted(QPointF scenePos); virtual void selectionCleared(); virtual void selectionDoubleClick(QPointF scenePos); virtual void selectionTripleClick(QPointF scenePos); virtual void selectionFocusChanged(bool focusIn); virtual bool isOverSelection(QPointF scenePos) const; virtual QString getSelectedText() const; virtual void fontChanged(const QFont& font); virtual QString getText() const; virtual qreal getAscent() const; virtual QRectF boundingRect() const = 0; virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) = 0; virtual void visibilityChanged(bool visible); virtual void reloadTheme(); private: friend class ChatLine; void setIndex(int row, int col); private: int row = -1; int col = -1; }; #endif // CHATLINECONTENT_H qTox/src/chatlog/chatlinecontentproxy.cpp000066400000000000000000000051441415623743500212270ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chatlinecontentproxy.h" #include "src/chatlog/content/filetransferwidget.h" #include #include #include #include /** * @enum ChatLineContentProxy::ChatLineContentProxyType * @brief Type tag to avoid dynamic_cast of contained QWidget* * * @value GenericType * @value FileTransferWidgetType = 0 */ ChatLineContentProxy::ChatLineContentProxy(QWidget* widget, ChatLineContentProxyType type, int minWidth, float widthInPercent) : widthPercent(widthInPercent) , widthMin(minWidth) , widgetType{type} { proxy = new QGraphicsProxyWidget(this); proxy->setWidget(widget); } ChatLineContentProxy::ChatLineContentProxy(QWidget* widget, int minWidth, float widthInPercent) : ChatLineContentProxy(widget, GenericType, minWidth, widthInPercent) { } ChatLineContentProxy::ChatLineContentProxy(FileTransferWidget* widget, int minWidth, float widthInPercent) : ChatLineContentProxy(widget, FileTransferWidgetType, minWidth, widthInPercent) { } QRectF ChatLineContentProxy::boundingRect() const { QRectF result = proxy->boundingRect(); result.setHeight(result.height() + 5); return result; } void ChatLineContentProxy::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { painter->setClipRect(boundingRect()); proxy->paint(painter, option, widget); } qreal ChatLineContentProxy::getAscent() const { return 7.0; } QWidget* ChatLineContentProxy::getWidget() const { return proxy->widget(); } void ChatLineContentProxy::setWidth(qreal width) { prepareGeometryChange(); proxy->widget()->setFixedWidth(qMax(static_cast(width * widthPercent), widthMin)); } ChatLineContentProxy::ChatLineContentProxyType ChatLineContentProxy::getWidgetType() const { return widgetType; } qTox/src/chatlog/chatlinecontentproxy.h000066400000000000000000000035751415623743500207020ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHATLINECONTENTPROXY_H #define CHATLINECONTENTPROXY_H #include "chatlinecontent.h" #include class FileTransferWidget; class ChatLineContentProxy : public ChatLineContent { Q_OBJECT public: enum ChatLineContentProxyType { GenericType, FileTransferWidgetType = 0, }; public: ChatLineContentProxy(QWidget* widget, int minWidth, float widthInPercent = 1.0f); ChatLineContentProxy(FileTransferWidget* widget, int minWidth, float widthInPercent = 1.0f); QRectF boundingRect() const override; void setWidth(qreal width) override; void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; qreal getAscent() const override; QWidget* getWidget() const; ChatLineContentProxyType getWidgetType() const; protected: ChatLineContentProxy(QWidget* widget, ChatLineContentProxyType type, int minWidth, float widthInPercent); private: QGraphicsProxyWidget* proxy; float widthPercent; int widthMin; const ChatLineContentProxyType widgetType; }; #endif // CHATLINECONTENTPROXY_H qTox/src/chatlog/chatlog.cpp000066400000000000000000000675061415623743500163760ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chatlog.h" #include "chatlinecontent.h" #include "chatlinecontentproxy.h" #include "chatmessage.h" #include "content/filetransferwidget.h" #include "src/widget/translator.h" #include "src/widget/style.h" #include #include #include #include #include #include #include #include #include #include /** * @var ChatLog::repNameAfter * @brief repetition interval sender name (sec) */ template T clamp(T x, T min, T max) { if (x > max) return max; if (x < min) return min; return x; } ChatLog::ChatLog(QWidget* parent) : QGraphicsView(parent) { // Create the scene busyScene = new QGraphicsScene(this); scene = new QGraphicsScene(this); scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex); setScene(scene); // Cfg. setInteractive(true); setAcceptDrops(false); setAlignment(Qt::AlignTop | Qt::AlignLeft); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setDragMode(QGraphicsView::NoDrag); setViewportUpdateMode(MinimalViewportUpdate); setContextMenuPolicy(Qt::CustomContextMenu); setBackgroundBrush(QBrush(Style::getColor(Style::GroundBase), Qt::SolidPattern)); // The selection rect for multi-line selection selGraphItem = scene->addRect(0, 0, 0, 0, selectionRectColor.darker(120), selectionRectColor); selGraphItem->setZValue(-1.0); // behind all other items // copy action (ie. Ctrl+C) copyAction = new QAction(this); copyAction->setIcon(QIcon::fromTheme("edit-copy")); copyAction->setShortcut(QKeySequence::Copy); copyAction->setEnabled(false); connect(copyAction, &QAction::triggered, this, [this]() { copySelectedText(); }); addAction(copyAction); // Ctrl+Insert shortcut QShortcut* copyCtrlInsShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Insert), this); connect(copyCtrlInsShortcut, &QShortcut::activated, this, [this]() { copySelectedText(); }); // select all action (ie. Ctrl+A) selectAllAction = new QAction(this); selectAllAction->setIcon(QIcon::fromTheme("edit-select-all")); selectAllAction->setShortcut(QKeySequence::SelectAll); connect(selectAllAction, &QAction::triggered, this, [this]() { selectAll(); }); addAction(selectAllAction); // This timer is used to scroll the view while the user is // moving the mouse past the top/bottom edge of the widget while selecting. selectionTimer = new QTimer(this); selectionTimer->setInterval(1000 / 30); selectionTimer->setSingleShot(false); selectionTimer->start(); connect(selectionTimer, &QTimer::timeout, this, &ChatLog::onSelectionTimerTimeout); // Background worker // Updates the layout of all chat-lines after a resize workerTimer = new QTimer(this); workerTimer->setSingleShot(false); workerTimer->setInterval(5); connect(workerTimer, &QTimer::timeout, this, &ChatLog::onWorkerTimeout); // This timer is used to detect multiple clicks multiClickTimer = new QTimer(this); multiClickTimer->setSingleShot(true); multiClickTimer->setInterval(QApplication::doubleClickInterval()); connect(multiClickTimer, &QTimer::timeout, this, &ChatLog::onMultiClickTimeout); // selection connect(this, &ChatLog::selectionChanged, this, [this]() { copyAction->setEnabled(hasTextToBeCopied()); copySelectedText(true); }); retranslateUi(); Translator::registerHandler(std::bind(&ChatLog::retranslateUi, this), this); } ChatLog::~ChatLog() { Translator::unregister(this); // Remove chatlines from scene for (ChatLine::Ptr l : lines) l->removeFromScene(); if (busyNotification) busyNotification->removeFromScene(); if (typingNotification) typingNotification->removeFromScene(); } void ChatLog::clearSelection() { if (selectionMode == SelectionMode::None) return; for (int i = selFirstRow; i <= selLastRow; ++i) lines[i]->selectionCleared(); selFirstRow = -1; selLastRow = -1; selClickedCol = -1; selClickedRow = -1; selectionMode = SelectionMode::None; emit selectionChanged(); updateMultiSelectionRect(); } QRect ChatLog::getVisibleRect() const { return mapToScene(viewport()->rect()).boundingRect().toRect(); } void ChatLog::updateSceneRect() { setSceneRect(calculateSceneRect()); } void ChatLog::layout(int start, int end, qreal width) { if (lines.empty()) return; qreal h = 0.0; // Line at start-1 is considered to have the correct position. All following lines are // positioned in respect to this line. if (start - 1 >= 0) h = lines[start - 1]->sceneBoundingRect().bottom() + lineSpacing; start = clamp(start, 0, lines.size()); end = clamp(end + 1, 0, lines.size()); for (int i = start; i < end; ++i) { ChatLine* l = lines[i].get(); l->layout(width, QPointF(0.0, h)); h += l->sceneBoundingRect().height() + lineSpacing; } } void ChatLog::mousePressEvent(QMouseEvent* ev) { QGraphicsView::mousePressEvent(ev); if (ev->button() == Qt::LeftButton) { clickPos = ev->pos(); clearSelection(); } if (lastClickButton == ev->button()) { // Counts only single clicks and first click of doule click clickCount++; } else { clickCount = 1; // restarting counter lastClickButton = ev->button(); } lastClickPos = ev->pos(); // Triggers on odd click counts handleMultiClickEvent(); } void ChatLog::mouseReleaseEvent(QMouseEvent* ev) { QGraphicsView::mouseReleaseEvent(ev); selectionScrollDir = AutoScrollDirection::NoDirection; multiClickTimer->start(); } void ChatLog::mouseMoveEvent(QMouseEvent* ev) { QGraphicsView::mouseMoveEvent(ev); QPointF scenePos = mapToScene(ev->pos()); if (ev->buttons() & Qt::LeftButton) { // autoscroll if (ev->pos().y() < 0) selectionScrollDir = AutoScrollDirection::Up; else if (ev->pos().y() > height()) selectionScrollDir = AutoScrollDirection::Down; else selectionScrollDir = AutoScrollDirection::NoDirection; // select if (selectionMode == SelectionMode::None && (clickPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) { QPointF sceneClickPos = mapToScene(clickPos.toPoint()); ChatLine::Ptr line = findLineByPosY(scenePos.y()); ChatLineContent* content = getContentFromPos(sceneClickPos); if (content) { selClickedRow = content->getRow(); selClickedCol = content->getColumn(); selFirstRow = content->getRow(); selLastRow = content->getRow(); content->selectionStarted(sceneClickPos); selectionMode = SelectionMode::Precise; // ungrab mouse grabber if (scene->mouseGrabberItem()) scene->mouseGrabberItem()->ungrabMouse(); } else if (line.get()) { selClickedRow = line->getRow(); selFirstRow = selClickedRow; selLastRow = selClickedRow; selectionMode = SelectionMode::Multi; } } if (selectionMode != SelectionMode::None) { ChatLineContent* content = getContentFromPos(scenePos); ChatLine::Ptr line = findLineByPosY(scenePos.y()); int row; if (content) { row = content->getRow(); int col = content->getColumn(); if (row == selClickedRow && col == selClickedCol) { selectionMode = SelectionMode::Precise; content->selectionMouseMove(scenePos); selGraphItem->hide(); } else if (col != selClickedCol) { selectionMode = SelectionMode::Multi; lines[selClickedRow]->selectionCleared(); } } else if (line.get()) { row = line->getRow(); if (row != selClickedRow) { selectionMode = SelectionMode::Multi; lines[selClickedRow]->selectionCleared(); } } else { return; } if (row >= selClickedRow) selLastRow = row; if (row <= selClickedRow) selFirstRow = row; updateMultiSelectionRect(); } emit selectionChanged(); } } // Much faster than QGraphicsScene::itemAt()! ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const { if (lines.empty()) return nullptr; auto itr = std::lower_bound(lines.cbegin(), lines.cend(), scenePos.y(), ChatLine::lessThanBSRectBottom); // find content if (itr != lines.cend() && (*itr)->sceneBoundingRect().contains(scenePos)) return (*itr)->getContent(scenePos); return nullptr; } bool ChatLog::isOverSelection(QPointF scenePos) const { if (selectionMode == SelectionMode::Precise) { ChatLineContent* content = getContentFromPos(scenePos); if (content) return content->isOverSelection(scenePos); } else if (selectionMode == SelectionMode::Multi) { if (selGraphItem->rect().contains(scenePos)) return true; } return false; } qreal ChatLog::useableWidth() const { return width() - verticalScrollBar()->sizeHint().width() - margins.right() - margins.left(); } void ChatLog::reposition(int start, int end, qreal deltaY) { if (lines.isEmpty()) return; start = clamp(start, 0, lines.size() - 1); end = clamp(end + 1, 0, lines.size()); for (int i = start; i < end; ++i) { ChatLine* l = lines[i].get(); l->moveBy(deltaY); } } void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l) { if (!l.get()) return; bool stickToBtm = stickToBottom(); // insert l->setRow(lines.size()); l->addToScene(scene); lines.append(l); // partial refresh layout(lines.last()->getRow(), lines.size(), useableWidth()); updateSceneRect(); if (stickToBtm) scrollToBottom(); checkVisibility(); updateTypingNotification(); } void ChatLog::insertChatlineOnTop(ChatLine::Ptr l) { if (!l.get()) return; insertChatlinesOnTop(QList() << l); } void ChatLog::insertChatlinesOnTop(const QList& newLines) { if (newLines.isEmpty()) return; QGraphicsScene::ItemIndexMethod oldIndexMeth = scene->itemIndexMethod(); scene->setItemIndexMethod(QGraphicsScene::NoIndex); // alloc space for old and new lines QVector combLines; combLines.reserve(newLines.size() + lines.size()); // add the new lines int i = 0; for (ChatLine::Ptr l : newLines) { l->addToScene(scene); l->visibilityChanged(false); l->setRow(i++); combLines.push_back(l); } // add the old lines for (ChatLine::Ptr l : lines) { l->setRow(i++); combLines.push_back(l); } lines = combLines; moveSelectionRectDownIfSelected(newLines.size()); scene->setItemIndexMethod(oldIndexMeth); // redo layout startResizeWorker(); } bool ChatLog::stickToBottom() const { return verticalScrollBar()->value() == verticalScrollBar()->maximum(); } void ChatLog::scrollToBottom() { updateSceneRect(); verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } void ChatLog::startResizeWorker() { if (lines.empty()) return; // (re)start the worker if (!workerTimer->isActive()) { // these values must not be reevaluated while the worker is running workerStb = stickToBottom(); if (!visibleLines.empty()) workerAnchorLine = visibleLines.first(); } // switch to busy scene displaying the busy notification if there is a lot // of text to be resized int txt = 0; for (ChatLine::Ptr line : lines) { if (txt > 500000) break; for (ChatLineContent* content : line->content) txt += content->getText().size(); } if (txt > 500000) setScene(busyScene); workerLastIndex = 0; workerTimer->start(); verticalScrollBar()->hide(); } void ChatLog::mouseDoubleClickEvent(QMouseEvent* ev) { QPointF scenePos = mapToScene(ev->pos()); ChatLineContent* content = getContentFromPos(scenePos); if (content) { content->selectionDoubleClick(scenePos); selClickedCol = content->getColumn(); selClickedRow = content->getRow(); selFirstRow = content->getRow(); selLastRow = content->getRow(); selectionMode = SelectionMode::Precise; emit selectionChanged(); } if (lastClickButton == ev->button()) { // Counts the second click of double click clickCount++; } else { clickCount = 1; // restarting counter lastClickButton = ev->button(); } lastClickPos = ev->pos(); // Triggers on even click counts handleMultiClickEvent(); } QString ChatLog::getSelectedText() const { if (selectionMode == SelectionMode::Precise) { return lines[selClickedRow]->content[selClickedCol]->getSelectedText(); } else if (selectionMode == SelectionMode::Multi) { // build a nicely formatted message QString out; for (int i = selFirstRow; i <= selLastRow; ++i) { if (lines[i]->content[1]->getText().isEmpty()) continue; QString timestamp = lines[i]->content[2]->getText().isEmpty() ? tr("pending") : lines[i]->content[2]->getText(); QString author = lines[i]->content[0]->getText(); QString msg = lines[i]->content[1]->getText(); out += QString(out.isEmpty() ? "[%2] %1: %3" : "\n[%2] %1: %3").arg(author, timestamp, msg); } return out; } return QString(); } bool ChatLog::isEmpty() const { return lines.isEmpty(); } bool ChatLog::hasTextToBeCopied() const { return selectionMode != SelectionMode::None; } ChatLine::Ptr ChatLog::getTypingNotification() const { return typingNotification; } QVector ChatLog::getLines() { return lines; } ChatLine::Ptr ChatLog::getLatestLine() const { if (!lines.empty()) { return lines.last(); } return nullptr; } ChatLine::Ptr ChatLog::getFirstLine() const { if (!lines.empty()) { return lines.first(); } return nullptr; } /** * @brief Finds the chat line object at a position on screen * @param pos Position on screen in global coordinates * @sa getContentFromPos() */ ChatLineContent* ChatLog::getContentFromGlobalPos(QPoint pos) const { return getContentFromPos(mapToScene(mapFromGlobal(pos))); } void ChatLog::clear() { clearSelection(); QVector savedLines; for (ChatLine::Ptr l : lines) { if (isActiveFileTransfer(l)) savedLines.push_back(l); else l->removeFromScene(); } lines.clear(); visibleLines.clear(); for (ChatLine::Ptr l : savedLines) insertChatlineAtBottom(l); updateSceneRect(); } void ChatLog::copySelectedText(bool toSelectionBuffer) const { QString text = getSelectedText(); QClipboard* clipboard = QApplication::clipboard(); if (clipboard && !text.isNull()) clipboard->setText(text, toSelectionBuffer ? QClipboard::Selection : QClipboard::Clipboard); } void ChatLog::setBusyNotification(ChatLine::Ptr notification) { if (!notification.get()) return; busyNotification = notification; busyNotification->addToScene(busyScene); busyNotification->visibilityChanged(true); } void ChatLog::setTypingNotification(ChatLine::Ptr notification) { typingNotification = notification; typingNotification->visibilityChanged(true); typingNotification->setVisible(false); typingNotification->addToScene(scene); updateTypingNotification(); } void ChatLog::setTypingNotificationVisible(bool visible) { if (typingNotification.get()) { typingNotification->setVisible(visible); updateTypingNotification(); } } void ChatLog::scrollToLine(ChatLine::Ptr line) { if (!line.get()) return; updateSceneRect(); verticalScrollBar()->setValue(line->sceneBoundingRect().top()); } void ChatLog::selectAll() { if (lines.empty()) return; clearSelection(); selectionMode = SelectionMode::Multi; selFirstRow = 0; selLastRow = lines.size() - 1; emit selectionChanged(); updateMultiSelectionRect(); } void ChatLog::fontChanged(const QFont& font) { for (ChatLine::Ptr l : lines) { l->fontChanged(font); } } void ChatLog::reloadTheme() { setBackgroundBrush(QBrush(Style::getColor(Style::GroundBase), Qt::SolidPattern)); selectionRectColor = Style::getColor(Style::SelectText); selGraphItem->setBrush(QBrush(selectionRectColor)); selGraphItem->setPen(QPen(selectionRectColor.darker(120))); for (ChatLine::Ptr l : lines) { l->reloadTheme(); } } void ChatLog::forceRelayout() { startResizeWorker(); } void ChatLog::checkVisibility(bool causedByScroll) { if (lines.empty()) return; // find first visible line auto lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(), ChatLine::lessThanBSRectBottom); // find last visible line auto upperBound = std::lower_bound(lowerBound, lines.cend(), getVisibleRect().bottom(), ChatLine::lessThanBSRectTop); const ChatLine::Ptr lastLineBeforeVisible = lowerBound == lines.cbegin() ? ChatLine::Ptr() : *std::prev(lowerBound); // set visibilty QList newVisibleLines; for (auto itr = lowerBound; itr != upperBound; ++itr) { newVisibleLines.append(*itr); if (!visibleLines.contains(*itr)) (*itr)->visibilityChanged(true); visibleLines.removeOne(*itr); } // these lines are no longer visible for (ChatLine::Ptr line : visibleLines) line->visibilityChanged(false); visibleLines = newVisibleLines; // enforce order std::sort(visibleLines.begin(), visibleLines.end(), ChatLine::lessThanRowIndex); // if (!visibleLines.empty()) // qDebug() << "visible from " << visibleLines.first()->getRow() << "to " << // visibleLines.last()->getRow() << " total " << visibleLines.size(); if (!visibleLines.isEmpty()) { emit firstVisibleLineChanged(lastLineBeforeVisible, visibleLines.at(0)); } if (causedByScroll && lowerBound != lines.cend() && lowerBound->get()->row == 0) { emit loadHistoryLower(); } } void ChatLog::scrollContentsBy(int dx, int dy) { QGraphicsView::scrollContentsBy(dx, dy); checkVisibility(true); } void ChatLog::resizeEvent(QResizeEvent* ev) { bool stb = stickToBottom(); if (ev->size().width() != ev->oldSize().width()) { startResizeWorker(); stb = false; // let the resize worker handle it } QGraphicsView::resizeEvent(ev); if (stb) scrollToBottom(); updateBusyNotification(); } void ChatLog::updateMultiSelectionRect() { if (selectionMode == SelectionMode::Multi && selFirstRow >= 0 && selLastRow >= 0) { QRectF selBBox; selBBox = selBBox.united(lines[selFirstRow]->sceneBoundingRect()); selBBox = selBBox.united(lines[selLastRow]->sceneBoundingRect()); if (selGraphItem->rect() != selBBox) scene->invalidate(selGraphItem->rect()); selGraphItem->setRect(selBBox); selGraphItem->show(); } else { selGraphItem->hide(); } } void ChatLog::updateTypingNotification() { ChatLine* notification = typingNotification.get(); if (!notification) return; qreal posY = 0.0; if (!lines.empty()) posY = lines.last()->sceneBoundingRect().bottom() + lineSpacing; notification->layout(useableWidth(), QPointF(0.0, posY)); } void ChatLog::updateBusyNotification() { if (busyNotification.get()) { // repoisition the busy notification (centered) busyNotification->layout(useableWidth(), getVisibleRect().topLeft() + QPointF(0, getVisibleRect().height() / 2.0)); } } ChatLine::Ptr ChatLog::findLineByPosY(qreal yPos) const { auto itr = std::lower_bound(lines.cbegin(), lines.cend(), yPos, ChatLine::lessThanBSRectBottom); if (itr != lines.cend()) return *itr; return ChatLine::Ptr(); } QRectF ChatLog::calculateSceneRect() const { qreal bottom = (lines.empty() ? 0.0 : lines.last()->sceneBoundingRect().bottom()); if (typingNotification.get() != nullptr) bottom += typingNotification->sceneBoundingRect().height() + lineSpacing; return QRectF(-margins.left(), -margins.top(), useableWidth(), bottom + margins.bottom() + margins.top()); } void ChatLog::onSelectionTimerTimeout() { const int scrollSpeed = 10; switch (selectionScrollDir) { case AutoScrollDirection::Up: verticalScrollBar()->setValue(verticalScrollBar()->value() - scrollSpeed); break; case AutoScrollDirection::Down: verticalScrollBar()->setValue(verticalScrollBar()->value() + scrollSpeed); break; default: break; } } void ChatLog::onWorkerTimeout() { // Fairly arbitrary but // large values will make the UI unresponsive const int stepSize = 50; layout(workerLastIndex, workerLastIndex + stepSize, useableWidth()); workerLastIndex += stepSize; // done? if (workerLastIndex >= lines.size()) { workerTimer->stop(); // switch back to the scene containing the chat messages setScene(scene); // make sure everything gets updated updateSceneRect(); checkVisibility(); updateTypingNotification(); updateMultiSelectionRect(); // scroll if (workerStb) scrollToBottom(); else scrollToLine(workerAnchorLine); // don't keep a Ptr to the anchor line workerAnchorLine = ChatLine::Ptr(); // hidden during busy screen verticalScrollBar()->show(); emit workerTimeoutFinished(); } } void ChatLog::onMultiClickTimeout() { clickCount = 0; } void ChatLog::handleMultiClickEvent() { // Ignore single or double clicks if (clickCount < 2) return; switch (clickCount) { case 3: QPointF scenePos = mapToScene(lastClickPos); ChatLineContent* content = getContentFromPos(scenePos); if (content) { content->selectionTripleClick(scenePos); selClickedCol = content->getColumn(); selClickedRow = content->getRow(); selFirstRow = content->getRow(); selLastRow = content->getRow(); selectionMode = SelectionMode::Precise; emit selectionChanged(); } break; } } void ChatLog::showEvent(QShowEvent*) { // Empty. // The default implementation calls centerOn - for some reason - causing // the scrollbar to move. } void ChatLog::focusInEvent(QFocusEvent* ev) { QGraphicsView::focusInEvent(ev); if (selectionMode != SelectionMode::None) { selGraphItem->setBrush(QBrush(selectionRectColor)); for (int i = selFirstRow; i <= selLastRow; ++i) lines[i]->selectionFocusChanged(true); } } void ChatLog::focusOutEvent(QFocusEvent* ev) { QGraphicsView::focusOutEvent(ev); if (selectionMode != SelectionMode::None) { selGraphItem->setBrush(QBrush(selectionRectColor.lighter(120))); for (int i = selFirstRow; i <= selLastRow; ++i) lines[i]->selectionFocusChanged(false); } } void ChatLog::retranslateUi() { copyAction->setText(tr("Copy")); selectAllAction->setText(tr("Select all")); } bool ChatLog::isActiveFileTransfer(ChatLine::Ptr l) { int count = l->getColumnCount(); for (int i = 0; i < count; ++i) { ChatLineContent* content = l->getContent(i); ChatLineContentProxy* proxy = qobject_cast(content); if (!proxy) continue; QWidget* widget = proxy->getWidget(); FileTransferWidget* transferWidget = qobject_cast(widget); if (transferWidget && transferWidget->isActive()) return true; } return false; } /** * @brief Adjusts the selection based on chatlog changing lines * @param offset Amount to shift selection rect up by. Must be non-negative. */ void ChatLog::moveSelectionRectUpIfSelected(int offset) { assert(offset >= 0); switch (selectionMode) { case SelectionMode::None: return; case SelectionMode::Precise: movePreciseSelectionUp(offset); break; case SelectionMode::Multi: moveMultiSelectionUp(offset); break; } } /** * @brief Adjusts the selections based on chatlog changing lines * @param offset removed from the lines indexes. Must be non-negative. */ void ChatLog::moveSelectionRectDownIfSelected(int offset) { assert(offset >= 0); switch (selectionMode) { case SelectionMode::None: return; case SelectionMode::Precise: movePreciseSelectionDown(offset); break; case SelectionMode::Multi: moveMultiSelectionDown(offset); break; } } void ChatLog::movePreciseSelectionDown(int offset) { assert(selFirstRow == selLastRow && selFirstRow == selClickedRow); const int lastLine = lines.size() - 1; if (selClickedRow + offset > lastLine) { clearSelection(); } else { const int newRow = selClickedRow + offset; selClickedRow = newRow; selLastRow = newRow; selFirstRow = newRow; emit selectionChanged(); } } void ChatLog::movePreciseSelectionUp(int offset) { assert(selFirstRow == selLastRow && selFirstRow == selClickedRow); if (selClickedRow < offset) { clearSelection(); } else { const int newRow = selClickedRow - offset; selClickedRow = newRow; selLastRow = newRow; selFirstRow = newRow; emit selectionChanged(); } } void ChatLog::moveMultiSelectionUp(int offset) { if (selLastRow < offset) { // entire selection now out of bounds clearSelection(); } else { selLastRow -= offset; selClickedRow = std::max(0, selClickedRow - offset); selFirstRow = std::max(0, selFirstRow - offset); updateMultiSelectionRect(); emit selectionChanged(); } } void ChatLog::moveMultiSelectionDown(int offset) { const int lastLine = lines.size() - 1; if (selFirstRow + offset > lastLine) { // entire selection now out of bounds clearSelection(); } else { selFirstRow += offset; selClickedRow = std::min(lastLine, selClickedRow + offset); selLastRow = std::min(lastLine, selLastRow + offset); updateMultiSelectionRect(); emit selectionChanged(); } } qTox/src/chatlog/chatlog.h000066400000000000000000000126471415623743500160370ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHATLOG_H #define CHATLOG_H #include #include #include #include "chatline.h" #include "chatmessage.h" #include "src/widget/style.h" class QGraphicsScene; class QGraphicsRectItem; class QMouseEvent; class QTimer; class ChatLineContent; struct ToxFile; class ChatLog : public QGraphicsView { Q_OBJECT public: explicit ChatLog(QWidget* parent = nullptr); virtual ~ChatLog(); void insertChatlineAtBottom(ChatLine::Ptr l); void insertChatlineOnTop(ChatLine::Ptr l); void insertChatlinesOnTop(const QList& newLines); void clearSelection(); void clear(); void copySelectedText(bool toSelectionBuffer = false) const; void setBusyNotification(ChatLine::Ptr notification); void setTypingNotification(ChatLine::Ptr notification); void setTypingNotificationVisible(bool visible); void scrollToLine(ChatLine::Ptr line); void selectAll(); void fontChanged(const QFont& font); void reloadTheme(); QString getSelectedText() const; bool isEmpty() const; bool hasTextToBeCopied() const; ChatLine::Ptr getTypingNotification() const; QVector getLines(); ChatLine::Ptr getLatestLine() const; ChatLine::Ptr getFirstLine() const; ChatLineContent* getContentFromGlobalPos(QPoint pos) const; const uint repNameAfter = 5 * 60; signals: void selectionChanged(); void workerTimeoutFinished(); void firstVisibleLineChanged(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& firstLine); void loadHistoryLower(); public slots: void forceRelayout(); private slots: void onSelectionTimerTimeout(); void onWorkerTimeout(); void onMultiClickTimeout(); protected: QRectF calculateSceneRect() const; QRect getVisibleRect() const; ChatLineContent* getContentFromPos(QPointF scenePos) const; void layout(int start, int end, qreal width); bool isOverSelection(QPointF scenePos) const; bool stickToBottom() const; qreal useableWidth() const; void reposition(int start, int end, qreal deltaY); void updateSceneRect(); void checkVisibility(bool causedByScroll = false); void scrollToBottom(); void startResizeWorker(); virtual void mouseDoubleClickEvent(QMouseEvent* ev) final override; virtual void mousePressEvent(QMouseEvent* ev) final override; virtual void mouseReleaseEvent(QMouseEvent* ev) final override; virtual void mouseMoveEvent(QMouseEvent* ev) final override; virtual void scrollContentsBy(int dx, int dy) final override; virtual void resizeEvent(QResizeEvent* ev) final override; virtual void showEvent(QShowEvent*) final override; virtual void focusInEvent(QFocusEvent* ev) final override; virtual void focusOutEvent(QFocusEvent* ev) final override; void updateMultiSelectionRect(); void updateTypingNotification(); void updateBusyNotification(); ChatLine::Ptr findLineByPosY(qreal yPos) const; private: void retranslateUi(); bool isActiveFileTransfer(ChatLine::Ptr l); void handleMultiClickEvent(); void moveSelectionRectUpIfSelected(int offset); void moveSelectionRectDownIfSelected(int offset); void movePreciseSelectionDown(int offset); void movePreciseSelectionUp(int offset); void moveMultiSelectionUp(int offset); void moveMultiSelectionDown(int offset); private: enum class SelectionMode { None, Precise, Multi, }; enum class AutoScrollDirection { NoDirection, Up, Down, }; QAction* copyAction = nullptr; QAction* selectAllAction = nullptr; QGraphicsScene* scene = nullptr; QGraphicsScene* busyScene = nullptr; QVector lines; QList visibleLines; ChatLine::Ptr typingNotification; ChatLine::Ptr busyNotification; // selection int selClickedRow = -1; // These 4 are only valid while selectionMode != None int selClickedCol = -1; int selFirstRow = -1; int selLastRow = -1; QColor selectionRectColor = Style::getColor(Style::SelectText); SelectionMode selectionMode = SelectionMode::None; QPointF clickPos; QGraphicsRectItem* selGraphItem = nullptr; QTimer* selectionTimer = nullptr; QTimer* workerTimer = nullptr; QTimer* multiClickTimer = nullptr; AutoScrollDirection selectionScrollDir = AutoScrollDirection::NoDirection; int clickCount = 0; QPoint lastClickPos; Qt::MouseButton lastClickButton; // worker vars int workerLastIndex = 0; bool workerStb = false; ChatLine::Ptr workerAnchorLine; // layout QMargins margins = QMargins(10, 10, 10, 10); qreal lineSpacing = 5.0f; }; #endif // CHATLOG_H qTox/src/chatlog/chatmessage.cpp000066400000000000000000000236011415623743500172250ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chatmessage.h" #include "chatlinecontentproxy.h" #include "textformatter.h" #include "content/filetransferwidget.h" #include "content/image.h" #include "content/notificationicon.h" #include "content/spinner.h" #include "content/text.h" #include "content/timestamp.h" #include "content/broken.h" #include "src/widget/style.h" #include #include #include "src/persistence/settings.h" #include "src/persistence/smileypack.h" #include "src/persistence/history.h" #define NAME_COL_WIDTH 90.0 #define TIME_COL_WIDTH 90.0 ChatMessage::ChatMessage() { } ChatMessage::Ptr ChatMessage::createChatMessage(const QString& sender, const QString& rawMessage, MessageType type, bool isMe, MessageState state, const QDateTime& date, bool colorizeName) { ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); QString text = rawMessage.toHtmlEscaped(); QString senderText = sender; auto textType = Text::NORMAL; // smileys if (Settings::getInstance().getUseEmoticons()) text = SmileyPack::getInstance().smileyfied(text); // quotes (green text) text = detectQuotes(text, type); text = highlightURI(text); // text styling Settings::StyleType styleType = Settings::getInstance().getStylePreference(); if (styleType != Settings::StyleType::NONE) { text = applyMarkdown(text, styleType == Settings::StyleType::WITH_CHARS); } switch (type) { case NORMAL: text = wrapDiv(text, "msg"); break; case ACTION: textType = Text::ACTION; senderText = "*"; text = wrapDiv(QString("%1 %2").arg(sender.toHtmlEscaped(), text), "action"); msg->setAsAction(); break; case ALERT: text = wrapDiv(text, "alert"); break; } // Note: Eliding cannot be enabled for RichText items. (QTBUG-17207) QFont baseFont = Settings::getInstance().getChatMessageFont(); QFont authorFont = baseFont; if (isMe) authorFont.setBold(true); QColor color = Style::getColor(Style::MainText); if (colorizeName) { QByteArray hash = QCryptographicHash::hash((sender.toUtf8()), QCryptographicHash::Sha256); quint8 *data = (quint8*)hash.data(); color.setHsv(data[0], 255, 196); if (!isMe && textType == Text::NORMAL) { textType = Text::CUSTOM; } } msg->addColumn(new Text(senderText, authorFont, true, sender, textType, color), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); msg->addColumn(new Text(text, baseFont, false, ((type == ACTION) && isMe) ? QString("%1 %2").arg(sender, rawMessage) : rawMessage), ColumnFormat(1.0, ColumnFormat::VariableSize)); switch (state) { case MessageState::complete: msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); break; case MessageState::pending: msg->addColumn(new Spinner(Style::getImagePath("chatArea/spinner.svg"), QSize(16, 16), 360.0 / 1.6), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); break; case MessageState::broken: msg->addColumn(new Broken(Style::getImagePath("chatArea/error.svg"), QSize(16, 16)), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); break; } return msg; } ChatMessage::Ptr ChatMessage::createChatInfoMessage(const QString& rawMessage, SystemMessageType type, const QDateTime& date) { ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); QString text = rawMessage.toHtmlEscaped(); QString img; switch (type) { case INFO: img = Style::getImagePath("chatArea/info.svg"); break; case ERROR: img = Style::getImagePath("chatArea/error.svg"); break; case TYPING: img = Style::getImagePath("chatArea/typing.svg"); break; } QFont baseFont = Settings::getInstance().getChatMessageFont(); msg->addColumn(new Image(QSize(18, 18), img), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); msg->addColumn(new Text("" + text + "", baseFont, false, text), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left)); msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); return msg; } ChatMessage::Ptr ChatMessage::createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date) { ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); QFont baseFont = Settings::getInstance().getChatMessageFont(); QFont authorFont = baseFont; if (isMe) authorFont.setBold(true); msg->addColumn(new Text(sender, authorFont, true), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); msg->addColumn(new ChatLineContentProxy(new FileTransferWidget(nullptr, file), 320, 0.6f), ColumnFormat(1.0, ColumnFormat::VariableSize)); msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); return msg; } ChatMessage::Ptr ChatMessage::createTypingNotification() { ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); QFont baseFont = Settings::getInstance().getChatMessageFont(); // Note: "[user]..." is just a placeholder. The actual text is set in // ChatForm::setFriendTyping() // // FIXME: Due to circumstances, placeholder is being used in a case where // user received typing notifications constantly since contact came online. // This causes "[user]..." to be displayed in place of user nick, as long // as user will keep typing. Issue #1280 msg->addColumn(new NotificationIcon(QSize(18, 18)), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); msg->addColumn(new Text("[user]...", baseFont, false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left)); return msg; } /** * @brief Create message placeholder while chatform restructures text * * It can take a while for chatform to resize large amounts of text, thus * a message placeholder is needed to inform users about it. * * @return created message */ ChatMessage::Ptr ChatMessage::createBusyNotification() { ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); QFont baseFont = Settings::getInstance().getChatMessageFont(); baseFont.setPixelSize(baseFont.pixelSize() + 2); baseFont.setBold(true); msg->addColumn(new Text(QObject::tr("Reformatting text in progress.."), baseFont, false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Center)); return msg; } void ChatMessage::markAsDelivered(const QDateTime& time) { QFont baseFont = Settings::getInstance().getChatMessageFont(); // remove the spinner and replace it by $time replaceContent(2, new Timestamp(time, Settings::getInstance().getTimestampFormat(), baseFont)); } QString ChatMessage::toString() const { ChatLineContent* c = getContent(1); if (c) return c->getText(); return QString(); } bool ChatMessage::isAction() const { return action; } void ChatMessage::setAsAction() { action = true; } void ChatMessage::hideSender() { ChatLineContent* c = getContent(0); if (c) c->hide(); } void ChatMessage::hideDate() { ChatLineContent* c = getContent(2); if (c) c->hide(); } QString ChatMessage::detectQuotes(const QString& str, MessageType type) { // detect text quotes QStringList messageLines = str.split("\n"); QString quotedText; for (int i = 0; i < messageLines.size(); ++i) { // don't quote first line in action message. This makes co-existence of // quotes and action messages possible, since only first line can cause // problems in case where there is quote in it used. if (QRegExp("^(>|>).*").exactMatch(messageLines[i])) { if (i > 0 || type != ACTION) quotedText += "" + messageLines[i] + " "; else quotedText += messageLines[i]; } else { quotedText += messageLines[i]; } if (i < messageLines.size() - 1) { quotedText += '\n'; } } return quotedText; } QString ChatMessage::wrapDiv(const QString& str, const QString& div) { return QString("

%2

").arg(div, /*QChar(0x200E) + */ QString(str)); } qTox/src/chatlog/chatmessage.h000066400000000000000000000045041415623743500166730ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHATMESSAGE_H #define CHATMESSAGE_H #include "chatline.h" #include "src/core/toxfile.h" #include "src/persistence/history.h" #include class QGraphicsScene; class ChatMessage : public ChatLine { public: using Ptr = std::shared_ptr; enum SystemMessageType { INFO, ERROR, TYPING, }; enum MessageType { NORMAL, ACTION, ALERT, }; ChatMessage(); static ChatMessage::Ptr createChatMessage(const QString& sender, const QString& rawMessage, MessageType type, bool isMe, MessageState state, const QDateTime& date, bool colorizeName = false); static ChatMessage::Ptr createChatInfoMessage(const QString& rawMessage, SystemMessageType type, const QDateTime& date); static ChatMessage::Ptr createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date); static ChatMessage::Ptr createTypingNotification(); static ChatMessage::Ptr createBusyNotification(); void markAsDelivered(const QDateTime& time); QString toString() const; bool isAction() const; void setAsAction(); void hideSender(); void hideDate(); protected: static QString detectQuotes(const QString& str, MessageType type); static QString wrapDiv(const QString& str, const QString& div); private: bool action = false; }; #endif // CHATMESSAGE_H qTox/src/chatlog/content/000077500000000000000000000000001415623743500157055ustar00rootroot00000000000000qTox/src/chatlog/content/broken.cpp000066400000000000000000000030371415623743500176740ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "broken.h" #include "src/chatlog/pixmapcache.h" #include class QStyleOptionGraphicsItem; Broken::Broken(const QString& img, QSize size) : pmap{PixmapCache::getInstance().get(img, size)} , size{size} { } QRectF Broken::boundingRect() const { return QRectF(QPointF(-size.width() / 2.0, -size.height() / 2.0), size); } void Broken::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->drawPixmap(0, 0, pmap); Q_UNUSED(option) Q_UNUSED(widget) } void Broken::setWidth(qreal width) { Q_UNUSED(width); } void Broken::visibilityChanged(bool visible) { Q_UNUSED(visible); } qreal Broken::getAscent() const { return 0.0; } qTox/src/chatlog/content/broken.h000066400000000000000000000024731415623743500173440ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef BROKEN_H #define BROKEN_H #include "../chatlinecontent.h" #include #include class Broken : public ChatLineContent { Q_OBJECT public: Broken(const QString& img, QSize size); QRectF boundingRect() const override; void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; void setWidth(qreal width) override; void visibilityChanged(bool visible) override; qreal getAscent() const override; private: QSize size; QPixmap pmap; }; #endif // BROKEN_H qTox/src/chatlog/content/filetransferwidget.cpp000066400000000000000000000555661415623743500223220ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "filetransferwidget.h" #include "ui_filetransferwidget.h" #include "src/core/core.h" #include "src/core/corefile.h" #include "src/persistence/settings.h" #include "src/widget/gui.h" #include "src/widget/style.h" #include "src/widget/widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // The leftButton is used to accept, pause, or resume a file transfer, as well as to open a // received file. // The rightButton is used to cancel a file transfer, or to open the directory a file was // downloaded to. FileTransferWidget::FileTransferWidget(QWidget* parent, ToxFile file) : QWidget(parent) , ui(new Ui::FileTransferWidget) , fileInfo(file) , backgroundColor(Style::getColor(Style::TransferMiddle)) , buttonColor(Style::getColor(Style::TransferWait)) , buttonBackgroundColor(Style::getColor(Style::GroundBase)) , active(true) { ui->setupUi(this); // hide the QWidget background (background-color: transparent doesn't seem to work) setAttribute(Qt::WA_TranslucentBackground, true); ui->previewButton->hide(); ui->filenameLabel->setText(file.fileName); ui->progressBar->setValue(0); ui->fileSizeLabel->setText(getHumanReadableSize(file.filesize)); ui->etaLabel->setText(""); backgroundColorAnimation = new QVariantAnimation(this); backgroundColorAnimation->setDuration(500); backgroundColorAnimation->setEasingCurve(QEasingCurve::OutCubic); connect(backgroundColorAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) { backgroundColor = val.value(); update(); }); buttonColorAnimation = new QVariantAnimation(this); buttonColorAnimation->setDuration(500); buttonColorAnimation->setEasingCurve(QEasingCurve::OutCubic); connect(buttonColorAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) { buttonColor = val.value(); update(); }); CoreFile* coreFile = Core::getInstance()->getCoreFile(); connect(ui->leftButton, &QPushButton::clicked, this, &FileTransferWidget::onLeftButtonClicked); connect(ui->rightButton, &QPushButton::clicked, this, &FileTransferWidget::onRightButtonClicked); connect(ui->previewButton, &QPushButton::clicked, this, &FileTransferWidget::onPreviewButtonClicked); // Set lastStatus to anything but the file's current value, this forces an update lastStatus = file.status == ToxFile::FINISHED ? ToxFile::INITIALIZING : ToxFile::FINISHED; updateWidget(file); setFixedHeight(64); } FileTransferWidget::~FileTransferWidget() { delete ui; } // TODO(sudden6): remove file IO from the UI /** * @brief Dangerous way to find out if a path is writable. * @param filepath Path to file which should be deleted. * @return True, if file writeable, false otherwise. */ bool FileTransferWidget::tryRemoveFile(const QString& filepath) { QFile tmp(filepath); bool writable = tmp.open(QIODevice::WriteOnly); tmp.remove(); return writable; } void FileTransferWidget::onFileTransferUpdate(ToxFile file) { updateWidget(file); } bool FileTransferWidget::isActive() const { return active; } void FileTransferWidget::acceptTransfer(const QString& filepath) { if (filepath.isEmpty()) { return; } // test if writable if (!tryRemoveFile(filepath)) { GUI::showWarning(tr("Location not writable", "Title of permissions popup"), tr("You do not have permission to write that location. Choose another, or " "cancel the save dialog.", "text of permissions popup")); return; } // everything ok! CoreFile* coreFile = Core::getInstance()->getCoreFile(); coreFile->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath); } void FileTransferWidget::setBackgroundColor(const QColor& c, bool whiteFont) { if (c != backgroundColor) { backgroundColorAnimation->setStartValue(backgroundColor); backgroundColorAnimation->setEndValue(c); backgroundColorAnimation->start(); } setProperty("fontColor", whiteFont ? "white" : "black"); setStyleSheet(Style::getStylesheet("fileTransferInstance/filetransferWidget.css")); Style::repolish(this); update(); } void FileTransferWidget::setButtonColor(const QColor& c) { if (c != buttonColor) { buttonColorAnimation->setStartValue(buttonColor); buttonColorAnimation->setEndValue(c); buttonColorAnimation->start(); } } bool FileTransferWidget::drawButtonAreaNeeded() const { return (ui->rightButton->isVisible() || ui->leftButton->isVisible()) && !(ui->leftButton->isVisible() && ui->leftButton->objectName() == "ok"); } void FileTransferWidget::paintEvent(QPaintEvent*) { // required by Hi-DPI support as border-image doesn't work. QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); qreal ratio = static_cast(geometry().height()) / static_cast(geometry().width()); const int r = 24; const int buttonFieldWidth = 32; const int lineWidth = 1; // Draw the widget background: painter.setClipRect(QRect(0, 0, width(), height())); painter.setBrush(QBrush(backgroundColor)); painter.drawRoundedRect(geometry(), r * ratio, r, Qt::RelativeSize); if (drawButtonAreaNeeded()) { // Draw the button background: QPainterPath buttonBackground; buttonBackground.addRoundedRect(width() - 2 * buttonFieldWidth - lineWidth * 2, 0, buttonFieldWidth, buttonFieldWidth + lineWidth, 50, 50, Qt::RelativeSize); buttonBackground.addRect(width() - 2 * buttonFieldWidth - lineWidth * 2, 0, buttonFieldWidth * 2, buttonFieldWidth / 2); buttonBackground.addRect(width() - 1.5 * buttonFieldWidth - lineWidth * 2, 0, buttonFieldWidth * 2, buttonFieldWidth + 1); buttonBackground.setFillRule(Qt::WindingFill); painter.setBrush(QBrush(buttonBackgroundColor)); painter.drawPath(buttonBackground); // Draw the left button: QPainterPath leftButton; leftButton.addRoundedRect(QRect(width() - 2 * buttonFieldWidth - lineWidth, 0, buttonFieldWidth, buttonFieldWidth), 50, 50, Qt::RelativeSize); leftButton.addRect(QRect(width() - 2 * buttonFieldWidth - lineWidth, 0, buttonFieldWidth / 2, buttonFieldWidth / 2)); leftButton.addRect(QRect(width() - 1.5 * buttonFieldWidth - lineWidth, 0, buttonFieldWidth / 2, buttonFieldWidth)); leftButton.setFillRule(Qt::WindingFill); painter.setBrush(QBrush(buttonColor)); painter.drawPath(leftButton); // Draw the right button: painter.setBrush(QBrush(buttonColor)); painter.setClipRect(QRect(width() - buttonFieldWidth, 0, buttonFieldWidth, buttonFieldWidth)); painter.drawRoundedRect(geometry(), r * ratio, r, Qt::RelativeSize); } } QString FileTransferWidget::getHumanReadableSize(qint64 size) { static const char* suffix[] = {"B", "kiB", "MiB", "GiB", "TiB"}; int exp = 0; if (size > 0) { exp = std::min((int)(log(size) / log(1024)), (int)(sizeof(suffix) / sizeof(suffix[0]) - 1)); } return QString().setNum(size / pow(1024, exp), 'f', exp > 1 ? 2 : 0).append(suffix[exp]); } void FileTransferWidget::updateWidgetColor(ToxFile const& file) { if (lastStatus == file.status) { return; } switch (file.status) { case ToxFile::INITIALIZING: case ToxFile::PAUSED: case ToxFile::TRANSMITTING: setBackgroundColor(Style::getColor(Style::TransferMiddle), false); break; case ToxFile::BROKEN: case ToxFile::CANCELED: setBackgroundColor(Style::getColor(Style::TransferBad), true); break; case ToxFile::FINISHED: setBackgroundColor(Style::getColor(Style::TransferGood), true); break; default: qCritical() << "Invalid file status"; assert(false); } } void FileTransferWidget::updateWidgetText(ToxFile const& file) { if (lastStatus == file.status && file.status != ToxFile::PAUSED) { return; } switch (file.status) { case ToxFile::INITIALIZING: if (file.direction == ToxFile::SENDING) { ui->progressLabel->setText(tr("Waiting to send...", "file transfer widget")); } else { ui->progressLabel->setText(tr("Accept to receive this file", "file transfer widget")); } break; case ToxFile::PAUSED: ui->etaLabel->setText(""); if (file.pauseStatus.localPaused()) { ui->progressLabel->setText(tr("Paused", "file transfer widget")); } else { ui->progressLabel->setText(tr("Remote Paused", "file transfer widget")); } break; case ToxFile::TRANSMITTING: ui->etaLabel->setText(""); ui->progressLabel->setText(tr("Resuming...", "file transfer widget")); break; case ToxFile::BROKEN: case ToxFile::CANCELED: break; case ToxFile::FINISHED: break; default: qCritical() << "Invalid file status"; assert(false); } } void FileTransferWidget::updatePreview(ToxFile const& file) { if (lastStatus == file.status) { return; } switch (file.status) { case ToxFile::INITIALIZING: case ToxFile::PAUSED: case ToxFile::TRANSMITTING: case ToxFile::BROKEN: case ToxFile::CANCELED: if (file.direction == ToxFile::SENDING) { showPreview(file.filePath); } break; case ToxFile::FINISHED: showPreview(file.filePath); break; default: qCritical() << "Invalid file status"; assert(false); } } void FileTransferWidget::updateFileProgress(ToxFile const& file) { switch (file.status) { case ToxFile::INITIALIZING: break; case ToxFile::PAUSED: fileProgress.resetSpeed(); break; case ToxFile::TRANSMITTING: { if (!fileProgress.needsUpdate()) { break; } fileProgress.addSample(file); auto speed = fileProgress.getSpeed(); auto progress = fileProgress.getProgress(); auto remainingTime = fileProgress.getTimeLeftSeconds(); ui->progressBar->setValue(static_cast(progress * 100.0)); // update UI if (speed > 0) { // ETA QTime toGo = QTime(0, 0).addSecs(remainingTime); QString format = toGo.hour() > 0 ? "hh:mm:ss" : "mm:ss"; ui->etaLabel->setText(toGo.toString(format)); } else { ui->etaLabel->setText(""); } ui->progressLabel->setText(getHumanReadableSize(speed) + "/s"); break; } case ToxFile::BROKEN: case ToxFile::CANCELED: case ToxFile::FINISHED: { ui->progressBar->hide(); ui->progressLabel->hide(); ui->etaLabel->hide(); break; } default: qCritical() << "Invalid file status"; assert(false); } } void FileTransferWidget::updateSignals(ToxFile const& file) { if (lastStatus == file.status) { return; } switch (file.status) { case ToxFile::CANCELED: case ToxFile::BROKEN: case ToxFile::FINISHED: active = false; disconnect(Core::getInstance()->getCoreFile(), nullptr, this, nullptr); break; case ToxFile::INITIALIZING: case ToxFile::PAUSED: case ToxFile::TRANSMITTING: break; default: qCritical() << "Invalid file status"; assert(false); } } void FileTransferWidget::setupButtons(ToxFile const& file) { if (lastStatus == file.status && file.status != ToxFile::PAUSED) { return; } switch (file.status) { case ToxFile::TRANSMITTING: ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg"))); ui->leftButton->setObjectName("pause"); ui->leftButton->setToolTip(tr("Pause transfer")); ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg"))); ui->rightButton->setObjectName("cancel"); ui->rightButton->setToolTip(tr("Cancel transfer")); setButtonColor(Style::getColor(Style::TransferGood)); break; case ToxFile::PAUSED: if (file.pauseStatus.localPaused()) { ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/arrow_white.svg"))); ui->leftButton->setObjectName("resume"); ui->leftButton->setToolTip(tr("Resume transfer")); } else { ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg"))); ui->leftButton->setObjectName("pause"); ui->leftButton->setToolTip(tr("Pause transfer")); } ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg"))); ui->rightButton->setObjectName("cancel"); ui->rightButton->setToolTip(tr("Cancel transfer")); setButtonColor(Style::getColor(Style::TransferMiddle)); break; case ToxFile::INITIALIZING: ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg"))); ui->rightButton->setObjectName("cancel"); ui->rightButton->setToolTip(tr("Cancel transfer")); if (file.direction == ToxFile::SENDING) { ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg"))); ui->leftButton->setObjectName("pause"); ui->leftButton->setToolTip(tr("Pause transfer")); } else { ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/yes.svg"))); ui->leftButton->setObjectName("accept"); ui->leftButton->setToolTip(tr("Accept transfer")); } break; case ToxFile::CANCELED: case ToxFile::BROKEN: ui->leftButton->hide(); ui->rightButton->hide(); break; case ToxFile::FINISHED: ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/yes.svg"))); ui->leftButton->setObjectName("ok"); ui->leftButton->setToolTip(tr("Open file")); ui->leftButton->show(); ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/dir.svg"))); ui->rightButton->setObjectName("dir"); ui->rightButton->setToolTip(tr("Open file directory")); ui->rightButton->show(); break; default: qCritical() << "Invalid file status"; assert(false); } } void FileTransferWidget::handleButton(QPushButton* btn) { CoreFile* coreFile = Core::getInstance()->getCoreFile(); if (fileInfo.direction == ToxFile::SENDING) { if (btn->objectName() == "cancel") { coreFile->cancelFileSend(fileInfo.friendId, fileInfo.fileNum); } else if (btn->objectName() == "pause") { coreFile->pauseResumeFile(fileInfo.friendId, fileInfo.fileNum); } else if (btn->objectName() == "resume") { coreFile->pauseResumeFile(fileInfo.friendId, fileInfo.fileNum); } } else // receiving or paused { if (btn->objectName() == "cancel") { coreFile->cancelFileRecv(fileInfo.friendId, fileInfo.fileNum); } else if (btn->objectName() == "pause") { coreFile->pauseResumeFile(fileInfo.friendId, fileInfo.fileNum); } else if (btn->objectName() == "resume") { coreFile->pauseResumeFile(fileInfo.friendId, fileInfo.fileNum); } else if (btn->objectName() == "accept") { QString path = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Save a file", "Title of the file saving dialog"), Settings::getInstance().getGlobalAutoAcceptDir() + "/" + fileInfo.fileName); acceptTransfer(path); } } if (btn->objectName() == "ok" || btn->objectName() == "previewButton") { Widget::confirmExecutableOpen(QFileInfo(fileInfo.filePath)); } else if (btn->objectName() == "dir") { QString dirPath = QFileInfo(fileInfo.filePath).dir().path(); QDesktopServices::openUrl(QUrl::fromLocalFile(dirPath)); } } void FileTransferWidget::showPreview(const QString& filename) { static const QStringList previewExtensions = {"png", "jpeg", "jpg", "gif", "svg", "PNG", "JPEG", "JPG", "GIF", "SVG"}; if (previewExtensions.contains(QFileInfo(filename).suffix())) { // Subtract to make border visible const int size = qMax(ui->previewButton->width(), ui->previewButton->height()) - 4; QFile imageFile(filename); if (!imageFile.open(QIODevice::ReadOnly)) { return; } const QByteArray imageFileData = imageFile.readAll(); QImage image = QImage::fromData(imageFileData); const int exifOrientation = getExifOrientation(imageFileData.constData(), imageFileData.size()); if (exifOrientation) { applyTransformation(exifOrientation, image); } const QPixmap iconPixmap = scaleCropIntoSquare(QPixmap::fromImage(image), size); ui->previewButton->setIcon(QIcon(iconPixmap)); ui->previewButton->setIconSize(iconPixmap.size()); ui->previewButton->show(); // Show mouseover preview, but make sure it's not larger than 50% of the screen // width/height const QRect desktopSize = QApplication::desktop()->geometry(); const int maxPreviewWidth{desktopSize.width() / 2}; const int maxPreviewHeight{desktopSize.height() / 2}; const QImage previewImage = [&image, maxPreviewWidth, maxPreviewHeight]() { if (image.width() > maxPreviewWidth || image.height() > maxPreviewHeight) { return image.scaled(maxPreviewWidth, maxPreviewHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { return image; } }(); QByteArray imageData; QBuffer buffer(&imageData); buffer.open(QIODevice::WriteOnly); previewImage.save(&buffer, "PNG"); buffer.close(); ui->previewButton->setToolTip(""); } } void FileTransferWidget::onLeftButtonClicked() { handleButton(ui->leftButton); } void FileTransferWidget::onRightButtonClicked() { handleButton(ui->rightButton); } void FileTransferWidget::onPreviewButtonClicked() { handleButton(ui->previewButton); } QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap& source, const int targetSize) { QPixmap result; // Make sure smaller-than-icon images (at least one dimension is smaller) will not be // upscaled if (source.width() < targetSize || source.height() < targetSize) { result = source; } else { result = source.scaled(targetSize, targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } // Then, image has to be cropped (if needed) so it will not overflow rectangle // Only one dimension will be bigger after Qt::KeepAspectRatioByExpanding if (result.width() > targetSize) { return result.copy((result.width() - targetSize) / 2, 0, targetSize, targetSize); } else if (result.height() > targetSize) { return result.copy(0, (result.height() - targetSize) / 2, targetSize, targetSize); } // Picture was rectangle in the first place, no cropping return result; } int FileTransferWidget::getExifOrientation(const char* data, const int size) { ExifData* exifData = exif_data_new_from_data(reinterpret_cast(data), size); if (!exifData) { return 0; } int orientation = 0; const ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); const ExifEntry* const exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION); if (exifEntry) { orientation = exif_get_short(exifEntry->data, byteOrder); } exif_data_free(exifData); return orientation; } void FileTransferWidget::applyTransformation(const int orientation, QImage& image) { QTransform exifTransform; switch (static_cast(orientation)) { case ExifOrientation::TopLeft: break; case ExifOrientation::TopRight: image = image.mirrored(1, 0); break; case ExifOrientation::BottomRight: exifTransform.rotate(180); break; case ExifOrientation::BottomLeft: image = image.mirrored(0, 1); break; case ExifOrientation::LeftTop: exifTransform.rotate(90); image = image.mirrored(0, 1); break; case ExifOrientation::RightTop: exifTransform.rotate(-90); break; case ExifOrientation::RightBottom: exifTransform.rotate(-90); image = image.mirrored(0, 1); break; case ExifOrientation::LeftBottom: exifTransform.rotate(90); break; default: qWarning() << "Invalid exif orientation passed to applyTransformation!"; } image = image.transformed(exifTransform); } void FileTransferWidget::updateWidget(ToxFile const& file) { assert(file == fileInfo); fileInfo = file; // If we repainted on every packet our gui would be *very* slow bool bTransmitNeedsUpdate = fileProgress.needsUpdate(); updatePreview(file); updateFileProgress(file); updateWidgetText(file); updateWidgetColor(file); setupButtons(file); updateSignals(file); lastStatus = file.status; // trigger repaint switch (file.status) { case ToxFile::TRANSMITTING: if (!bTransmitNeedsUpdate) { break; } // fallthrough default: update(); } } qTox/src/chatlog/content/filetransferwidget.h000066400000000000000000000066531415623743500217600ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FILETRANSFERWIDGET_H #define FILETRANSFERWIDGET_H #include #include #include "src/chatlog/chatlinecontent.h" #include "src/chatlog/toxfileprogress.h" #include "src/core/toxfile.h" namespace Ui { class FileTransferWidget; } class QVariantAnimation; class QPushButton; class FileTransferWidget : public QWidget { Q_OBJECT public: explicit FileTransferWidget(QWidget* parent, ToxFile file); virtual ~FileTransferWidget(); bool isActive() const; static QString getHumanReadableSize(qint64 size); void onFileTransferUpdate(ToxFile file); protected: void updateWidgetColor(ToxFile const& file); void updateWidgetText(ToxFile const& file); void updateFileProgress(ToxFile const& file); void updateSignals(ToxFile const& file); void updatePreview(ToxFile const& file); void setupButtons(ToxFile const& file); void handleButton(QPushButton* btn); void showPreview(const QString& filename); void acceptTransfer(const QString& filepath); void setBackgroundColor(const QColor& c, bool whiteFont); void setButtonColor(const QColor& c); bool drawButtonAreaNeeded() const; virtual void paintEvent(QPaintEvent*) final override; private slots: void onLeftButtonClicked(); void onRightButtonClicked(); void onPreviewButtonClicked(); private: static QPixmap scaleCropIntoSquare(const QPixmap& source, int targetSize); static int getExifOrientation(const char* data, const int size); static void applyTransformation(const int oritentation, QImage& image); static bool tryRemoveFile(const QString &filepath); void updateWidget(ToxFile const& file); private: Ui::FileTransferWidget* ui; ToxFileProgress fileProgress; ToxFile fileInfo; QVariantAnimation* backgroundColorAnimation = nullptr; QVariantAnimation* buttonColorAnimation = nullptr; QColor backgroundColor; QColor buttonColor; QColor buttonBackgroundColor; bool active; ToxFile::FileStatus lastStatus = ToxFile::INITIALIZING; enum class ExifOrientation { /* do not change values, this is exif spec * * name corresponds to where the 0 row and 0 column is in form row-column * i.e. entry 5 here means that the 0'th row corresponds to the left side of the scene and * the 0'th column corresponds to the top of the captured scene. This means that the image * needs to be mirrored and rotated to be displayed. */ TopLeft = 1, TopRight = 2, BottomRight = 3, BottomLeft = 4, LeftTop = 5, RightTop = 6, RightBottom = 7, LeftBottom = 8 }; }; #endif // FILETRANSFERWIDGET_H qTox/src/chatlog/content/filetransferwidget.ui000066400000000000000000000342631415623743500221440ustar00rootroot00000000000000 FileTransferWidget 0 0 615 243 Form 0 0 0 0 0 0 0 QFrame::NoFrame QFrame::Plain 0 0 0 0 0 0 4 2 0 6 0 Qt::Vertical 20 40 QLayout::SetDefaultConstraint 4 2 4 2 6 0 Qt::Vertical 1 20 Qt::Vertical QSizePolicy::Preferred 1 20 0 0 Filename Qt::Vertical 1 21 16777215 12 24 Qt::Horizontal false 0 0 0 0 0 0 0 10Mb Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 0kb/s Qt::AlignCenter 0 0 ETA:10:10 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 60 60 60 60 PointingHandCursor QPushButton{ border: 2px solid white } :themes/default/fileTransferInstance/no.svg:themes/default/fileTransferInstance/no.svg 32 18 true Qt::Vertical 20 40 65 32 65 32 0 0 0 0 0 0 0 32 32 32 32 PointingHandCursor :themes/default/fileTransferInstance/no.svg:themes/default/fileTransferInstance/no.svg 14 14 0 0 32 32 32 32 PointingHandCursor :themes/default/fileTransferInstance/no.svg:themes/default/fileTransferInstance/no.svg 14 14 CroppingLabel QLabel
src/widget/tool/croppinglabel.h
previewButton
qTox/src/chatlog/content/image.cpp000066400000000000000000000030121415623743500174670ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "image.h" #include "../pixmapcache.h" #include Image::Image(QSize Size, const QString& filename) : size(Size) { pmap = PixmapCache::getInstance().get(filename, size); } QRectF Image::boundingRect() const { return QRectF(QPointF(-size.width() / 2.0, -size.height() / 2.0), size); } qreal Image::getAscent() const { return 0.0; } void Image::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { painter->setClipRect(boundingRect()); painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->translate(-size.width() / 2.0, -size.height() / 2.0); painter->drawPixmap(0, 0, pmap); Q_UNUSED(option) Q_UNUSED(widget) } void Image::setWidth(qreal width) { Q_UNUSED(width) } qTox/src/chatlog/content/image.h000066400000000000000000000024161415623743500171430ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IMAGE_H #define IMAGE_H #include "../chatlinecontent.h" #include class Image : public ChatLineContent { public: Image(QSize size, const QString& filename); virtual QRectF boundingRect() const override; virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; virtual void setWidth(qreal width) override; virtual qreal getAscent() const override; private: QSize size; QPixmap pmap; }; #endif // IMAGE_H qTox/src/chatlog/content/notificationicon.cpp000066400000000000000000000050321415623743500217500ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "notificationicon.h" #include "../pixmapcache.h" #include "src/widget/style.h" #include #include #include NotificationIcon::NotificationIcon(QSize Size) : size(Size) { pmap = PixmapCache::getInstance().get(Style::getImagePath("chatArea/typing.svg"), size); updateTimer = new QTimer(this); updateTimer->setInterval(1000 / 30); updateTimer->setSingleShot(false); updateTimer->start(); connect(updateTimer, &QTimer::timeout, this, &NotificationIcon::updateGradient); } QRectF NotificationIcon::boundingRect() const { return QRectF(QPointF(-size.width() / 2.0, -size.height() / 2.0), size); } void NotificationIcon::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { painter->setClipRect(boundingRect()); painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->translate(-size.width() / 2.0, -size.height() / 2.0); painter->fillRect(QRect(0, 0, size.width(), size.height()), grad); painter->drawPixmap(0, 0, size.width(), size.height(), pmap); Q_UNUSED(option) Q_UNUSED(widget) } void NotificationIcon::setWidth(qreal width) { Q_UNUSED(width) } qreal NotificationIcon::getAscent() const { return 3.0; } void NotificationIcon::updateGradient() { alpha += 0.01; if (alpha + dotWidth >= 1.0) alpha = 0.0; grad = QLinearGradient(QPointF(-0.5 * size.width(), 0), QPointF(3.0 / 2.0 * size.width(), 0)); grad.setColorAt(0, Qt::lightGray); grad.setColorAt(qMax(0.0, alpha - dotWidth), Qt::lightGray); grad.setColorAt(alpha, Qt::black); grad.setColorAt(qMin(1.0, alpha + dotWidth), Qt::lightGray); grad.setColorAt(1, Qt::lightGray); if (scene() && isVisible()) scene()->invalidate(sceneBoundingRect()); } qTox/src/chatlog/content/notificationicon.h000066400000000000000000000030061415623743500214140ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef NOTIFICATIONICON_H #define NOTIFICATIONICON_H #include "../chatlinecontent.h" #include #include class QTimer; class NotificationIcon : public ChatLineContent { Q_OBJECT public: explicit NotificationIcon(QSize size); virtual QRectF boundingRect() const override; virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; virtual void setWidth(qreal width) override; virtual qreal getAscent() const override; private slots: void updateGradient(); private: QSize size; QPixmap pmap; QLinearGradient grad; QTimer* updateTimer = nullptr; qreal dotWidth = 0.2; qreal alpha = 0.0; }; #endif // NOTIFICATIONICON_H qTox/src/chatlog/content/spinner.cpp000066400000000000000000000052071415623743500200730ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "spinner.h" #include "../pixmapcache.h" #include #include #include #include #include Spinner::Spinner(const QString& img, QSize Size, qreal speed) : size(Size) , rotSpeed(speed) { pmap = PixmapCache::getInstance().get(img, size); timer.setInterval(1000 / 30); // 30Hz timer.setSingleShot(false); blendAnimation = new QVariantAnimation(this); blendAnimation->setStartValue(0.0); blendAnimation->setEndValue(1.0); blendAnimation->setDuration(350); blendAnimation->setEasingCurve(QEasingCurve::InCubic); blendAnimation->start(QAbstractAnimation::DeleteWhenStopped); connect(blendAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) { alpha = val.toDouble(); }); QObject::connect(&timer, &QTimer::timeout, this, &Spinner::timeout); } QRectF Spinner::boundingRect() const { return QRectF(QPointF(-size.width() / 2.0, -size.height() / 2.0), size); } void Spinner::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { painter->setClipRect(boundingRect()); QTransform trans = QTransform() .rotate(QTime::currentTime().msecsSinceStartOfDay() / 1000.0 * rotSpeed) .translate(-size.width() / 2.0, -size.height() / 2.0); painter->setOpacity(alpha); painter->setTransform(trans, true); painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->drawPixmap(0, 0, pmap); Q_UNUSED(option) Q_UNUSED(widget) } void Spinner::setWidth(qreal width) { Q_UNUSED(width) } void Spinner::visibilityChanged(bool visible) { if (visible) timer.start(); else timer.stop(); } qreal Spinner::getAscent() const { return 0.0; } void Spinner::timeout() { if (scene()) scene()->invalidate(sceneBoundingRect()); } qTox/src/chatlog/content/spinner.h000066400000000000000000000030571415623743500175410ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SPINNER_H #define SPINNER_H #include "../chatlinecontent.h" #include #include #include class QVariantAnimation; class Spinner : public ChatLineContent { Q_OBJECT public: Spinner(const QString& img, QSize size, qreal speed); virtual QRectF boundingRect() const override; virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; virtual void setWidth(qreal width) override; virtual void visibilityChanged(bool visible) override; virtual qreal getAscent() const override; private slots: void timeout(); private: QSize size; QPixmap pmap; qreal rotSpeed; QTimer timer; qreal alpha = 0.0; QVariantAnimation* blendAnimation; }; #endif // SPINNER_H qTox/src/chatlog/content/text.cpp000066400000000000000000000260631415623743500174040ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "text.h" #include "../documentcache.h" #include #include #include #include #include #include #include #include #include #include Text::Text(const QString& txt, const QFont& font, bool enableElide, const QString& rwText, const TextType& type, const QColor& custom) : rawText(rwText) , elide(enableElide) , defFont(font) , defStyleSheet(Style::getStylesheet(QStringLiteral("chatArea/innerStyle.css"), font)) , textType(type) , customColor(custom) { color = textColor(); setText(txt); setAcceptedMouseButtons(Qt::LeftButton); setAcceptHoverEvents(true); } Text::~Text() { if (doc) DocumentCache::getInstance().push(doc); } void Text::setText(const QString& txt) { text = txt; dirty = true; } void Text::selectText(const QString& txt, const std::pair& point) { regenerate(); if (!doc) { return; } auto cursor = doc->find(txt, point.first); selectText(cursor, point); } void Text::selectText(const QRegularExpression &exp, const std::pair& point) { regenerate(); if (!doc) { return; } auto cursor = doc->find(exp, point.first); selectText(cursor, point); } void Text::deselectText() { dirty = true; regenerate(); update(); } void Text::setWidth(qreal w) { width = w; dirty = true; regenerate(); } void Text::selectionMouseMove(QPointF scenePos) { if (!doc) return; int cur = cursorFromPos(scenePos); if (cur >= 0) { selectionEnd = cur; selectedText = extractSanitizedText(getSelectionStart(), getSelectionEnd()); } update(); } void Text::selectionStarted(QPointF scenePos) { int cur = cursorFromPos(scenePos); if (cur >= 0) { selectionEnd = cur; selectionAnchor = cur; } } void Text::selectionCleared() { selectedText.clear(); selectedText.squeeze(); // Do not reset selectionAnchor! selectionEnd = -1; update(); } void Text::selectionDoubleClick(QPointF scenePos) { if (!doc) return; int cur = cursorFromPos(scenePos); if (cur >= 0) { QTextCursor cursor(doc); cursor.setPosition(cur); cursor.select(QTextCursor::WordUnderCursor); selectionAnchor = cursor.selectionStart(); selectionEnd = cursor.selectionEnd(); selectedText = extractSanitizedText(getSelectionStart(), getSelectionEnd()); } update(); } void Text::selectionTripleClick(QPointF scenePos) { if (!doc) return; int cur = cursorFromPos(scenePos); if (cur >= 0) { QTextCursor cursor(doc); cursor.setPosition(cur); cursor.select(QTextCursor::BlockUnderCursor); selectionAnchor = cursor.selectionStart(); selectionEnd = cursor.selectionEnd(); if (cursor.block().isValid() && cursor.block().blockNumber() != 0) selectionAnchor++; selectedText = extractSanitizedText(getSelectionStart(), getSelectionEnd()); } update(); } void Text::selectionFocusChanged(bool focusIn) { selectionHasFocus = focusIn; update(); } bool Text::isOverSelection(QPointF scenePos) const { int cur = cursorFromPos(scenePos); if (getSelectionStart() < cur && getSelectionEnd() >= cur) return true; return false; } QString Text::getSelectedText() const { return selectedText; } void Text::fontChanged(const QFont& font) { defFont = font; } QRectF Text::boundingRect() const { return QRectF(QPointF(0, 0), size); } void Text::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!doc) return; painter->setClipRect(boundingRect()); // draw selection QAbstractTextDocumentLayout::PaintContext ctx; QAbstractTextDocumentLayout::Selection sel; if (hasSelection()) { sel.cursor = QTextCursor(doc); sel.cursor.setPosition(getSelectionStart()); sel.cursor.setPosition(getSelectionEnd(), QTextCursor::KeepAnchor); } const QColor selectionColor = Style::getColor(Style::SelectText); sel.format.setBackground(selectionColor.lighter(selectionHasFocus ? 100 : 160)); sel.format.setForeground(selectionHasFocus ? Qt::white : Qt::black); ctx.selections.append(sel); ctx.palette.setColor(QPalette::Text, color); // draw text doc->documentLayout()->draw(painter, ctx); } void Text::visibilityChanged(bool visible) { keepInMemory = visible; regenerate(); update(); } void Text::reloadTheme() { defStyleSheet = Style::getStylesheet(QStringLiteral("chatArea/innerStyle.css"), defFont); color = textColor(); dirty = true; regenerate(); update(); } qreal Text::getAscent() const { return ascent; } void Text::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (event->button() == Qt::LeftButton) event->accept(); // grabber } void Text::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (!doc) return; QString anchor = doc->documentLayout()->anchorAt(event->pos()); // open anchor in browser if (!anchor.isEmpty()) QDesktopServices::openUrl(anchor); } void Text::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { if (!doc) return; QString anchor = doc->documentLayout()->anchorAt(event->pos()); if (anchor.isEmpty()) setCursor(Qt::IBeamCursor); else setCursor(Qt::PointingHandCursor); // tooltip setToolTip(extractImgTooltip(cursorFromPos(event->scenePos(), false))); } QString Text::getText() const { return rawText; } /** * @brief Extracts the target of a link from the text at a given coordinate * @param scenePos Position in scene coordinates * @return The link target URL, or an empty string if there is no link there */ QString Text::getLinkAt(QPointF scenePos) const { QTextCursor cursor(doc); cursor.setPosition(cursorFromPos(scenePos)); return cursor.charFormat().anchorHref(); } void Text::regenerate() { if (!doc) { doc = DocumentCache::getInstance().pop(); dirty = true; } if (dirty) { doc->setDefaultFont(defFont); if (elide) { QFontMetrics metrics = QFontMetrics(defFont); QString elidedText = metrics.elidedText(text, Qt::ElideRight, qRound(width)); doc->setPlainText(elidedText); } else { doc->setDefaultStyleSheet(defStyleSheet); doc->setHtml(text); } // wrap mode QTextOption opt; opt.setWrapMode(elide ? QTextOption::NoWrap : QTextOption::WrapAtWordBoundaryOrAnywhere); doc->setDefaultTextOption(opt); // width doc->setTextWidth(width); doc->documentLayout()->update(); // update ascent if (doc->firstBlock().layout()->lineCount() > 0) ascent = doc->firstBlock().layout()->lineAt(0).ascent(); // let the scene know about our change in size if (size != idealSize()) prepareGeometryChange(); // get the new width and height size = idealSize(); dirty = false; } // if we are not visible -> free mem if (!keepInMemory) freeResources(); } void Text::freeResources() { DocumentCache::getInstance().push(doc); doc = nullptr; } QSizeF Text::idealSize() { if (doc) return doc->size(); return size; } int Text::cursorFromPos(QPointF scenePos, bool fuzzy) const { if (doc) return doc->documentLayout()->hitTest(mapFromScene(scenePos), fuzzy ? Qt::FuzzyHit : Qt::ExactHit); return -1; } int Text::getSelectionEnd() const { return qMax(selectionAnchor, selectionEnd); } int Text::getSelectionStart() const { return qMin(selectionAnchor, selectionEnd); } bool Text::hasSelection() const { return selectionEnd >= 0; } QString Text::extractSanitizedText(int from, int to) const { if (!doc) return ""; QString txt; QTextBlock begin = doc->findBlock(from); QTextBlock end = doc->findBlock(to); for (QTextBlock block = begin; block != end.next() && block.isValid(); block = block.next()) { for (QTextBlock::Iterator itr = block.begin(); itr != block.end(); ++itr) { int pos = itr.fragment().position(); // fragment position -> position of the first // character in the fragment if (itr.fragment().charFormat().isImageFormat()) { QTextImageFormat imgFmt = itr.fragment().charFormat().toImageFormat(); QString key = imgFmt.name(); // img key (eg. key::D for :D) QString rune = key.mid(4); if (pos >= from && pos < to) { txt += rune; ++pos; } } else { for (QChar c : itr.fragment().text()) { if (pos >= from && pos < to) txt += c; ++pos; } } } txt += '\n'; } txt.chop(1); return txt; } QString Text::extractImgTooltip(int pos) const { for (QTextBlock::Iterator itr = doc->firstBlock().begin(); itr != doc->firstBlock().end(); ++itr) { if (itr.fragment().contains(pos) && itr.fragment().charFormat().isImageFormat()) { QTextImageFormat imgFmt = itr.fragment().charFormat().toImageFormat(); return imgFmt.toolTip(); } } return QString(); } void Text::selectText(QTextCursor& cursor, const std::pair& point) { if (!cursor.isNull()) { cursor.beginEditBlock(); cursor.setPosition(point.first); cursor.setPosition(point.first + point.second, QTextCursor::KeepAnchor); cursor.endEditBlock(); QTextCharFormat format; format.setBackground(QBrush(Style::getColor(Style::SearchHighlighted))); cursor.mergeCharFormat(format); regenerate(); update(); } } QColor Text::textColor() const { QColor c = Style::getColor(Style::MainText); if (textType == ACTION) { c = Style::getColor(Style::Action); } else if (textType == CUSTOM) { c = customColor; } return c; } qTox/src/chatlog/content/text.h000066400000000000000000000071651415623743500170530ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TEXT_H #define TEXT_H #include "../chatlinecontent.h" #include "src/widget/style.h" #include class QTextDocument; class Text : public ChatLineContent { Q_OBJECT public: enum TextType { NORMAL, ACTION, CUSTOM }; Text(const QString& txt = "", const QFont& font = QFont(), bool enableElide = false, const QString& rawText = QString(), const TextType& type = NORMAL, const QColor& custom = Style::getColor(Style::MainText)); virtual ~Text(); void setText(const QString& txt); void selectText(const QString& txt, const std::pair& point); void selectText(const QRegularExpression& exp, const std::pair& point); void deselectText(); virtual void setWidth(qreal width) final; virtual void selectionMouseMove(QPointF scenePos) final; virtual void selectionStarted(QPointF scenePos) final; virtual void selectionCleared() final; virtual void selectionDoubleClick(QPointF scenePos) final; virtual void selectionTripleClick(QPointF scenePos) final; virtual void selectionFocusChanged(bool focusIn) final; virtual bool isOverSelection(QPointF scenePos) const final; virtual QString getSelectedText() const final; virtual void fontChanged(const QFont& font) final; virtual QRectF boundingRect() const final; virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) final; virtual void visibilityChanged(bool keepInMemory) final; virtual void reloadTheme() final override; virtual qreal getAscent() const final; virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) final override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) final override; void hoverMoveEvent(QGraphicsSceneHoverEvent* event) final override; virtual QString getText() const final; QString getLinkAt(QPointF scenePos) const; protected: // dynamic resource management void regenerate(); void freeResources(); virtual QSizeF idealSize(); int cursorFromPos(QPointF scenePos, bool fuzzy = true) const; int getSelectionEnd() const; int getSelectionStart() const; bool hasSelection() const; QString extractSanitizedText(int from, int to) const; QString extractImgTooltip(int pos) const; QTextDocument* doc = nullptr; QSizeF size; qreal width = 0.0; private: void selectText(QTextCursor& cursor, const std::pair& point); QColor textColor() const; QString text; QString rawText; QString selectedText; bool keepInMemory = false; bool elide = false; bool dirty = false; bool selectionHasFocus = true; int selectionEnd = -1; int selectionAnchor = -1; qreal ascent = 0.0; QFont defFont; QString defStyleSheet; TextType textType; QColor color; QColor customColor; }; #endif // TEXT_H qTox/src/chatlog/content/timestamp.cpp000066400000000000000000000022331415623743500204140ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "timestamp.h" Timestamp::Timestamp(const QDateTime& time, const QString& format, const QFont& font) : Text(time.toString(format), font, false, time.toString(format)) { this->time = time; } QDateTime Timestamp::getTime() { return time; } QSizeF Timestamp::idealSize() { if (doc) { return QSizeF(qMin(doc->idealWidth(), width), doc->size().height()); } return size; } qTox/src/chatlog/content/timestamp.h000066400000000000000000000021731415623743500200640ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TIMESTAMP_H #define TIMESTAMP_H #include "text.h" #include #include class QTextDocument; class Timestamp : public Text { Q_OBJECT public: Timestamp(const QDateTime& time, const QString& format, const QFont& font); QDateTime getTime(); protected: QSizeF idealSize(); private: QDateTime time; }; #endif // TIMESTAMP_H qTox/src/chatlog/createChatMessage000066400000000000000000000000001415623743500175140ustar00rootroot00000000000000qTox/src/chatlog/customtextdocument.cpp000066400000000000000000000032761415623743500207250ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "customtextdocument.h" #include "src/persistence/settings.h" #include "src/persistence/smileypack.h" #include "src/widget/style.h" #include #include #include CustomTextDocument::CustomTextDocument(QObject* parent) : QTextDocument(parent) { setUndoRedoEnabled(false); setUseDesignMetrics(false); } QVariant CustomTextDocument::loadResource(int type, const QUrl& name) { if (type == QTextDocument::ImageResource && name.scheme() == "key") { QSize size = QSize(Settings::getInstance().getEmojiFontPointSize(), Settings::getInstance().getEmojiFontPointSize()); QString fileName = QUrl::fromPercentEncoding(name.toEncoded()).mid(4).toHtmlEscaped(); std::shared_ptr icon = SmileyPack::getInstance().getAsIcon(fileName); emoticonIcons.append(icon); return icon->pixmap(size); } return QTextDocument::loadResource(type, name); } qTox/src/chatlog/customtextdocument.h000066400000000000000000000022641415623743500203660ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CUSTOMTEXTDOCUMENT_H #define CUSTOMTEXTDOCUMENT_H #include #include #include class QIcon; class CustomTextDocument : public QTextDocument { Q_OBJECT public: explicit CustomTextDocument(QObject* parent = nullptr); protected: virtual QVariant loadResource(int type, const QUrl& name); private: QList> emoticonIcons; }; #endif // CUSTOMTEXTDOCUMENT_H qTox/src/chatlog/documentcache.cpp000066400000000000000000000025101415623743500175370ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "documentcache.h" #include "customtextdocument.h" DocumentCache::~DocumentCache() { while (!documents.isEmpty()) delete documents.pop(); } QTextDocument* DocumentCache::pop() { if (documents.empty()) documents.push(new CustomTextDocument); return documents.pop(); } void DocumentCache::push(QTextDocument* doc) { if (doc) { doc->clear(); documents.push(doc); } } /** * @brief Returns the singleton instance. */ DocumentCache& DocumentCache::getInstance() { static DocumentCache instance; return instance; } qTox/src/chatlog/documentcache.h000066400000000000000000000023261415623743500172110ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef DOCUMENTCACHE_H #define DOCUMENTCACHE_H #include class QTextDocument; class DocumentCache { public: static DocumentCache& getInstance(); QTextDocument* pop(); void push(QTextDocument* doc); private: DocumentCache() = default; ~DocumentCache(); DocumentCache(DocumentCache&) = delete; DocumentCache& operator=(const DocumentCache&) = delete; private: QStack documents; }; #endif // DOCUMENTCACHE_H qTox/src/chatlog/pixmapcache.cpp000066400000000000000000000023371415623743500172260ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "pixmapcache.h" QPixmap PixmapCache::get(const QString& filename, QSize size) { auto itr = cache.find(filename); if (itr == cache.end()) { QIcon icon; icon.addFile(filename); cache.insert(filename, icon); return icon.pixmap(size); } return itr.value().pixmap(size); } /** * @brief Returns the singleton instance. */ PixmapCache& PixmapCache::getInstance() { static PixmapCache instance; return instance; } qTox/src/chatlog/pixmapcache.h000066400000000000000000000022511415623743500166660ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ICONCACHE_H #define ICONCACHE_H #include #include #include class PixmapCache { public: QPixmap get(const QString& filename, QSize size); static PixmapCache& getInstance(); protected: PixmapCache() { } PixmapCache(PixmapCache&) = delete; PixmapCache& operator=(const PixmapCache&) = delete; private: QHash cache; }; #endif // ICONCACHE_H qTox/src/chatlog/textformatter.cpp000066400000000000000000000247121415623743500176550ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "textformatter.h" #include #include // clang-format off // Note: escaping of '\' is only needed because QStringLiteral is broken by linebreak static const QString SINGLE_SIGN_PATTERN = QStringLiteral("(?<=^|\\s)" "[%1]" "(?!\\s)" "([^%1\\n]+?)" "(? REGEX_TO_WRAPPER[] { REGEXP_WRAPPER_PAIR(SINGLE_SLASH_PATTERN, "%1"), REGEXP_WRAPPER_PAIR(SINGLE_SIGN_PATTERN.arg('*'), "%1"), REGEXP_WRAPPER_PAIR(SINGLE_SIGN_PATTERN.arg('_'), "%1"), REGEXP_WRAPPER_PAIR(SINGLE_SIGN_PATTERN.arg('~'), "%1"), REGEXP_WRAPPER_PAIR(SINGLE_SIGN_PATTERN.arg('`'), "%1"), REGEXP_WRAPPER_PAIR(DOUBLE_SIGN_PATTERN.arg('*'), "%1"), REGEXP_WRAPPER_PAIR(DOUBLE_SIGN_PATTERN.arg('/'), "%1"), REGEXP_WRAPPER_PAIR(DOUBLE_SIGN_PATTERN.arg('_'), "%1"), REGEXP_WRAPPER_PAIR(DOUBLE_SIGN_PATTERN.arg('~'), "%1"), REGEXP_WRAPPER_PAIR(MULTILINE_CODE, "%1"), }; #undef REGEXP_WRAPPER_PAIR static const QString HREF_WRAPPER = QStringLiteral(R"(
%1)"); static const QString WWW_WRAPPER = QStringLiteral(R"(%1)"); static const QVector WWW_WORD_PATTERN = { QRegularExpression(QStringLiteral(R"((?<=^|\s)\S*((www\.)\S+))")) }; static const QVector URI_WORD_PATTERNS = { // Note: This does not match only strictly valid URLs, but we broaden search to any string following scheme to // allow UTF-8 "IRI"s instead of ASCII-only URLs QRegularExpression(QStringLiteral(R"((?<=^|\s)\S*((((http[s]?)|ftp)://)\S+))")), QRegularExpression(QStringLiteral(R"((?<=^|\s)\S*((file|smb)://([\S| ]*)))")), QRegularExpression(QStringLiteral(R"((?<=^|\s)\S*(tox:[a-zA-Z\d]{76}))")), QRegularExpression(QStringLiteral(R"((?<=^|\s)\S*(mailto:\S+@\S+\.\S+))")), QRegularExpression(QStringLiteral(R"((?<=^|\s)\S*(magnet:[?]((xt(.\d)?=urn:)|(mt=)|(kt=)|(tr=)|(dn=)|(xl=)|(xs=)|(as=)|(x.))[\S| ]+))")), }; // clang-format on struct MatchingUri { bool valid{false}; int length{0}; }; // pairs of characters that are ignored when surrounding a URI static const QPair URI_WRAPPING_CHARS[] = { {QString("("), QString(")")}, {QString("["), QString("]")}, {QString("""), QString(""")}, {QString("'"), QString("'")} }; // characters which are ignored from the end of URI static const QChar URI_ENDING_CHARS[] = { QChar::fromLatin1('?'), QChar::fromLatin1('.'), QChar::fromLatin1('!'), QChar::fromLatin1(':'), QChar::fromLatin1(',') }; /** * @brief Strips wrapping characters and ending punctuation from URI * @param QRegularExpressionMatch of a word containing a URI * @return MatchingUri containing info on the stripped URI */ MatchingUri stripSurroundingChars(const QStringRef wrappedUri, const int startOfBareUri) { bool matchFound; int curValidationStartPos = 0; int curValidationEndPos = wrappedUri.length(); do { matchFound = false; for (auto const& surroundChars : URI_WRAPPING_CHARS) { const int openingCharLength = surroundChars.first.length(); const int closingCharLength = surroundChars.second.length(); if (surroundChars.first == wrappedUri.mid(curValidationStartPos, openingCharLength) && surroundChars.second == wrappedUri.mid(curValidationEndPos - closingCharLength, closingCharLength)) { curValidationStartPos += openingCharLength; curValidationEndPos -= closingCharLength; matchFound = true; break; } } for (QChar const endChar : URI_ENDING_CHARS) { const int charLength = 1; if (endChar == wrappedUri.at(curValidationEndPos - charLength)) { curValidationEndPos -= charLength; matchFound = true; break; } } } while (matchFound); MatchingUri strippedMatch; if (startOfBareUri != curValidationStartPos) { strippedMatch.valid = false; } else { strippedMatch.valid = true; strippedMatch.length = curValidationEndPos - startOfBareUri; } return strippedMatch; } /** * @brief Wrap substrings matching "patterns" with "wrapper" in "message" * @param message Where search for patterns * @param patterns Array of regex patterns to find strings to wrap * @param wrapper Surrounds the matched strings * @note done separately from URI since the link must have a scheme added to be valid * @return Copy of message with highlighted URLs */ QString highlight(const QString& message, const QVector& patterns, const QString& wrapper) { QString result = message; for (const QRegularExpression& exp : patterns) { const int startLength = result.length(); int offset = 0; QRegularExpressionMatchIterator iter = exp.globalMatch(result); while (iter.hasNext()) { const QRegularExpressionMatch match = iter.next(); const int uriWithWrapMatch{0}; const int uriWithoutWrapMatch{1}; MatchingUri matchUri = stripSurroundingChars(match.capturedRef(uriWithWrapMatch), match.capturedStart(uriWithoutWrapMatch) - match.capturedStart(uriWithWrapMatch)); if (!matchUri.valid) { continue; } const QString wrappedURL = wrapper.arg(match.captured(uriWithoutWrapMatch).left(matchUri.length)); result.replace(match.capturedStart(uriWithoutWrapMatch) + offset, matchUri.length, wrappedURL); offset = result.length() - startLength; } } return result; } /** * @brief Highlights URLs within passed message string * @param message Where search for URLs * @return Copy of message with highlighted URLs */ QString highlightURI(const QString& message) { QString result = highlight(message, URI_WORD_PATTERNS, HREF_WRAPPER); result = highlight(result, WWW_WORD_PATTERN, WWW_WRAPPER); return result; } /** * @brief Checks HTML tags intersection while applying styles to the message text * @param str Checking string * @return True, if tag intersection detected */ static bool isTagIntersection(const QString& str) { const QRegularExpression TAG_PATTERN("(?<=<)/?[a-zA-Z0-9]+(?=>)"); int openingTagCount = 0; int closingTagCount = 0; QRegularExpressionMatchIterator iter = TAG_PATTERN.globalMatch(str); while (iter.hasNext()) { iter.next().captured()[0] == '/' ? ++closingTagCount : ++openingTagCount; } return openingTagCount != closingTagCount; } /** * @brief Applies markdown to passed message string * @param message Formatting string * @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting * string * @return Copy of message with markdown applied */ QString applyMarkdown(const QString& message, bool showFormattingSymbols) { QString result = message; for (const QPair& pair : REGEX_TO_WRAPPER) { QRegularExpressionMatchIterator iter = pair.first.globalMatch(result); int offset = 0; while (iter.hasNext()) { const QRegularExpressionMatch match = iter.next(); QString captured = match.captured(!showFormattingSymbols); if (isTagIntersection(captured)) { continue; } const int length = match.capturedLength(); const QString wrappedText = pair.second.arg(captured); const int startPos = match.capturedStart() + offset; result.replace(startPos, length, wrappedText); offset += wrappedText.length() - length; } } return result; } qTox/src/chatlog/textformatter.h000066400000000000000000000017331415623743500173200ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TEXTFORMATTER_H #define TEXTFORMATTER_H #include QString highlightURI(const QString& message); QString applyMarkdown(const QString& message, bool showFormattingSymbols); #endif // TEXTFORMATTER_H qTox/src/chatlog/toxfileprogress.cpp000066400000000000000000000050131415623743500201750ustar00rootroot00000000000000/* Copyright © 2018-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toxfileprogress.h" #include "src/core/toxfile.h" bool ToxFileProgress::needsUpdate() const { QTime now = QTime::currentTime(); qint64 dt = lastTick.msecsTo(now); // ms if (dt < 1000) { return false; } return true; } void ToxFileProgress::addSample(ToxFile const& file) { QTime now = QTime::currentTime(); qint64 dt = lastTick.msecsTo(now); // ms if (dt < 1000) { return; } // ETA, speed qreal deltaSecs = dt / 1000.0; // (can't use ::abs or ::max on unsigned types substraction, they'd just overflow) quint64 deltaBytes = file.bytesSent > lastBytesSent ? file.bytesSent - lastBytesSent : lastBytesSent - file.bytesSent; qreal bytesPerSec = static_cast(static_cast(deltaBytes) / deltaSecs); // Update member variables meanIndex = meanIndex % TRANSFER_ROLLING_AVG_COUNT; meanData[meanIndex++] = bytesPerSec; double meanBytesPerSec = 0.0; for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i) { meanBytesPerSec += meanData[i]; } meanBytesPerSec /= static_cast(TRANSFER_ROLLING_AVG_COUNT); lastTick = now; progress = static_cast(file.bytesSent) / static_cast(file.filesize); speedBytesPerSecond = meanBytesPerSec; timeLeftSeconds = (file.filesize - file.bytesSent) / getSpeed(); lastBytesSent = file.bytesSent; } void ToxFileProgress::resetSpeed() { meanIndex = 0; for (auto& item : meanData) { item = 0; } } double ToxFileProgress::getProgress() const { return progress; } double ToxFileProgress::getSpeed() const { return speedBytesPerSecond; } double ToxFileProgress::getTimeLeftSeconds() const { return timeLeftSeconds; } qTox/src/chatlog/toxfileprogress.h000066400000000000000000000026231415623743500176460ustar00rootroot00000000000000/* Copyright © 2018-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXFILEPROGRESS_H #define TOXFILEPROGRESS_H #include struct ToxFile; class ToxFileProgress { public: bool needsUpdate() const; void addSample(ToxFile const& file); void resetSpeed(); double getProgress() const; double getSpeed() const; double getTimeLeftSeconds() const; private: uint64_t lastBytesSent = 0; static const uint8_t TRANSFER_ROLLING_AVG_COUNT = 4; uint8_t meanIndex = 0; double meanData[TRANSFER_ROLLING_AVG_COUNT] = {0.0}; QTime lastTick = QTime::currentTime(); double speedBytesPerSecond; double timeLeftSeconds; double progress; }; #endif // TOXFILEPROGRESS_H qTox/src/core/000077500000000000000000000000001415623743500135425ustar00rootroot00000000000000qTox/src/core/contactid.cpp000066400000000000000000000054321415623743500162220ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #include #include #include "src/core/contactid.h" /** * @brief The default constructor. Creates an empty id. */ ContactId::ContactId() : id() { } /** * @brief Constructs a ContactId from bytes. * @param rawId The bytes to construct the ContactId from. */ ContactId::ContactId(const QByteArray& rawId) { id = QByteArray(rawId); } /** * @brief Compares the equality of the ContactId. * @param other ContactId to compare. * @return True if both ContactId are equal, false otherwise. */ bool ContactId::operator==(const ContactId& other) const { return id == other.id; } /** * @brief Compares the inequality of the ContactId. * @param other ContactId to compare. * @return True if both ContactIds are not equal, false otherwise. */ bool ContactId::operator!=(const ContactId& other) const { return id != other.id; } /** * @brief Compares two ContactIds * @param other ContactId to compare. * @return True if this ContactIds is less than the other ContactId, false otherwise. */ bool ContactId::operator<(const ContactId& other) const { return id < other.id; } /** * @brief Converts the ContactId to a uppercase hex string. * @return QString containing the hex representation of the id */ QString ContactId::toString() const { return id.toHex().toUpper(); } /** * @brief Returns a pointer to the raw id data. * @return Pointer to the raw id data, which is exactly `ContactId::getPkSize()` * bytes long. Returns a nullptr if the ContactId is empty. */ const uint8_t* ContactId::getData() const { if (id.isEmpty()) { return nullptr; } return reinterpret_cast(id.constData()); } /** * @brief Get a copy of the id * @return Copied id bytes */ QByteArray ContactId::getByteArray() const { return QByteArray(id); // TODO: Is a copy really necessary? } /** * @brief Checks if the ContactId contains a id. * @return True if there is a id, False otherwise. */ bool ContactId::isEmpty() const { return id.isEmpty(); } qTox/src/core/contactid.h000066400000000000000000000031701415623743500156640ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CONTACTID_H #define CONTACTID_H #include #include #include #include #include class ContactId { public: virtual ~ContactId() = default; ContactId& operator=(const ContactId& other) = default; ContactId& operator=(ContactId&& other) = default; bool operator==(const ContactId& other) const; bool operator!=(const ContactId& other) const; bool operator<(const ContactId& other) const; QString toString() const; QByteArray getByteArray() const; const uint8_t* getData() const; bool isEmpty() const; virtual int getSize() const = 0; protected: ContactId(); explicit ContactId(const QByteArray& rawId); QByteArray id; }; inline uint qHash(const ContactId& id) { return qHash(id.getByteArray()); } using ContactIdPtr = std::shared_ptr; #endif // CONTACTID_H qTox/src/core/core.cpp000066400000000000000000001357461415623743500152160ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "core.h" #include "corefile.h" #include "src/core/coreav.h" #include "src/core/dhtserver.h" #include "src/core/icoresettings.h" #include "src/core/toxlogger.h" #include "src/core/toxoptions.h" #include "src/core/toxstring.h" #include "src/model/groupinvite.h" #include "src/model/status.h" #include "src/net/bootstrapnodeupdater.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/util/strongtype.h" #include "src/util/compatiblerecursivemutex.h" #include #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #include #endif #include #include #include #include #include #include const QString Core::TOX_EXT = ".tox"; #define ASSERT_CORE_THREAD assert(QThread::currentThread() == coreThread.get()) namespace { bool LogConferenceTitleError(TOX_ERR_CONFERENCE_TITLE error) { switch (error) { case TOX_ERR_CONFERENCE_TITLE_OK: break; case TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND: qWarning() << "Conference title not found"; break; case TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH: qWarning() << "Invalid conference title length"; break; case TOX_ERR_CONFERENCE_TITLE_FAIL_SEND: qWarning() << "Failed to send title packet"; } return error; } bool parseToxErrBootstrap(Tox_Err_Bootstrap error) { switch(error) { case TOX_ERR_BOOTSTRAP_OK: return true; case TOX_ERR_BOOTSTRAP_NULL: qCritical() << "null argument when not expected"; return false; case TOX_ERR_BOOTSTRAP_BAD_HOST: qCritical() << "Could not resolve hostname, or invalid IP address"; return false; case TOX_ERR_BOOTSTRAP_BAD_PORT: qCritical() << "out of range port"; return false; default: qCritical() << "Unknown Tox_Err_bootstrap error code:" << error; return false; } } bool parseFriendSendMessageError(Tox_Err_Friend_Send_Message error) { switch (error) { case TOX_ERR_FRIEND_SEND_MESSAGE_OK: return true; case TOX_ERR_FRIEND_SEND_MESSAGE_NULL: qCritical() << "Send friend message passed an unexpected null argument"; return false; case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND: qCritical() << "Send friend message could not find friend"; return false; case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED: qCritical() << "Send friend message: friend is offline"; return false; case TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ: qCritical() << "Failed to allocate more message queue"; return false; case TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG: qCritical() << "Attemped to send message that's too long"; return false; case TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY: qCritical() << "Attempted to send an empty message"; return false; default: qCritical() << "Unknown friend send message error:" << static_cast(error); return false; } } bool parseConferenceSendMessageError(Tox_Err_Conference_Send_Message error) { switch (error) { case TOX_ERR_CONFERENCE_SEND_MESSAGE_OK: return true; case TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND: qCritical() << "Conference not found"; return false; case TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND: qCritical() << "Conference message failed to send"; return false; case TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION: qCritical() << "No connection"; return false; case TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG: qCritical() << "Message too long"; return false; default: qCritical() << "Unknown Tox_Err_Conference_Send_Message error:" << static_cast(error); return false; } } } // namespace Core::Core(QThread* coreThread) : tox(nullptr) , av(nullptr) , toxTimer{new QTimer{this}} , coreThread(coreThread) { assert(toxTimer); toxTimer->setSingleShot(true); connect(toxTimer, &QTimer::timeout, this, &Core::process); connect(coreThread, &QThread::finished, toxTimer, &QTimer::stop); } Core::~Core() { /* * First stop the thread to stop the timer and avoid Core emitting callbacks * into an already destructed CoreAV. */ coreThread->exit(0); coreThread->wait(); av.reset(); tox.reset(); } /** * @brief Registers all toxcore callbacks * @param tox Tox instance to register the callbacks on */ void Core::registerCallbacks(Tox* tox) { tox_callback_friend_request(tox, onFriendRequest); tox_callback_friend_message(tox, onFriendMessage); tox_callback_friend_name(tox, onFriendNameChange); tox_callback_friend_typing(tox, onFriendTypingChange); tox_callback_friend_status_message(tox, onStatusMessageChanged); tox_callback_friend_status(tox, onUserStatusChanged); tox_callback_friend_connection_status(tox, onConnectionStatusChanged); tox_callback_friend_read_receipt(tox, onReadReceiptCallback); tox_callback_conference_invite(tox, onGroupInvite); tox_callback_conference_message(tox, onGroupMessage); tox_callback_conference_peer_list_changed(tox, onGroupPeerListChange); tox_callback_conference_peer_name(tox, onGroupPeerNameChange); tox_callback_conference_title(tox, onGroupTitleChange); } /** * @brief Factory method for the Core object * @param savedata empty if new profile or saved data else * @param settings Settings specific to Core * @return nullptr or a Core object ready to start */ ToxCorePtr Core::makeToxCore(const QByteArray& savedata, const ICoreSettings* const settings, ToxCoreErrors* err) { QThread* thread = new QThread(); if (thread == nullptr) { qCritical() << "could not allocate Core thread"; return {}; } thread->setObjectName("qTox Core"); auto toxOptions = ToxOptions::makeToxOptions(savedata, settings); if (toxOptions == nullptr) { qCritical() << "could not allocate Tox Options data structure"; if (err) { *err = ToxCoreErrors::ERROR_ALLOC; } return {}; } ToxCorePtr core(new Core(thread)); if (core == nullptr) { if (err) { *err = ToxCoreErrors::ERROR_ALLOC; } return {}; } Tox_Err_New tox_err; core->tox = ToxPtr(tox_new(*toxOptions, &tox_err)); switch (tox_err) { case TOX_ERR_NEW_OK: break; case TOX_ERR_NEW_LOAD_BAD_FORMAT: qCritical() << "failed to parse Tox save data"; if (err) { *err = ToxCoreErrors::BAD_PROXY; } return {}; case TOX_ERR_NEW_PORT_ALLOC: if (toxOptions->getIPv6Enabled()) { toxOptions->setIPv6Enabled(false); core->tox = ToxPtr(tox_new(*toxOptions, &tox_err)); if (tox_err == TOX_ERR_NEW_OK) { qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery " "may not work properly."; break; } } qCritical() << "can't to bind the port"; if (err) { *err = ToxCoreErrors::FAILED_TO_START; } return {}; case TOX_ERR_NEW_PROXY_BAD_HOST: case TOX_ERR_NEW_PROXY_BAD_PORT: case TOX_ERR_NEW_PROXY_BAD_TYPE: qCritical() << "bad proxy, error code:" << tox_err; if (err) { *err = ToxCoreErrors::BAD_PROXY; } return {}; case TOX_ERR_NEW_PROXY_NOT_FOUND: qCritical() << "proxy not found"; if (err) { *err = ToxCoreErrors::BAD_PROXY; } return {}; case TOX_ERR_NEW_LOAD_ENCRYPTED: qCritical() << "attempted to load encrypted Tox save data"; if (err) { *err = ToxCoreErrors::INVALID_SAVE; } return {}; case TOX_ERR_NEW_MALLOC: qCritical() << "memory allocation failed"; if (err) { *err = ToxCoreErrors::ERROR_ALLOC; } return {}; case TOX_ERR_NEW_NULL: qCritical() << "a parameter was null"; if (err) { *err = ToxCoreErrors::FAILED_TO_START; } return {}; default: qCritical() << "Tox core failed to start, unknown error code:" << tox_err; if (err) { *err = ToxCoreErrors::FAILED_TO_START; } return {}; } // tox should be valid by now assert(core->tox != nullptr); // toxcore is successfully created, create toxav // TODO(sudden6): don't create CoreAv here, Core should be usable without CoreAV core->av = CoreAV::makeCoreAV(core->tox.get(), core->coreLoopLock); if (!core->av) { qCritical() << "Toxav failed to start"; if (err) { *err = ToxCoreErrors::FAILED_TO_START; } return {}; } // create CoreFile core->file = CoreFile::makeCoreFile(core.get(), core->tox.get(), core->coreLoopLock); if (!core->file) { qCritical() << "CoreFile failed to start"; if (err) { *err = ToxCoreErrors::FAILED_TO_START; } return {}; } registerCallbacks(core->tox.get()); // connect the thread with the Core connect(thread, &QThread::started, core.get(), &Core::onStarted); core->moveToThread(thread); // when leaving this function 'core' should be ready for it's start() action or // a nullptr return core; } void Core::onStarted() { ASSERT_CORE_THREAD; // One time initialization stuff QString name = getUsername(); if (!name.isEmpty()) { emit usernameSet(name); } QString msg = getStatusMessage(); if (!msg.isEmpty()) { emit statusMessageSet(msg); } ToxId id = getSelfId(); // Id comes from toxcore, must be valid assert(id.isValid()); emit idSet(id); loadFriends(); loadGroups(); process(); // starts its own timer av->start(); emit avReady(); } /** * @brief Starts toxcore and it's event loop, can be called from any thread */ void Core::start() { coreThread->start(); } /** * @brief Returns the global widget's Core instance */ Core* Core::getInstance() { return Nexus::getCore(); } const CoreAV* Core::getAv() const { return av.get(); } CoreAV* Core::getAv() { return av.get(); } CoreFile* Core::getCoreFile() const { return file.get(); } /* Using the now commented out statements in checkConnection(), I watched how * many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials, * 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks. * So I set the tolerance here at 25, and initial DCs should be very rare now. * This should be able to go to 50 or 100 without affecting legitimate disconnects' * downtime, but lets be conservative for now. Edit: now ~~40~~ 30. */ #define CORE_DISCONNECT_TOLERANCE 30 /** * @brief Processes toxcore events and ensure we stay connected, called by its own timer */ void Core::process() { QMutexLocker ml{&coreLoopLock}; ASSERT_CORE_THREAD; static int tolerance = CORE_DISCONNECT_TOLERANCE; tox_iterate(tox.get(), this); #ifdef DEBUG // we want to see the debug messages immediately fflush(stdout); #endif // TODO(sudden6): recheck if this is still necessary if (checkConnection()) { tolerance = CORE_DISCONNECT_TOLERANCE; } else if (!(--tolerance)) { bootstrapDht(); tolerance = 3 * CORE_DISCONNECT_TOLERANCE; } unsigned sleeptime = qMin(tox_iteration_interval(tox.get()), getCoreFile()->corefileIterationInterval()); toxTimer->start(sleeptime); } bool Core::checkConnection() { ASSERT_CORE_THREAD; static bool isConnected = false; bool toxConnected = tox_self_get_connection_status(tox.get()) != TOX_CONNECTION_NONE; if (toxConnected && !isConnected) { qDebug() << "Connected to the DHT"; emit connected(); } else if (!toxConnected && isConnected) { qDebug() << "Disconnected from the DHT"; emit disconnected(); } isConnected = toxConnected; return toxConnected; } /** * @brief Connects us to the Tox network */ void Core::bootstrapDht() { ASSERT_CORE_THREAD; QList bootstrapNodes = BootstrapNodeUpdater::loadDefaultBootstrapNodes(); int listSize = bootstrapNodes.size(); if (!listSize) { qWarning() << "no bootstrap list?!?"; return; } int i = 0; #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) static int j = QRandomGenerator::global()->generate() % listSize; #else static int j = qrand() % listSize; #endif // i think the more we bootstrap, the more we jitter because the more we overwrite nodes while (i < 2) { const DhtServer& dhtServer = bootstrapNodes[j % listSize]; QString dhtServerAddress = dhtServer.address.toLatin1(); QString port = QString::number(dhtServer.port); QString name = dhtServer.name; qDebug() << QString("Connecting to a bootstrap node..."); QByteArray address = dhtServer.address.toLatin1(); // TODO: constucting the pk via ToxId is a workaround ToxPk pk = ToxId{dhtServer.userId}.getPublicKey(); const uint8_t* pkPtr = pk.getData(); Tox_Err_Bootstrap error; tox_bootstrap(tox.get(), address.constData(), dhtServer.port, pkPtr, &error); parseToxErrBootstrap(error); tox_add_tcp_relay(tox.get(), address.constData(), dhtServer.port, pkPtr, &error); parseToxErrBootstrap(error); ++j; ++i; } } void Core::onFriendRequest(Tox*, const uint8_t* cFriendPk, const uint8_t* cMessage, size_t cMessageSize, void* core) { ToxPk friendPk(cFriendPk); QString requestMessage = ToxString(cMessage, cMessageSize).getQString(); emit static_cast(core)->friendRequestReceived(friendPk, requestMessage); } void Core::onFriendMessage(Tox*, uint32_t friendId, Tox_Message_Type type, const uint8_t* cMessage, size_t cMessageSize, void* core) { bool isAction = (type == TOX_MESSAGE_TYPE_ACTION); QString msg = ToxString(cMessage, cMessageSize).getQString(); emit static_cast(core)->friendMessageReceived(friendId, msg, isAction); } void Core::onFriendNameChange(Tox*, uint32_t friendId, const uint8_t* cName, size_t cNameSize, void* core) { QString newName = ToxString(cName, cNameSize).getQString(); // no saveRequest, this callback is called on every connection, not just on name change emit static_cast(core)->friendUsernameChanged(friendId, newName); } void Core::onFriendTypingChange(Tox*, uint32_t friendId, bool isTyping, void* core) { emit static_cast(core)->friendTypingChanged(friendId, isTyping); } void Core::onStatusMessageChanged(Tox*, uint32_t friendId, const uint8_t* cMessage, size_t cMessageSize, void* core) { QString message = ToxString(cMessage, cMessageSize).getQString(); // no saveRequest, this callback is called on every connection, not just on name change emit static_cast(core)->friendStatusMessageChanged(friendId, message); } void Core::onUserStatusChanged(Tox*, uint32_t friendId, Tox_User_Status userstatus, void* core) { Status::Status status; switch (userstatus) { case TOX_USER_STATUS_AWAY: status = Status::Status::Away; break; case TOX_USER_STATUS_BUSY: status = Status::Status::Busy; break; default: status = Status::Status::Online; break; } // no saveRequest, this callback is called on every connection, not just on name change emit static_cast(core)->friendStatusChanged(friendId, status); } void Core::onConnectionStatusChanged(Tox*, uint32_t friendId, Tox_Connection status, void* vCore) { Core* core = static_cast(vCore); Status::Status friendStatus = status != TOX_CONNECTION_NONE ? Status::Status::Online : Status::Status::Offline; // Ignore Online because it will be emited from onUserStatusChanged bool isOffline = friendStatus == Status::Status::Offline; if (isOffline) { emit core->friendStatusChanged(friendId, friendStatus); core->checkLastOnline(friendId); } } void Core::onGroupInvite(Tox* tox, uint32_t friendId, Tox_Conference_Type type, const uint8_t* cookie, size_t length, void* vCore) { Core* core = static_cast(vCore); const QByteArray data(reinterpret_cast(cookie), length); const GroupInvite inviteInfo(friendId, type, data); switch (type) { case TOX_CONFERENCE_TYPE_TEXT: qDebug() << QString("Text group invite by %1").arg(friendId); emit core->groupInviteReceived(inviteInfo); break; case TOX_CONFERENCE_TYPE_AV: qDebug() << QString("AV group invite by %1").arg(friendId); emit core->groupInviteReceived(inviteInfo); break; default: qWarning() << "Group invite with unknown type " << type; } } void Core::onGroupMessage(Tox*, uint32_t groupId, uint32_t peerId, Tox_Message_Type type, const uint8_t* cMessage, size_t length, void* vCore) { Core* core = static_cast(vCore); bool isAction = type == TOX_MESSAGE_TYPE_ACTION; QString message = ToxString(cMessage, length).getQString(); emit core->groupMessageReceived(groupId, peerId, message, isAction); } void Core::onGroupPeerListChange(Tox*, uint32_t groupId, void* vCore) { const auto core = static_cast(vCore); qDebug() << QString("Group %1 peerlist changed").arg(groupId); // no saveRequest, this callback is called on every connection to group peer, not just on brand new peers emit core->groupPeerlistChanged(groupId); } void Core::onGroupPeerNameChange(Tox*, uint32_t groupId, uint32_t peerId, const uint8_t* name, size_t length, void* vCore) { const auto newName = ToxString(name, length).getQString(); qDebug() << QString("Group %1, Peer %2, name changed to %3").arg(groupId).arg(peerId).arg(newName); auto* core = static_cast(vCore); auto peerPk = core->getGroupPeerPk(groupId, peerId); emit core->groupPeerNameChanged(groupId, peerPk, newName); } void Core::onGroupTitleChange(Tox*, uint32_t groupId, uint32_t peerId, const uint8_t* cTitle, size_t length, void* vCore) { Core* core = static_cast(vCore); QString author = core->getGroupPeerName(groupId, peerId); emit core->saveRequest(); emit core->groupTitleChanged(groupId, author, ToxString(cTitle, length).getQString()); } void Core::onReadReceiptCallback(Tox*, uint32_t friendId, uint32_t receipt, void* core) { emit static_cast(core)->receiptRecieved(friendId, ReceiptNum{receipt}); } void Core::acceptFriendRequest(const ToxPk& friendPk) { QMutexLocker ml{&coreLoopLock}; // TODO: error handling uint32_t friendId = tox_friend_add_norequest(tox.get(), friendPk.getData(), nullptr); if (friendId == std::numeric_limits::max()) { emit failedToAddFriend(friendPk); } else { emit saveRequest(); emit friendAdded(friendId, friendPk); } } /** * @brief Checks that sending friendship request is correct and returns error message accordingly * @param friendId Id of a friend which request is destined to * @param message Friendship request message * @return Returns empty string if sending request is correct, according error message otherwise */ QString Core::getFriendRequestErrorMessage(const ToxId& friendId, const QString& message) const { QMutexLocker ml{&coreLoopLock}; if (!friendId.isValid()) { return tr("Invalid Tox ID", "Error while sending friendship request"); } if (message.isEmpty()) { return tr("You need to write a message with your request", "Error while sending friendship request"); } if (message.length() > static_cast(tox_max_friend_request_length())) { return tr("Your message is too long!", "Error while sending friendship request"); } if (hasFriendWithPublicKey(friendId.getPublicKey())) { return tr("Friend is already added", "Error while sending friendship request"); } return QString{}; } void Core::requestFriendship(const ToxId& friendId, const QString& message) { QMutexLocker ml{&coreLoopLock}; ToxPk friendPk = friendId.getPublicKey(); QString errorMessage = getFriendRequestErrorMessage(friendId, message); if (!errorMessage.isNull()) { emit failedToAddFriend(friendPk, errorMessage); emit saveRequest(); return; } ToxString cMessage(message); uint32_t friendNumber = tox_friend_add(tox.get(), friendId.getBytes(), cMessage.data(), cMessage.size(), nullptr); if (friendNumber == std::numeric_limits::max()) { qDebug() << "Failed to request friendship"; emit failedToAddFriend(friendPk); } else { qDebug() << "Requested friendship of " << friendNumber; emit friendAdded(friendNumber, friendPk); emit requestSent(friendPk, message); } emit saveRequest(); } bool Core::sendMessageWithType(uint32_t friendId, const QString& message, Tox_Message_Type type, ReceiptNum& receipt) { int size = message.toUtf8().size(); auto maxSize = tox_max_message_length(); if (size > maxSize) { qCritical() << "Core::sendMessageWithType called with message of size:" << size << "when max is:" << maxSize << ". Ignoring."; return false; } ToxString cMessage(message); Tox_Err_Friend_Send_Message error; receipt = ReceiptNum{tox_friend_send_message(tox.get(), friendId, type, cMessage.data(), cMessage.size(), &error)}; if (parseFriendSendMessageError(error)) { return true; } return false; } bool Core::sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) { QMutexLocker ml(&coreLoopLock); return sendMessageWithType(friendId, message, TOX_MESSAGE_TYPE_NORMAL, receipt); } bool Core::sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) { QMutexLocker ml(&coreLoopLock); return sendMessageWithType(friendId, action, TOX_MESSAGE_TYPE_ACTION, receipt); } void Core::sendTyping(uint32_t friendId, bool typing) { QMutexLocker ml{&coreLoopLock}; if (!tox_self_set_typing(tox.get(), friendId, typing, nullptr)) { emit failedToSetTyping(typing); } } void Core::sendGroupMessageWithType(int groupId, const QString& message, Tox_Message_Type type) { QMutexLocker ml{&coreLoopLock}; int size = message.toUtf8().size(); auto maxSize = tox_max_message_length(); if (size > maxSize) { qCritical() << "Core::sendMessageWithType called with message of size:" << size << "when max is:" << maxSize << ". Ignoring."; return; } ToxString cMsg(message); Tox_Err_Conference_Send_Message error; tox_conference_send_message(tox.get(), groupId, type, cMsg.data(), cMsg.size(), &error); if (!parseConferenceSendMessageError(error)) { emit groupSentFailed(groupId); return; } } void Core::sendGroupMessage(int groupId, const QString& message) { QMutexLocker ml{&coreLoopLock}; sendGroupMessageWithType(groupId, message, TOX_MESSAGE_TYPE_NORMAL); } void Core::sendGroupAction(int groupId, const QString& message) { QMutexLocker ml{&coreLoopLock}; sendGroupMessageWithType(groupId, message, TOX_MESSAGE_TYPE_ACTION); } void Core::changeGroupTitle(int groupId, const QString& title) { QMutexLocker ml{&coreLoopLock}; ToxString cTitle(title); Tox_Err_Conference_Title error; bool success = tox_conference_set_title(tox.get(), groupId, cTitle.data(), cTitle.size(), &error); if (success && error == TOX_ERR_CONFERENCE_TITLE_OK) { emit saveRequest(); emit groupTitleChanged(groupId, getUsername(), title); return; } qCritical() << "Fail of tox_conference_set_title"; switch (error) { case TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND: qCritical() << "Conference not found"; break; case TOX_ERR_CONFERENCE_TITLE_FAIL_SEND: qCritical() << "Conference title failed to send"; break; case TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH: qCritical() << "Invalid length"; break; default: break; } } void Core::removeFriend(uint32_t friendId) { QMutexLocker ml{&coreLoopLock}; if (!tox_friend_delete(tox.get(), friendId, nullptr)) { emit failedToRemoveFriend(friendId); return; } emit saveRequest(); emit friendRemoved(friendId); } void Core::removeGroup(int groupId) { QMutexLocker ml{&coreLoopLock}; Tox_Err_Conference_Delete error; bool success = tox_conference_delete(tox.get(), groupId, &error); if (success && error == TOX_ERR_CONFERENCE_DELETE_OK) { emit saveRequest(); av->leaveGroupCall(groupId); return; } qCritical() << "Fail of tox_conference_delete"; switch (error) { case TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND: qCritical() << "Conference not found"; break; default: break; } } /** * @brief Returns our username, or an empty string on failure */ QString Core::getUsername() const { QMutexLocker ml{&coreLoopLock}; QString sname; if (!tox) { return sname; } int size = tox_self_get_name_size(tox.get()); uint8_t* name = new uint8_t[size]; tox_self_get_name(tox.get(), name); sname = ToxString(name, size).getQString(); delete[] name; return sname; } void Core::setUsername(const QString& username) { QMutexLocker ml{&coreLoopLock}; if (username == getUsername()) { return; } ToxString cUsername(username); if (!tox_self_set_name(tox.get(), cUsername.data(), cUsername.size(), nullptr)) { emit failedToSetUsername(username); return; } emit usernameSet(username); emit saveRequest(); } /** * @brief Returns our Tox ID */ ToxId Core::getSelfId() const { QMutexLocker ml{&coreLoopLock}; uint8_t friendId[TOX_ADDRESS_SIZE] = {0x00}; tox_self_get_address(tox.get(), friendId); return ToxId(friendId, TOX_ADDRESS_SIZE); } /** * @brief Gets self public key * @return Self PK */ ToxPk Core::getSelfPublicKey() const { QMutexLocker ml{&coreLoopLock}; uint8_t friendId[TOX_ADDRESS_SIZE] = {0x00}; tox_self_get_address(tox.get(), friendId); return ToxPk(friendId); } /** * @brief Returns our public and private keys */ QPair Core::getKeypair() const { QMutexLocker ml{&coreLoopLock}; QPair keypair; assert(tox != nullptr); QByteArray pk(TOX_PUBLIC_KEY_SIZE, 0x00); QByteArray sk(TOX_SECRET_KEY_SIZE, 0x00); tox_self_get_public_key(tox.get(), reinterpret_cast(pk.data())); tox_self_get_secret_key(tox.get(), reinterpret_cast(sk.data())); keypair.first = pk; keypair.second = sk; return keypair; } /** * @brief Returns our status message, or an empty string on failure */ QString Core::getStatusMessage() const { QMutexLocker ml{&coreLoopLock}; assert(tox != nullptr); size_t size = tox_self_get_status_message_size(tox.get()); if (size == 0) { return {}; } uint8_t* name = new uint8_t[size]; tox_self_get_status_message(tox.get(), name); QString sname = ToxString(name, size).getQString(); delete[] name; return sname; } /** * @brief Returns our user status */ Status::Status Core::getStatus() const { QMutexLocker ml{&coreLoopLock}; return static_cast(tox_self_get_status(tox.get())); } void Core::setStatusMessage(const QString& message) { QMutexLocker ml{&coreLoopLock}; if (message == getStatusMessage()) { return; } ToxString cMessage(message); if (!tox_self_set_status_message(tox.get(), cMessage.data(), cMessage.size(), nullptr)) { emit failedToSetStatusMessage(message); return; } emit saveRequest(); emit statusMessageSet(message); } void Core::setStatus(Status::Status status) { QMutexLocker ml{&coreLoopLock}; Tox_User_Status userstatus; switch (status) { case Status::Status::Online: userstatus = TOX_USER_STATUS_NONE; break; case Status::Status::Away: userstatus = TOX_USER_STATUS_AWAY; break; case Status::Status::Busy: userstatus = TOX_USER_STATUS_BUSY; break; default: return; break; } tox_self_set_status(tox.get(), userstatus); emit saveRequest(); emit statusSet(status); } /** * @brief Returns the unencrypted tox save data */ QByteArray Core::getToxSaveData() { QMutexLocker ml{&coreLoopLock}; uint32_t fileSize = tox_get_savedata_size(tox.get()); QByteArray data; data.resize(fileSize); tox_get_savedata(tox.get(), (uint8_t*)data.data()); return data; } // Declared to avoid code duplication #define GET_FRIEND_PROPERTY(property, function, checkSize) \ const size_t property##Size = function##_size(tox.get(), ids[i], nullptr); \ if ((!checkSize || property##Size) && property##Size != SIZE_MAX) { \ uint8_t* prop = new uint8_t[property##Size]; \ if (function(tox.get(), ids[i], prop, nullptr)) { \ QString propStr = ToxString(prop, property##Size).getQString(); \ emit friend##property##Changed(ids[i], propStr); \ } \ \ delete[] prop; \ } void Core::loadFriends() { QMutexLocker ml{&coreLoopLock}; const size_t friendCount = tox_self_get_friend_list_size(tox.get()); if (friendCount == 0) { return; } uint32_t* ids = new uint32_t[friendCount]; tox_self_get_friend_list(tox.get(), ids); uint8_t friendPk[TOX_PUBLIC_KEY_SIZE] = {0x00}; for (size_t i = 0; i < friendCount; ++i) { if (!tox_friend_get_public_key(tox.get(), ids[i], friendPk, nullptr)) { continue; } emit friendAdded(ids[i], ToxPk(friendPk)); GET_FRIEND_PROPERTY(Username, tox_friend_get_name, true); GET_FRIEND_PROPERTY(StatusMessage, tox_friend_get_status_message, false); checkLastOnline(ids[i]); } delete[] ids; } void Core::loadGroups() { QMutexLocker ml{&coreLoopLock}; const size_t groupCount = tox_conference_get_chatlist_size(tox.get()); if (groupCount == 0) { return; } auto groupNumbers = new uint32_t[groupCount]; tox_conference_get_chatlist(tox.get(), groupNumbers); for (size_t i = 0; i < groupCount; ++i) { TOX_ERR_CONFERENCE_TITLE error; QString name; const auto groupNumber = groupNumbers[i]; size_t titleSize = tox_conference_get_title_size(tox.get(), groupNumber, &error); const GroupId persistentId = getGroupPersistentId(groupNumber); const QString defaultName = tr("Groupchat %1").arg(persistentId.toString().left(8)); if (LogConferenceTitleError(error)) { name = defaultName; } else { QByteArray nameByteArray = QByteArray(static_cast(titleSize), Qt::Uninitialized); tox_conference_get_title(tox.get(), groupNumber, reinterpret_cast(nameByteArray.data()), &error); if (LogConferenceTitleError(error)) { name = defaultName; } else { name = ToxString(nameByteArray).getQString(); } } if (getGroupAvEnabled(groupNumber)) { if (toxav_groupchat_enable_av(tox.get(), groupNumber, CoreAV::groupCallCallback, this)) { qCritical() << "Failed to enable audio on loaded group" << groupNumber; } } emit emptyGroupCreated(groupNumber, persistentId, name); } delete[] groupNumbers; } void Core::checkLastOnline(uint32_t friendId) { QMutexLocker ml{&coreLoopLock}; const uint64_t lastOnline = tox_friend_get_last_online(tox.get(), friendId, nullptr); if (lastOnline != std::numeric_limits::max()) { emit friendLastSeenChanged(friendId, QDateTime::fromTime_t(lastOnline)); } } /** * @brief Returns the list of friendIds in our friendlist, an empty list on error */ QVector Core::getFriendList() const { QMutexLocker ml{&coreLoopLock}; QVector friends; friends.resize(tox_self_get_friend_list_size(tox.get())); tox_self_get_friend_list(tox.get(), friends.data()); return friends; } /** * @brief Print in console text of error. * @param error Error to handle. * @return True if no error, false otherwise. */ bool Core::parsePeerQueryError(Tox_Err_Conference_Peer_Query error) const { switch (error) { case TOX_ERR_CONFERENCE_PEER_QUERY_OK: return true; case TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND: qCritical() << "Conference not found"; return false; case TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION: qCritical() << "No connection"; return false; case TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND: qCritical() << "Peer not found"; return false; default: qCritical() << "Unknow error code:" << error; return false; } } GroupId Core::getGroupPersistentId(uint32_t groupNumber) const { QMutexLocker ml{&coreLoopLock}; size_t conferenceIdSize = TOX_CONFERENCE_UID_SIZE; QByteArray groupPersistentId(conferenceIdSize, Qt::Uninitialized); if (tox_conference_get_id(tox.get(), groupNumber, reinterpret_cast(groupPersistentId.data()))) { return GroupId{groupPersistentId}; } else { qCritical() << "Failed to get conference ID of group" << groupNumber; return {}; } } /** * @brief Get number of peers in the conference. * @return The number of peers in the conference. UINT32_MAX on failure. */ uint32_t Core::getGroupNumberPeers(int groupId) const { QMutexLocker ml{&coreLoopLock}; Tox_Err_Conference_Peer_Query error; uint32_t count = tox_conference_peer_count(tox.get(), groupId, &error); if (!parsePeerQueryError(error)) { return std::numeric_limits::max(); } return count; } /** * @brief Get the name of a peer of a group */ QString Core::getGroupPeerName(int groupId, int peerId) const { QMutexLocker ml{&coreLoopLock}; // from tox.h: "If peer_number == UINT32_MAX, then author is unknown (e.g. initial joining the conference)." if (peerId == std::numeric_limits::max()) { return {}; } Tox_Err_Conference_Peer_Query error; size_t length = tox_conference_peer_get_name_size(tox.get(), groupId, peerId, &error); if (!parsePeerQueryError(error)) { return QString{}; } QByteArray name(length, Qt::Uninitialized); uint8_t* namePtr = reinterpret_cast(name.data()); bool success = tox_conference_peer_get_name(tox.get(), groupId, peerId, namePtr, &error); if (!parsePeerQueryError(error)) { return QString{}; } assert(success); return ToxString(name).getQString(); } /** * @brief Get the public key of a peer of a group */ ToxPk Core::getGroupPeerPk(int groupId, int peerId) const { QMutexLocker ml{&coreLoopLock}; uint8_t friendPk[TOX_PUBLIC_KEY_SIZE] = {0x00}; Tox_Err_Conference_Peer_Query error; bool success = tox_conference_peer_get_public_key(tox.get(), groupId, peerId, friendPk, &error); if (!parsePeerQueryError(error)) { return ToxPk{}; } assert(success); return ToxPk(friendPk); } /** * @brief Get the names of the peers of a group */ QStringList Core::getGroupPeerNames(int groupId) const { QMutexLocker ml{&coreLoopLock}; assert(tox != nullptr); uint32_t nPeers = getGroupNumberPeers(groupId); if (nPeers == std::numeric_limits::max()) { qWarning() << "getGroupPeerNames: Unable to get number of peers"; return {}; } QStringList names; for (uint32_t i = 0; i < nPeers; ++i) { TOX_ERR_CONFERENCE_PEER_QUERY error; size_t length = tox_conference_peer_get_name_size(tox.get(), groupId, i, &error); if (!parsePeerQueryError(error)) { names.append(QString()); continue; } QByteArray name(length, Qt::Uninitialized); uint8_t* namePtr = reinterpret_cast(name.data()); bool ok = tox_conference_peer_get_name(tox.get(), groupId, i, namePtr, &error); if (ok && parsePeerQueryError(error)) { names.append(ToxString(name).getQString()); } else { names.append(QString()); } } assert(names.size() == nPeers); return names; } /** * @brief Check, that group has audio or video stream * @param groupId Id of group to check * @return True for AV groups, false for text-only groups */ bool Core::getGroupAvEnabled(int groupId) const { QMutexLocker ml{&coreLoopLock}; TOX_ERR_CONFERENCE_GET_TYPE error; TOX_CONFERENCE_TYPE type = tox_conference_get_type(tox.get(), groupId, &error); switch (error) { case TOX_ERR_CONFERENCE_GET_TYPE_OK: break; case TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND: qWarning() << "Conference not found"; break; default: qWarning() << "Unknown error code:" << QString::number(error); break; } return type == TOX_CONFERENCE_TYPE_AV; } /** * @brief Print in console text of error. * @param error Error to handle. * @return True if no error, false otherwise. */ bool Core::parseConferenceJoinError(Tox_Err_Conference_Join error) const { switch (error) { case TOX_ERR_CONFERENCE_JOIN_OK: return true; case TOX_ERR_CONFERENCE_JOIN_DUPLICATE: qCritical() << "Conference duplicate"; return false; case TOX_ERR_CONFERENCE_JOIN_FAIL_SEND: qCritical() << "Conference join failed to send"; return false; case TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND: qCritical() << "Friend not found"; return false; case TOX_ERR_CONFERENCE_JOIN_INIT_FAIL: qCritical() << "Init fail"; return false; case TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH: qCritical() << "Invalid length"; return false; case TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE: qCritical() << "Wrong conference type"; return false; default: qCritical() << "Unknow error code:" << error; return false; } } /** * @brief Accept a groupchat invite. * @param inviteInfo Object which contains info about group invitation * * @return Conference number on success, UINT32_MAX on failure. */ uint32_t Core::joinGroupchat(const GroupInvite& inviteInfo) { QMutexLocker ml{&coreLoopLock}; const uint32_t friendId = inviteInfo.getFriendId(); const uint8_t confType = inviteInfo.getType(); const QByteArray invite = inviteInfo.getInvite(); const uint8_t* const cookie = reinterpret_cast(invite.data()); const size_t cookieLength = invite.length(); uint32_t groupNum{std::numeric_limits::max()}; switch (confType) { case TOX_CONFERENCE_TYPE_TEXT: { qDebug() << QString("Trying to join text groupchat invite sent by friend %1").arg(friendId); Tox_Err_Conference_Join error; groupNum = tox_conference_join(tox.get(), friendId, cookie, cookieLength, &error); if (!parseConferenceJoinError(error)) { groupNum = std::numeric_limits::max(); } break; } case TOX_CONFERENCE_TYPE_AV: { qDebug() << QString("Trying to join AV groupchat invite sent by friend %1").arg(friendId); groupNum = toxav_join_av_groupchat(tox.get(), friendId, cookie, cookieLength, CoreAV::groupCallCallback, const_cast(this)); break; } default: qWarning() << "joinGroupchat: Unknown groupchat type " << confType; } if (groupNum != std::numeric_limits::max()) { emit saveRequest(); emit groupJoined(groupNum, getGroupPersistentId(groupNum)); } return groupNum; } void Core::groupInviteFriend(uint32_t friendId, int groupId) { QMutexLocker ml{&coreLoopLock}; Tox_Err_Conference_Invite error; tox_conference_invite(tox.get(), friendId, groupId, &error); switch (error) { case TOX_ERR_CONFERENCE_INVITE_OK: break; case TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND: qCritical() << "Conference not found"; break; case TOX_ERR_CONFERENCE_INVITE_FAIL_SEND: qCritical() << "Conference invite failed to send"; break; default: break; } } int Core::createGroup(uint8_t type) { QMutexLocker ml{&coreLoopLock}; if (type == TOX_CONFERENCE_TYPE_TEXT) { Tox_Err_Conference_New error; uint32_t groupId = tox_conference_new(tox.get(), &error); switch (error) { case TOX_ERR_CONFERENCE_NEW_OK: emit saveRequest(); emit emptyGroupCreated(groupId, getGroupPersistentId(groupId)); return groupId; case TOX_ERR_CONFERENCE_NEW_INIT: qCritical() << "The conference instance failed to initialize"; return std::numeric_limits::max(); default: return std::numeric_limits::max(); } } else if (type == TOX_CONFERENCE_TYPE_AV) { uint32_t groupId = toxav_add_av_groupchat(tox.get(), CoreAV::groupCallCallback, this); emit saveRequest(); emit emptyGroupCreated(groupId, getGroupPersistentId(groupId)); return groupId; } else { qWarning() << "createGroup: Unknown type " << type; return -1; } } /** * @brief Checks if a friend is online. Unknown friends are considered offline. */ bool Core::isFriendOnline(uint32_t friendId) const { QMutexLocker ml{&coreLoopLock}; Tox_Connection connection = tox_friend_get_connection_status(tox.get(), friendId, nullptr); return connection != TOX_CONNECTION_NONE; } /** * @brief Checks if we have a friend by public key */ bool Core::hasFriendWithPublicKey(const ToxPk& publicKey) const { QMutexLocker ml{&coreLoopLock}; if (publicKey.isEmpty()) { return false; } // TODO: error handling uint32_t friendId = tox_friend_by_public_key(tox.get(), publicKey.getData(), nullptr); return friendId != std::numeric_limits::max(); } /** * @brief Get the public key part of the ToxID only */ ToxPk Core::getFriendPublicKey(uint32_t friendNumber) const { QMutexLocker ml{&coreLoopLock}; uint8_t rawid[TOX_PUBLIC_KEY_SIZE]; if (!tox_friend_get_public_key(tox.get(), friendNumber, rawid, nullptr)) { qWarning() << "getFriendPublicKey: Getting public key failed"; return ToxPk(); } return ToxPk(rawid); } /** * @brief Get the username of a friend */ QString Core::getFriendUsername(uint32_t friendnumber) const { QMutexLocker ml{&coreLoopLock}; size_t namesize = tox_friend_get_name_size(tox.get(), friendnumber, nullptr); if (namesize == SIZE_MAX) { qWarning() << "getFriendUsername: Failed to get name size for friend " << friendnumber; return QString(); } uint8_t* name = new uint8_t[namesize]; tox_friend_get_name(tox.get(), friendnumber, name, nullptr); ToxString sname(name, namesize); delete[] name; return sname.getQString(); } QStringList Core::splitMessage(const QString& message) { QStringList splittedMsgs; QByteArray ba_message{message.toUtf8()}; /* * TODO: Remove this hack; the reported max message length we receive from c-toxcore * as of 08-02-2019 is inaccurate, causing us to generate too large messages when splitting * them up. * * The inconsistency lies in c-toxcore group.c:2480 using MAX_GROUP_MESSAGE_DATA_LEN to verify * message size is within limit, but tox_max_message_length giving a different size limit to us. * * (uint32_t tox_max_message_length(void); declared in tox.h, unable to see explicit definition) */ const auto maxLen = tox_max_message_length() - 50; while (ba_message.size() > maxLen) { int splitPos = ba_message.lastIndexOf('\n', maxLen - 1); if (splitPos <= 0) { splitPos = ba_message.lastIndexOf(' ', maxLen - 1); } if (splitPos <= 0) { constexpr uint8_t firstOfMultiByteMask = 0xC0; constexpr uint8_t multiByteMask = 0x80; splitPos = maxLen; // don't split a utf8 character if ((ba_message[splitPos] & multiByteMask) == multiByteMask) { while ((ba_message[splitPos] & firstOfMultiByteMask) != firstOfMultiByteMask) { --splitPos; } } --splitPos; } splittedMsgs.append(QString{ba_message.left(splitPos + 1)}); ba_message = ba_message.mid(splitPos + 1); } splittedMsgs.append(QString{ba_message}); return splittedMsgs; } QString Core::getPeerName(const ToxPk& id) const { QMutexLocker ml{&coreLoopLock}; QString name; uint32_t friendId = tox_friend_by_public_key(tox.get(), id.getData(), nullptr); if (friendId == std::numeric_limits::max()) { qWarning() << "getPeerName: No such peer"; return name; } const size_t nameSize = tox_friend_get_name_size(tox.get(), friendId, nullptr); if (nameSize == SIZE_MAX) { return name; } uint8_t* cname = new uint8_t[nameSize < tox_max_name_length() ? tox_max_name_length() : nameSize]; if (!tox_friend_get_name(tox.get(), friendId, cname, nullptr)) { qWarning() << "getPeerName: Can't get name of friend " + QString().setNum(friendId); delete[] cname; return name; } name = ToxString(cname, nameSize).getQString(); delete[] cname; return name; } /** * @brief Sets the NoSpam value to prevent friend request spam * @param nospam an arbitrary which becomes part of the Tox ID */ void Core::setNospam(uint32_t nospam) { QMutexLocker ml{&coreLoopLock}; tox_self_set_nospam(tox.get(), nospam); emit idSet(getSelfId()); } qTox/src/core/core.h000066400000000000000000000236401415623743500146500ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CORE_HPP #define CORE_HPP #include "groupid.h" #include "icorefriendmessagesender.h" #include "icoregroupmessagesender.h" #include "icoregroupquery.h" #include "icoreidhandler.h" #include "receiptnum.h" #include "toxfile.h" #include "toxid.h" #include "toxpk.h" #include "src/util/strongtype.h" #include "src/util/compatiblerecursivemutex.h" #include "src/model/status.h" #include #include #include #include #include #include #include class CoreAV; class CoreFile; class IAudioControl; class ICoreSettings; class GroupInvite; class Profile; class Core; using ToxCorePtr = std::unique_ptr; class Core : public QObject, public ICoreFriendMessageSender, public ICoreIdHandler, public ICoreGroupMessageSender, public ICoreGroupQuery { Q_OBJECT public: enum class ToxCoreErrors { BAD_PROXY, INVALID_SAVE, FAILED_TO_START, ERROR_ALLOC }; static ToxCorePtr makeToxCore(const QByteArray& savedata, const ICoreSettings* const settings, ToxCoreErrors* err = nullptr); static Core* getInstance(); const CoreAV* getAv() const; CoreAV* getAv(); CoreFile* getCoreFile() const; ~Core(); static const QString TOX_EXT; static QStringList splitMessage(const QString& message); QString getPeerName(const ToxPk& id) const; QVector getFriendList() const; GroupId getGroupPersistentId(uint32_t groupNumber) const override; uint32_t getGroupNumberPeers(int groupId) const override; QString getGroupPeerName(int groupId, int peerId) const override; ToxPk getGroupPeerPk(int groupId, int peerId) const override; QStringList getGroupPeerNames(int groupId) const override; bool getGroupAvEnabled(int groupId) const override; ToxPk getFriendPublicKey(uint32_t friendNumber) const; QString getFriendUsername(uint32_t friendNumber) const; bool isFriendOnline(uint32_t friendId) const; bool hasFriendWithPublicKey(const ToxPk& publicKey) const; uint32_t joinGroupchat(const GroupInvite& inviteInfo); void quitGroupChat(int groupId) const; QString getUsername() const override; Status::Status getStatus() const; QString getStatusMessage() const; ToxId getSelfId() const override; ToxPk getSelfPublicKey() const override; QPair getKeypair() const; void sendFile(uint32_t friendId, QString filename, QString filePath, long long filesize); public slots: void start(); QByteArray getToxSaveData(); void acceptFriendRequest(const ToxPk& friendPk); void requestFriendship(const ToxId& friendAddress, const QString& message); void groupInviteFriend(uint32_t friendId, int groupId); int createGroup(uint8_t type = TOX_CONFERENCE_TYPE_AV); void removeFriend(uint32_t friendId); void removeGroup(int groupId); void setStatus(Status::Status status); void setUsername(const QString& username); void setStatusMessage(const QString& message); bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override; void sendGroupMessage(int groupId, const QString& message) override; void sendGroupAction(int groupId, const QString& message) override; void changeGroupTitle(int groupId, const QString& title); bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override; void sendTyping(uint32_t friendId, bool typing); void setNospam(uint32_t nospam); signals: void connected(); void disconnected(); void friendRequestReceived(const ToxPk& friendPk, const QString& message); void friendAvatarChanged(const ToxPk& friendPk, const QByteArray& pic); void friendAvatarRemoved(const ToxPk& friendPk); void requestSent(const ToxPk& friendPk, const QString& message); void failedToAddFriend(const ToxPk& friendPk, const QString& errorInfo = QString()); void usernameSet(const QString& username); void statusMessageSet(const QString& message); void statusSet(Status::Status status); void idSet(const ToxId& id); void failedToSetUsername(const QString& username); void failedToSetStatusMessage(const QString& message); void failedToSetStatus(Status::Status status); void failedToSetTyping(bool typing); void avReady(); void saveRequest(); /** * @deprecated prefer signals using ToxPk */ void fileAvatarOfferReceived(uint32_t friendId, uint32_t fileId, const QByteArray& avatarHash); void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction); void friendAdded(uint32_t friendId, const ToxPk& friendPk); void friendStatusChanged(uint32_t friendId, Status::Status status); void friendStatusMessageChanged(uint32_t friendId, const QString& message); void friendUsernameChanged(uint32_t friendId, const QString& username); void friendTypingChanged(uint32_t friendId, bool isTyping); void friendRemoved(uint32_t friendId); void friendLastSeenChanged(uint32_t friendId, const QDateTime& dateTime); void emptyGroupCreated(int groupnumber, const GroupId groupId, const QString& title = QString()); void groupInviteReceived(const GroupInvite& inviteInfo); void groupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change); void groupPeerlistChanged(int groupnumber); void groupPeerNameChanged(int groupnumber, const ToxPk& peerPk, const QString& newName); void groupTitleChanged(int groupnumber, const QString& author, const QString& title); void groupPeerAudioPlaying(int groupnumber, ToxPk peerPk); void groupSentFailed(int groupId); void groupJoined(int groupnumber, GroupId groupId); void actionSentResult(uint32_t friendId, const QString& action, int success); void receiptRecieved(int friedId, ReceiptNum receipt); void failedToRemoveFriend(uint32_t friendId); private: Core(QThread* coreThread); static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage, size_t cMessageSize, void* core); static void onFriendMessage(Tox* tox, uint32_t friendId, Tox_Message_Type type, const uint8_t* cMessage, size_t cMessageSize, void* core); static void onFriendNameChange(Tox* tox, uint32_t friendId, const uint8_t* cName, size_t cNameSize, void* core); static void onFriendTypingChange(Tox* tox, uint32_t friendId, bool isTyping, void* core); static void onStatusMessageChanged(Tox* tox, uint32_t friendId, const uint8_t* cMessage, size_t cMessageSize, void* core); static void onUserStatusChanged(Tox* tox, uint32_t friendId, Tox_User_Status userstatus, void* core); static void onConnectionStatusChanged(Tox* tox, uint32_t friendId, Tox_Connection status, void* vCore); static void onGroupInvite(Tox* tox, uint32_t friendId, Tox_Conference_Type type, const uint8_t* cookie, size_t length, void* vCore); static void onGroupMessage(Tox* tox, uint32_t groupId, uint32_t peerId, Tox_Message_Type type, const uint8_t* cMessage, size_t length, void* vCore); static void onGroupPeerListChange(Tox*, uint32_t groupId, void* core); static void onGroupPeerNameChange(Tox*, uint32_t groupId, uint32_t peerId, const uint8_t* name, size_t length, void* core); static void onGroupTitleChange(Tox* tox, uint32_t groupId, uint32_t peerId, const uint8_t* cTitle, size_t length, void* vCore); static void onReadReceiptCallback(Tox* tox, uint32_t friendId, uint32_t receipt, void* core); void sendGroupMessageWithType(int groupId, const QString& message, Tox_Message_Type type); bool sendMessageWithType(uint32_t friendId, const QString& message, Tox_Message_Type type, ReceiptNum& receipt); bool parsePeerQueryError(Tox_Err_Conference_Peer_Query error) const; bool parseConferenceJoinError(Tox_Err_Conference_Join error) const; bool checkConnection(); void makeTox(QByteArray savedata, ICoreSettings* s); void loadFriends(); void loadGroups(); void bootstrapDht(); void checkLastOnline(uint32_t friendId); QString getFriendRequestErrorMessage(const ToxId& friendId, const QString& message) const; static void registerCallbacks(Tox* tox); private slots: void process(); void onStarted(); private: struct ToxDeleter { void operator()(Tox* tox) { tox_kill(tox); } }; using ToxPtr = std::unique_ptr; ToxPtr tox; std::unique_ptr file; std::unique_ptr av; QTimer* toxTimer = nullptr; // recursive, since we might call our own functions mutable CompatibleRecursiveMutex coreLoopLock; std::unique_ptr coreThread = nullptr; }; #endif // CORE_HPP qTox/src/core/coreav.cpp000066400000000000000000000673771415623743500155510ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "coreav.h" #include "core.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/persistence/settings.h" #include "src/video/corevideosource.h" #include "src/video/videoframe.h" #include "src/util/compatiblerecursivemutex.h" #include #include #include #include #include #include /** * @fn void CoreAV::avInvite(uint32_t friendId, bool video) * @brief Sent when a friend calls us. * @param friendId Id of friend in call list. * @param video False if chat is audio only, true audio and video. * * @fn void CoreAV::avStart(uint32_t friendId, bool video) * @brief Sent when a call we initiated has started. * @param friendId Id of friend in call list. * @param video False if chat is audio only, true audio and video. * * @fn void CoreAV::avEnd(uint32_t friendId) * @brief Sent when a call was ended by the peer. * @param friendId Id of friend in call list. * * @var CoreAV::VIDEO_DEFAULT_BITRATE * @brief Picked at random by fair dice roll. */ /** * @var std::atomic_flag CoreAV::threadSwitchLock * @brief This flag is to be acquired before switching in a blocking way between the UI and CoreAV * thread. * * The CoreAV thread must have priority for the flag, other threads should back off or release it * quickly. * CoreAV needs to interface with three threads, the toxcore/Core thread that fires non-payload * toxav callbacks, the toxav/CoreAV thread that fires AV payload callbacks and manages * most of CoreAV's members, and the UI thread, which calls our [start/answer/cancel]Call functions * and which we call via signals. * When the UI calls us, we switch from the UI thread to the CoreAV thread to do the processing, * when toxcore fires a non-payload av callback, we do the processing in the CoreAV thread and then * switch to the UI thread to send it a signal. Both switches block both threads, so this would * deadlock. */ CoreAV::CoreAV(std::unique_ptr toxav, CompatibleRecursiveMutex& toxCoreLock) : audio{nullptr} , toxav{std::move(toxav)} , coreavThread{new QThread{this}} , iterateTimer{new QTimer{this}} , coreLock{toxCoreLock} { assert(coreavThread); assert(iterateTimer); coreavThread->setObjectName("qTox CoreAV"); moveToThread(coreavThread.get()); connectCallbacks(*this->toxav); iterateTimer->setSingleShot(true); connect(iterateTimer, &QTimer::timeout, this, &CoreAV::process); connect(coreavThread.get(), &QThread::finished, iterateTimer, &QTimer::stop); connect(coreavThread.get(), &QThread::started, this, &CoreAV::process); } void CoreAV::connectCallbacks(ToxAV& toxav) { toxav_callback_call(&toxav, CoreAV::callCallback, this); toxav_callback_call_state(&toxav, CoreAV::stateCallback, this); toxav_callback_audio_bit_rate(&toxav, CoreAV::audioBitrateCallback, this); toxav_callback_video_bit_rate(&toxav, CoreAV::videoBitrateCallback, this); toxav_callback_audio_receive_frame(&toxav, CoreAV::audioFrameCallback, this); toxav_callback_video_receive_frame(&toxav, CoreAV::videoFrameCallback, this); } /** * @brief Factory method for CoreAV * @param core pointer to the Tox instance * @return CoreAV instance on success, {} on failure */ CoreAV::CoreAVPtr CoreAV::makeCoreAV(Tox* core, CompatibleRecursiveMutex &toxCoreLock) { TOXAV_ERR_NEW err; std::unique_ptr toxav{toxav_new(core, &err)}; switch (err) { case TOXAV_ERR_NEW_OK: break; case TOXAV_ERR_NEW_MALLOC: qCritical() << "Failed to allocate ressources for ToxAV"; return {}; case TOXAV_ERR_NEW_MULTIPLE: qCritical() << "Attempted to create multiple ToxAV instances"; return {}; case TOXAV_ERR_NEW_NULL: qCritical() << "Unexpected NULL parameter"; return {}; } assert(toxav != nullptr); return CoreAVPtr{new CoreAV{std::move(toxav), toxCoreLock}}; } /** * @brief Set the audio backend * @param audio The audio backend to use * @note This must be called before starting CoreAV and audio must outlive CoreAV */ void CoreAV::setAudio(IAudioControl& newAudio) { audio.exchange(&newAudio); } /** * @brief Get the audio backend used * @return Pointer to the audio backend * @note This is needed only for the case CoreAV needs to restart and the restarting class doesn't * have access to the audio backend and wants to keep it the same. */ IAudioControl* CoreAV::getAudio() { return audio; } CoreAV::~CoreAV() { /* Gracefully leave calls and group calls to avoid deadlocks in destructor */ for (const auto& call : calls) { cancelCall(call.first); } for (const auto& call : groupCalls) { leaveGroupCall(call.first); } assert(calls.empty()); assert(groupCalls.empty()); coreavThread->exit(0); coreavThread->wait(); } /** * @brief Starts the CoreAV main loop that calls toxav's main loop */ void CoreAV::start() { coreavThread->start(); } void CoreAV::process() { assert(QThread::currentThread() == coreavThread.get()); toxav_iterate(toxav.get()); iterateTimer->start(toxav_iteration_interval(toxav.get())); } /** * @brief Checks the call status for a Tox friend. * @param f the friend to check * @return true, if call is started for the friend, false otherwise */ bool CoreAV::isCallStarted(const Friend* f) const { QReadLocker locker{&callsLock}; return f && (calls.find(f->getId()) != calls.end()); } /** * @brief Checks the call status for a Tox group. * @param g the group to check * @return true, if call is started for the group, false otherwise */ bool CoreAV::isCallStarted(const Group* g) const { QReadLocker locker{&callsLock}; return g && (groupCalls.find(g->getId()) != groupCalls.end()); } /** * @brief Checks the call status for a Tox friend. * @param f the friend to check * @return true, if call is active for the friend, false otherwise */ bool CoreAV::isCallActive(const Friend* f) const { QReadLocker locker{&callsLock}; auto it = calls.find(f->getId()); if (it == calls.end()) { return false; } return isCallStarted(f) && it->second->isActive(); } /** * @brief Checks the call status for a Tox group. * @param g the group to check * @return true, if the call is active for the group, false otherwise */ bool CoreAV::isCallActive(const Group* g) const { QReadLocker locker{&callsLock}; auto it = groupCalls.find(g->getId()); if (it == groupCalls.end()) { return false; } return isCallStarted(g) && it->second->isActive(); } bool CoreAV::isCallVideoEnabled(const Friend* f) const { QReadLocker locker{&callsLock}; auto it = calls.find(f->getId()); return isCallStarted(f) && it->second->getVideoEnabled(); } bool CoreAV::answerCall(uint32_t friendNum, bool video) { QWriteLocker locker{&callsLock}; QMutexLocker coreLocker{&coreLock}; qDebug() << QString("Answering call %1").arg(friendNum); auto it = calls.find(friendNum); assert(it != calls.end()); TOXAV_ERR_ANSWER err; const uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0; if (toxav_answer(toxav.get(), friendNum, Settings::getInstance().getAudioBitrate(), videoBitrate, &err)) { it->second->setActive(true); return true; } else { qWarning() << "Failed to answer call with error" << err; toxav_call_control(toxav.get(), friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr); calls.erase(it); return false; } } bool CoreAV::startCall(uint32_t friendNum, bool video) { QWriteLocker locker{&callsLock}; QMutexLocker coreLocker{&coreLock}; qDebug() << QString("Starting call with %1").arg(friendNum); auto it = calls.find(friendNum); if (it != calls.end()) { qWarning() << QString("Can't start call with %1, we're already in this call!").arg(friendNum); return false; } uint32_t videoBitrate = video ? VIDEO_DEFAULT_BITRATE : 0; if (!toxav_call(toxav.get(), friendNum, Settings::getInstance().getAudioBitrate(), videoBitrate, nullptr)) return false; // Audio backend must be set before making a call assert(audio != nullptr); ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall(friendNum, video, *this, *audio)); // Call object must be owned by this thread or there will be locking problems with Audio call->moveToThread(this->thread()); assert(call != nullptr); calls.emplace(friendNum, std::move(call)); return true; } bool CoreAV::cancelCall(uint32_t friendNum) { QWriteLocker locker{&callsLock}; QMutexLocker coreLocker{&coreLock}; qDebug() << QString("Cancelling call with %1").arg(friendNum); if (!toxav_call_control(toxav.get(), friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr)) { qWarning() << QString("Failed to cancel call with %1").arg(friendNum); return false; } calls.erase(friendNum); locker.unlock(); emit avEnd(friendNum); return true; } void CoreAV::timeoutCall(uint32_t friendNum) { QWriteLocker locker{&callsLock}; if (!cancelCall(friendNum)) { qWarning() << QString("Failed to timeout call with %1").arg(friendNum); return; } qDebug() << "Call with friend" << friendNum << "timed out"; } /** * @brief Send audio frame to a friend * @param callId Id of friend in call list. * @param pcm An array of audio samples (Pulse-code modulation). * @param samples Number of samples in this frame. * @param chans Number of audio channels. * @param rate Audio sampling rate used in this frame. * @return False only on error, but not if there's nothing to send. */ bool CoreAV::sendCallAudio(uint32_t callId, const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) const { QReadLocker locker{&callsLock}; auto it = calls.find(callId); if (it == calls.end()) { return false; } ToxFriendCall const& call = *it->second; if (call.getMuteMic() || !call.isActive() || !(call.getState() & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A)) { return true; } // TOXAV_ERR_SEND_FRAME_SYNC means toxav failed to lock, retry 5 times in this case TOXAV_ERR_SEND_FRAME err; int retries = 0; do { if (!toxav_audio_send_frame(toxav.get(), callId, pcm, samples, chans, rate, &err)) { if (err == TOXAV_ERR_SEND_FRAME_SYNC) { ++retries; QThread::usleep(500); } else { qDebug() << "toxav_audio_send_frame error: " << err; } } } while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5); if (err == TOXAV_ERR_SEND_FRAME_SYNC) { qDebug() << "toxav_audio_send_frame error: Lock busy, dropping frame"; } return true; } void CoreAV::sendCallVideo(uint32_t callId, std::shared_ptr vframe) { QWriteLocker locker{&callsLock}; // We might be running in the FFmpeg thread and holding the CameraSource lock // So be careful not to deadlock with anything while toxav locks in toxav_video_send_frame auto it = calls.find(callId); if (it == calls.end()) { return; } ToxFriendCall& call = *it->second; if (!call.getVideoEnabled() || !call.isActive() || !(call.getState() & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V)) { return; } if (call.getNullVideoBitrate()) { qDebug() << "Restarting video stream to friend" << callId; QMutexLocker coreLocker{&coreLock}; toxav_video_set_bit_rate(toxav.get(), callId, VIDEO_DEFAULT_BITRATE, nullptr); call.setNullVideoBitrate(false); } ToxYUVFrame frame = vframe->toToxYUVFrame(); if (!frame) { return; } // TOXAV_ERR_SEND_FRAME_SYNC means toxav failed to lock, retry 5 times in this case // We don't want to be dropping iframes because of some lock held by toxav_iterate TOXAV_ERR_SEND_FRAME err; int retries = 0; do { if (!toxav_video_send_frame(toxav.get(), callId, frame.width, frame.height, frame.y, frame.u, frame.v, &err)) { if (err == TOXAV_ERR_SEND_FRAME_SYNC) { ++retries; QThread::usleep(500); } else { qDebug() << "toxav_video_send_frame error: " << err; } } } while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5); if (err == TOXAV_ERR_SEND_FRAME_SYNC) { qDebug() << "toxav_video_send_frame error: Lock busy, dropping frame"; } } /** * @brief Toggles the mute state of the call's input (microphone). * @param f The friend assigned to the call */ void CoreAV::toggleMuteCallInput(const Friend* f) { QWriteLocker locker{&callsLock}; auto it = calls.find(f->getId()); if (f && (it != calls.end())) { ToxCall& call = *it->second; call.setMuteMic(!call.getMuteMic()); } } /** * @brief Toggles the mute state of the call's output (speaker). * @param f The friend assigned to the call */ void CoreAV::toggleMuteCallOutput(const Friend* f) { QWriteLocker locker{&callsLock}; auto it = calls.find(f->getId()); if (f && (it != calls.end())) { ToxCall& call = *it->second; call.setMuteVol(!call.getMuteVol()); } } /** * @brief Called from Tox API when group call receives audio data. * * @param[in] tox the Tox object * @param[in] group the group number * @param[in] peer the peer number * @param[in] data the audio data to playback * @param[in] samples the audio samples * @param[in] channels the audio channels * @param[in] sample_rate the audio sample rate * @param[in] core the qTox Core class */ void CoreAV::groupCallCallback(void* tox, uint32_t group, uint32_t peer, const int16_t* data, unsigned samples, uint8_t channels, uint32_t sample_rate, void* core) { /* * Currently group call audio decoding is handled in the Tox thread by c-toxcore, * so we can be sure that this function is always called from the Core thread. * To change this, an API change in c-toxcore is needed and this function probably must be * changed. * See https://github.com/TokTok/c-toxcore/issues/1364 for details. */ Q_UNUSED(tox); Core* c = static_cast(core); CoreAV* cav = c->getAv(); QReadLocker locker{&cav->callsLock}; const ToxPk peerPk = c->getGroupPeerPk(group, peer); const Settings& s = Settings::getInstance(); // don't play the audio if it comes from a muted peer if (s.getBlackList().contains(peerPk.toString())) { return; } emit c->groupPeerAudioPlaying(group, peerPk); auto it = cav->groupCalls.find(group); if (it == cav->groupCalls.end()) { return; } ToxGroupCall& call = *it->second; if (call.getMuteVol() || !call.isActive()) { return; } call.playAudioBuffer(peerPk, data, samples, channels, sample_rate); } /** * @brief Called from core to make sure the source for that peer is invalidated when they leave. * @param group Group Index * @param peer Peer Index */ void CoreAV::invalidateGroupCallPeerSource(int group, ToxPk peerPk) { QWriteLocker locker{&callsLock}; auto it = groupCalls.find(group); if (it == groupCalls.end()) { return; } it->second->removePeer(peerPk); } /** * @brief Get a call's video source. * @param friendNum Id of friend in call list. * @return Video surface to show */ VideoSource* CoreAV::getVideoSourceFromCall(int friendNum) const { QReadLocker locker{&callsLock}; auto it = calls.find(friendNum); if (it == calls.end()) { qWarning() << "CoreAV::getVideoSourceFromCall: No such call, did it die before we finished " "answering?"; return nullptr; } return it->second->getVideoSource(); } /** * @brief Starts a call in an existing AV groupchat. * @note Call from the GUI thread. * @param groupId Id of group to join */ void CoreAV::joinGroupCall(const Group& group) { QWriteLocker locker{&callsLock}; qDebug() << QString("Joining group call %1").arg(group.getId()); // Audio backend must be set before starting a call assert(audio != nullptr); ToxGroupCallPtr groupcall = ToxGroupCallPtr(new ToxGroupCall{group, *this, *audio}); // Call Objects must be owned by CoreAV or there will be locking problems with Audio groupcall->moveToThread(this->thread()); assert(groupcall != nullptr); auto ret = groupCalls.emplace(group.getId(), std::move(groupcall)); if (ret.second == false) { qWarning() << "This group call already exists, not joining!"; return; } ret.first->second->setActive(true); } /** * @brief Will not leave the group, just stop the call. * @note Call from the GUI thread. * @param groupId Id of group to leave */ void CoreAV::leaveGroupCall(int groupId) { QWriteLocker locker{&callsLock}; qDebug() << QString("Leaving group call %1").arg(groupId); groupCalls.erase(groupId); } bool CoreAV::sendGroupCallAudio(int groupId, const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) const { QReadLocker locker{&callsLock}; std::map::const_iterator it = groupCalls.find(groupId); if (it == groupCalls.end()) { return false; } if (!it->second->isActive() || it->second->getMuteMic()) { return true; } if (toxav_group_send_audio(toxav_get_tox(toxav.get()), groupId, pcm, samples, chans, rate) != 0) qDebug() << "toxav_group_send_audio error"; return true; } /** * @brief Mutes or unmutes the group call's input (microphone). * @param g The group * @param mute True to mute, false to unmute */ void CoreAV::muteCallInput(const Group* g, bool mute) { QWriteLocker locker{&callsLock}; auto it = groupCalls.find(g->getId()); if (g && (it != groupCalls.end())) { it->second->setMuteMic(mute); } } /** * @brief Mutes or unmutes the group call's output (speaker). * @param g The group * @param mute True to mute, false to unmute */ void CoreAV::muteCallOutput(const Group* g, bool mute) { QWriteLocker locker{&callsLock}; auto it = groupCalls.find(g->getId()); if (g && (it != groupCalls.end())) { it->second->setMuteVol(mute); } } /** * @brief Returns the group calls input (microphone) state. * @param groupId The group id to check * @return true when muted, false otherwise */ bool CoreAV::isGroupCallInputMuted(const Group* g) const { QReadLocker locker{&callsLock}; if (!g) { return false; } const uint32_t groupId = g->getId(); auto it = groupCalls.find(groupId); return (it != groupCalls.end()) && it->second->getMuteMic(); } /** * @brief Returns the group calls output (speaker) state. * @param groupId The group id to check * @return true when muted, false otherwise */ bool CoreAV::isGroupCallOutputMuted(const Group* g) const { QReadLocker locker{&callsLock}; if (!g) { return false; } const uint32_t groupId = g->getId(); auto it = groupCalls.find(groupId); return (it != groupCalls.end()) && it->second->getMuteVol(); } /** * @brief Returns the calls input (microphone) mute state. * @param f The friend to check * @return true when muted, false otherwise */ bool CoreAV::isCallInputMuted(const Friend* f) const { QReadLocker locker{&callsLock}; if (!f) { return false; } const uint32_t friendId = f->getId(); auto it = calls.find(friendId); return (it != calls.end()) && it->second->getMuteMic(); } /** * @brief Returns the calls output (speaker) mute state. * @param friendId The friend to check * @return true when muted, false otherwise */ bool CoreAV::isCallOutputMuted(const Friend* f) const { QReadLocker locker{&callsLock}; if (!f) { return false; } const uint32_t friendId = f->getId(); auto it = calls.find(friendId); return (it != calls.end()) && it->second->getMuteVol(); } /** * @brief Signal to all peers that we're not sending video anymore. * @note The next frame sent cancels this. */ void CoreAV::sendNoVideo() { QWriteLocker locker{&callsLock}; // We don't change the audio bitrate, but we signal that we're not sending video anymore qDebug() << "CoreAV: Signaling end of video sending"; for (auto& kv : calls) { ToxFriendCall& call = *kv.second; toxav_video_set_bit_rate(toxav.get(), kv.first, 0, nullptr); call.setNullVideoBitrate(true); } } void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool video, void* vSelf) { CoreAV* self = static_cast(vSelf); QWriteLocker locker{&self->callsLock}; // Audio backend must be set before receiving a call assert(self->audio != nullptr); ToxFriendCallPtr call = ToxFriendCallPtr(new ToxFriendCall{friendNum, video, *self, *self->audio}); // Call object must be owned by CoreAV thread or there will be locking problems with Audio call->moveToThread(self->thread()); assert(call != nullptr); auto it = self->calls.emplace(friendNum, std::move(call)); if (it.second == false) { qWarning() << QString("Rejecting call invite from %1, we're already in that call!").arg(friendNum); toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr); return; } qDebug() << QString("Received call invite from %1").arg(friendNum); // We don't get a state callback when answering, so fill the state ourselves in advance int state = 0; if (audio) state |= TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_ACCEPTING_A; if (video) state |= TOXAV_FRIEND_CALL_STATE_SENDING_V | TOXAV_FRIEND_CALL_STATE_ACCEPTING_V; it.first->second->setState(static_cast(state)); // Must explicitely unlock, because a deadlock can happen via ChatForm/Audio locker.unlock(); emit self->avInvite(friendNum, video); } void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, void* vSelf) { Q_UNUSED(toxav); CoreAV* self = static_cast(vSelf); // we must unlock this lock before emitting any signals QWriteLocker locker{&self->callsLock}; auto it = self->calls.find(friendNum); if (it == self->calls.end()) { qWarning() << QString("stateCallback called, but call %1 is already dead").arg(friendNum); return; } ToxFriendCall& call = *it->second; if (state & TOXAV_FRIEND_CALL_STATE_ERROR) { qWarning() << "Call with friend" << friendNum << "died of unnatural causes!"; self->calls.erase(friendNum); locker.unlock(); emit self->avEnd(friendNum, true); } else if (state & TOXAV_FRIEND_CALL_STATE_FINISHED) { qDebug() << "Call with friend" << friendNum << "finished quietly"; self->calls.erase(friendNum); locker.unlock(); emit self->avEnd(friendNum); } else { // If our state was null, we started the call and were still ringing if (!call.getState() && state) { call.setActive(true); bool videoEnabled = call.getVideoEnabled(); call.setState(static_cast(state)); locker.unlock(); emit self->avStart(friendNum, videoEnabled); } else if ((call.getState() & TOXAV_FRIEND_CALL_STATE_SENDING_V) && !(state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) { qDebug() << "Friend" << friendNum << "stopped sending video"; if (call.getVideoSource()) { call.getVideoSource()->stopSource(); } call.setState(static_cast(state)); } else if (!(call.getState() & TOXAV_FRIEND_CALL_STATE_SENDING_V) && (state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) { // Workaround toxav sometimes firing callbacks for "send last frame" -> "stop sending // video" // out of orders (even though they were sent in order by the other end). // We simply stop the videoSource from emitting anything while the other end says it's // not sending if (call.getVideoSource()) { call.getVideoSource()->restartSource(); } call.setState(static_cast(state)); } } } // This is only a dummy implementation for now void CoreAV::bitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t arate, uint32_t vrate, void* vSelf) { CoreAV* self = static_cast(vSelf); Q_UNUSED(self); Q_UNUSED(toxav); qDebug() << "Recommended bitrate with" << friendNum << " is now " << arate << "/" << vrate << ", ignoring it"; } // This is only a dummy implementation for now void CoreAV::audioBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rate, void* vSelf) { CoreAV* self = static_cast(vSelf); Q_UNUSED(self); Q_UNUSED(toxav); qDebug() << "Recommended audio bitrate with" << friendNum << " is now " << rate << ", ignoring it"; } // This is only a dummy implementation for now void CoreAV::videoBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rate, void* vSelf) { CoreAV* self = static_cast(vSelf); Q_UNUSED(self); Q_UNUSED(toxav); qDebug() << "Recommended video bitrate with" << friendNum << " is now " << rate << ", ignoring it"; } void CoreAV::audioFrameCallback(ToxAV*, uint32_t friendNum, const int16_t* pcm, size_t sampleCount, uint8_t channels, uint32_t samplingRate, void* vSelf) { CoreAV* self = static_cast(vSelf); // This callback should come from the CoreAV thread assert(QThread::currentThread() == self->coreavThread.get()); QReadLocker locker{&self->callsLock}; auto it = self->calls.find(friendNum); if (it == self->calls.end()) { return; } ToxFriendCall& call = *it->second; if (call.getMuteVol()) { return; } call.playAudioBuffer(pcm, sampleCount, channels, samplingRate); } void CoreAV::videoFrameCallback(ToxAV*, uint32_t friendNum, uint16_t w, uint16_t h, const uint8_t* y, const uint8_t* u, const uint8_t* v, int32_t ystride, int32_t ustride, int32_t vstride, void* vSelf) { auto self = static_cast(vSelf); // This callback should come from the CoreAV thread assert(QThread::currentThread() == self->coreavThread.get()); QReadLocker locker{&self->callsLock}; auto it = self->calls.find(friendNum); if (it == self->calls.end()) { return; } CoreVideoSource* videoSource = it->second->getVideoSource(); if (!videoSource) { return; } vpx_image frame; frame.d_h = h; frame.d_w = w; frame.planes[0] = const_cast(y); frame.planes[1] = const_cast(u); frame.planes[2] = const_cast(v); frame.stride[0] = ystride; frame.stride[1] = ustride; frame.stride[2] = vstride; videoSource->pushFrame(&frame); } qTox/src/core/coreav.h000066400000000000000000000135571415623743500152050ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef COREAV_H #define COREAV_H #include "src/core/toxcall.h" #include "src/util/compatiblerecursivemutex.h" #include #include #include #include #include #include class Friend; class Group; class IAudioControl; class QThread; class QTimer; class CoreVideoSource; class CameraSource; class VideoSource; class VideoFrame; class Core; struct vpx_image; class CoreAV : public QObject { Q_OBJECT public: using CoreAVPtr = std::unique_ptr; static CoreAVPtr makeCoreAV(Tox* core, CompatibleRecursiveMutex& coreLock); void setAudio(IAudioControl& newAudio); IAudioControl* getAudio(); ~CoreAV(); bool isCallStarted(const Friend* f) const; bool isCallStarted(const Group* f) const; bool isCallActive(const Friend* f) const; bool isCallActive(const Group* g) const; bool isCallVideoEnabled(const Friend* f) const; bool sendCallAudio(uint32_t friendNum, const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) const; void sendCallVideo(uint32_t friendNum, std::shared_ptr frame); bool sendGroupCallAudio(int groupNum, const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) const; VideoSource* getVideoSourceFromCall(int callNumber) const; void sendNoVideo(); void joinGroupCall(const Group& group); void leaveGroupCall(int groupNum); void muteCallInput(const Group* g, bool mute); void muteCallOutput(const Group* g, bool mute); bool isGroupCallInputMuted(const Group* g) const; bool isGroupCallOutputMuted(const Group* g) const; bool isCallInputMuted(const Friend* f) const; bool isCallOutputMuted(const Friend* f) const; void toggleMuteCallInput(const Friend* f); void toggleMuteCallOutput(const Friend* f); static void groupCallCallback(void* tox, uint32_t group, uint32_t peer, const int16_t* data, unsigned samples, uint8_t channels, uint32_t sample_rate, void* core); void invalidateGroupCallPeerSource(int group, ToxPk peerPk); public slots: bool startCall(uint32_t friendNum, bool video); bool answerCall(uint32_t friendNum, bool video); bool cancelCall(uint32_t friendNum); void timeoutCall(uint32_t friendNum); void start(); signals: void avInvite(uint32_t friendId, bool video); void avStart(uint32_t friendId, bool video); void avEnd(uint32_t friendId, bool error = false); private slots: static void callCallback(ToxAV* toxAV, uint32_t friendNum, bool audio, bool video, void* self); static void stateCallback(ToxAV*, uint32_t friendNum, uint32_t state, void* self); static void bitrateCallback(ToxAV* toxAV, uint32_t friendNum, uint32_t arate, uint32_t vrate, void* self); static void audioBitrateCallback(ToxAV* toxAV, uint32_t friendNum, uint32_t rate, void* self); static void videoBitrateCallback(ToxAV* toxAV, uint32_t friendNum, uint32_t rate, void* self); private: struct ToxAVDeleter { void operator()(ToxAV* tox) { toxav_kill(tox); } }; CoreAV(std::unique_ptr tox, CompatibleRecursiveMutex &toxCoreLock); void connectCallbacks(ToxAV& toxav); void process(); static void audioFrameCallback(ToxAV* toxAV, uint32_t friendNum, const int16_t* pcm, size_t sampleCount, uint8_t channels, uint32_t samplingRate, void* self); static void videoFrameCallback(ToxAV* toxAV, uint32_t friendNum, uint16_t w, uint16_t h, const uint8_t* y, const uint8_t* u, const uint8_t* v, int32_t ystride, int32_t ustride, int32_t vstride, void* self); private: static constexpr uint32_t VIDEO_DEFAULT_BITRATE = 2500; private: // atomic because potentially accessed by different threads std::atomic audio; std::unique_ptr toxav; std::unique_ptr coreavThread; QTimer* iterateTimer = nullptr; using ToxFriendCallPtr = std::unique_ptr; /** * @brief Maps friend IDs to ToxFriendCall. * @note Need to use STL container here, because Qt containers need a copy constructor. */ std::map calls; using ToxGroupCallPtr = std::unique_ptr; /** * @brief Maps group IDs to ToxGroupCalls. * @note Need to use STL container here, because Qt containers need a copy constructor. */ std::map groupCalls; // protect 'calls' and 'groupCalls' mutable QReadWriteLock callsLock{QReadWriteLock::Recursive}; /** * @brief needed to synchronize with the Core thread, some toxav_* functions * must not execute at the same time as tox_iterate() * @note This must be a recursive mutex as we're going to lock it in callbacks */ CompatibleRecursiveMutex& coreLock; }; #endif // COREAV_H qTox/src/core/corefile.cpp000066400000000000000000000501411415623743500160370ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "corefile.h" #include "core.h" #include "toxfile.h" #include "toxstring.h" #include "src/persistence/settings.h" #include "src/model/status.h" #include "src/model/toxclientstandards.h" #include "src/util/compatiblerecursivemutex.h" #include #include #include #include #include #include #include /** * @class CoreFile * @brief Manages the file transfer service of toxcore */ CoreFilePtr CoreFile::makeCoreFile(Core *core, Tox *tox, CompatibleRecursiveMutex &coreLoopLock) { assert(core != nullptr); assert(tox != nullptr); connectCallbacks(*tox); CoreFilePtr result = CoreFilePtr{new CoreFile{tox, coreLoopLock}}; connect(core, &Core::friendStatusChanged, result.get(), &CoreFile::onConnectionStatusChanged); return result; } CoreFile::CoreFile(Tox *core, CompatibleRecursiveMutex &coreLoopLock) : tox{core} , coreLoopLock{&coreLoopLock} { } /** * @brief Get corefile iteration interval. * * tox_iterate calls to get good file transfer performances * @return The maximum amount of time in ms that Core should wait between two tox_iterate() calls. */ unsigned CoreFile::corefileIterationInterval() { /* Sleep at most 1000ms if we have no FT, 10 for user FTs There is no real difference between 10ms sleep and 50ms sleep when it comes to CPU usage – just keep the CPU usage low when there are no file transfers, and speed things up when there is an ongoing file transfer. */ constexpr unsigned fileInterval = 10, idleInterval = 1000; for (ToxFile& file : fileMap) { if (file.status == ToxFile::TRANSMITTING) { return fileInterval; } } return idleInterval; } void CoreFile::connectCallbacks(Tox &tox) { // be careful not to to reconnect already used callbacks here tox_callback_file_chunk_request(&tox, CoreFile::onFileDataCallback); tox_callback_file_recv(&tox, CoreFile::onFileReceiveCallback); tox_callback_file_recv_chunk(&tox, CoreFile::onFileRecvChunkCallback); tox_callback_file_recv_control(&tox, CoreFile::onFileControlCallback); } void CoreFile::sendAvatarFile(uint32_t friendId, const QByteArray& data) { QMutexLocker{coreLoopLock}; uint64_t filesize = 0; uint8_t *file_id = nullptr; uint8_t *file_name = nullptr; size_t nameLength = 0; uint8_t avatarHash[TOX_HASH_LENGTH]; if (!data.isEmpty()) { static_assert(TOX_HASH_LENGTH <= TOX_FILE_ID_LENGTH, "TOX_HASH_LENGTH > TOX_FILE_ID_LENGTH!"); tox_hash(avatarHash, (uint8_t*)data.data(), data.size()); filesize = data.size(); file_id = avatarHash; file_name = avatarHash; nameLength = TOX_HASH_LENGTH; } Tox_Err_File_Send error; const uint32_t fileNum = tox_file_send(tox, friendId, TOX_FILE_KIND_AVATAR, filesize, file_id, file_name, nameLength, &error); switch (error) { case TOX_ERR_FILE_SEND_OK: break; case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED: qCritical() << "Friend not connected"; return; case TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND: qCritical() << "Friend not found"; return; case TOX_ERR_FILE_SEND_NAME_TOO_LONG: qCritical() << "Name too long"; return; case TOX_ERR_FILE_SEND_NULL: qCritical() << "Send null"; return; case TOX_ERR_FILE_SEND_TOO_MANY: qCritical() << "To many ougoing transfer"; return; default: return; } ToxFile file{fileNum, friendId, "", "", ToxFile::SENDING}; file.filesize = filesize; file.fileKind = TOX_FILE_KIND_AVATAR; file.avatarData = data; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); tox_file_get_file_id(tox, friendId, fileNum, (uint8_t*)file.resumeFileId.data(), nullptr); addFile(friendId, fileNum, file); } void CoreFile::sendFile(uint32_t friendId, QString filename, QString filePath, long long filesize) { QMutexLocker{coreLoopLock}; ToxString fileName(filename); TOX_ERR_FILE_SEND sendErr; uint32_t fileNum = tox_file_send(tox, friendId, TOX_FILE_KIND_DATA, filesize, nullptr, fileName.data(), fileName.size(), &sendErr); if (sendErr != TOX_ERR_FILE_SEND_OK) { qWarning() << "sendFile: Can't create the Tox file sender (" << sendErr << ")"; emit fileSendFailed(friendId, fileName.getQString()); return; } qDebug() << QString("sendFile: Created file sender %1 with friend %2").arg(fileNum).arg(friendId); ToxFile file{fileNum, friendId, fileName.getQString(), filePath, ToxFile::SENDING}; file.filesize = filesize; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); tox_file_get_file_id(tox, friendId, fileNum, (uint8_t*)file.resumeFileId.data(), nullptr); if (!file.open(false)) { qWarning() << QString("sendFile: Can't open file, error: %1").arg(file.file->errorString()); } addFile(friendId, fileNum, file); emit fileSendStarted(file); } void CoreFile::pauseResumeFile(uint32_t friendId, uint32_t fileId) { QMutexLocker{coreLoopLock}; ToxFile* file = findFile(friendId, fileId); if (!file) { qWarning("pauseResumeFileSend: No such file in queue"); return; } if (file->status != ToxFile::TRANSMITTING && file->status != ToxFile::PAUSED) { qWarning() << "pauseResumeFileSend: File is stopped"; return; } file->pauseStatus.localPauseToggle(); if (file->pauseStatus.paused()) { file->status = ToxFile::PAUSED; emit fileTransferPaused(*file); } else { file->status = ToxFile::TRANSMITTING; emit fileTransferAccepted(*file); } if (file->pauseStatus.localPaused()) { tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, nullptr); } else { tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); } } void CoreFile::cancelFileSend(uint32_t friendId, uint32_t fileId) { QMutexLocker{coreLoopLock}; ToxFile* file = findFile(friendId, fileId); if (!file) { qWarning("cancelFileSend: No such file in queue"); return; } file->status = ToxFile::CANCELED; emit fileTransferCancelled(*file); tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); } void CoreFile::cancelFileRecv(uint32_t friendId, uint32_t fileId) { QMutexLocker{coreLoopLock}; ToxFile* file = findFile(friendId, fileId); if (!file) { qWarning("cancelFileRecv: No such file in queue"); return; } file->status = ToxFile::CANCELED; emit fileTransferCancelled(*file); tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); } void CoreFile::rejectFileRecvRequest(uint32_t friendId, uint32_t fileId) { QMutexLocker{coreLoopLock}; ToxFile* file = findFile(friendId, fileId); if (!file) { qWarning("rejectFileRecvRequest: No such file in queue"); return; } file->status = ToxFile::CANCELED; emit fileTransferCancelled(*file); tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); } void CoreFile::acceptFileRecvRequest(uint32_t friendId, uint32_t fileId, QString path) { QMutexLocker{coreLoopLock}; ToxFile* file = findFile(friendId, fileId); if (!file) { qWarning("acceptFileRecvRequest: No such file in queue"); return; } file->setFilePath(path); if (!file->open(true)) { qWarning() << "acceptFileRecvRequest: Unable to open file"; return; } file->status = ToxFile::TRANSMITTING; emit fileTransferAccepted(*file); tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); } ToxFile* CoreFile::findFile(uint32_t friendId, uint32_t fileId) { QMutexLocker{coreLoopLock}; uint64_t key = getFriendKey(friendId, fileId); if (fileMap.contains(key)) { return &fileMap[key]; } qWarning() << "findFile: File transfer with ID" << friendId << ':' << fileId << "doesn't exist"; return nullptr; } void CoreFile::addFile(uint32_t friendId, uint32_t fileId, const ToxFile& file) { uint64_t key = getFriendKey(friendId, fileId); if (fileMap.contains(key)) { qWarning() << "addFile: Overwriting existing file transfer with same ID" << friendId << ':' << fileId; } fileMap.insert(key, file); } void CoreFile::removeFile(uint32_t friendId, uint32_t fileId) { uint64_t key = getFriendKey(friendId, fileId); if (!fileMap.contains(key)) { qWarning() << "removeFile: No such file in queue"; return; } fileMap[key].file->close(); fileMap.remove(key); } QString CoreFile::getCleanFileName(QString filename) { QRegularExpression regex{QStringLiteral(R"([<>:"/\\|?])")}; filename.replace(regex, "_"); return filename; } void CoreFile::onFileReceiveCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint32_t kind, uint64_t filesize, const uint8_t* fname, size_t fnameLen, void* vCore) { Core* core = static_cast(vCore); CoreFile* coreFile = core->getCoreFile(); auto filename = ToxString(fname, fnameLen); const ToxPk friendPk = core->getFriendPublicKey(friendId); if (kind == TOX_FILE_KIND_AVATAR) { if (!filesize) { qDebug() << QString("Received empty avatar request %1:%2").arg(friendId).arg(fileId); // Avatars of size 0 means explicitely no avatar tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); emit core->friendAvatarRemoved(core->getFriendPublicKey(friendId)); return; } else { if (!ToxClientStandards::IsValidAvatarSize(filesize)) { qWarning() << QString("Received avatar request from %1 with size %2.").arg(friendId).arg(filesize) + QString(" The max size allowed for avatars is %3. Cancelling transfer.").arg(ToxClientStandards::MaxAvatarSize); tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); return; } static_assert(TOX_HASH_LENGTH <= TOX_FILE_ID_LENGTH, "TOX_HASH_LENGTH > TOX_FILE_ID_LENGTH!"); uint8_t avatarHash[TOX_FILE_ID_LENGTH]; tox_file_get_file_id(tox, friendId, fileId, avatarHash, nullptr); QByteArray avatarBytes{static_cast(static_cast(avatarHash)), TOX_HASH_LENGTH}; emit core->fileAvatarOfferReceived(friendId, fileId, avatarBytes); return; } } else { const auto cleanFileName = CoreFile::getCleanFileName(filename.getQString()); if (cleanFileName != filename.getQString()) { qDebug() << QStringLiteral("Cleaned filename"); filename = ToxString(cleanFileName); emit coreFile->fileNameChanged(friendPk); } else { qDebug() << QStringLiteral("filename already clean"); } qDebug() << QString("Received file request %1:%2 kind %3").arg(friendId).arg(fileId).arg(kind); } ToxFile file{fileId, friendId, filename.getBytes(), "", ToxFile::RECEIVING}; file.filesize = filesize; file.fileKind = kind; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); tox_file_get_file_id(tox, friendId, fileId, (uint8_t*)file.resumeFileId.data(), nullptr); coreFile->addFile(friendId, fileId, file); if (kind != TOX_FILE_KIND_AVATAR) { emit coreFile->fileReceiveRequested(file); } } // TODO(sudden6): This whole method is a mess but needed to get stuff working for now void CoreFile::handleAvatarOffer(uint32_t friendId, uint32_t fileId, bool accept) { if (!accept) { // If it's an avatar but we already have it cached, cancel qDebug() << QString("Received avatar request %1:%2, reject, since we have it in cache.") .arg(friendId) .arg(fileId); tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); return; } // It's an avatar and we don't have it, autoaccept the transfer qDebug() << QString("Received avatar request %1:%2, accept, since we don't have it " "in cache.") .arg(friendId) .arg(fileId); tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_RESUME, nullptr); ToxFile file{fileId, friendId, "", "", ToxFile::RECEIVING}; file.filesize = 0; file.fileKind = TOX_FILE_KIND_AVATAR; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); tox_file_get_file_id(tox, friendId, fileId, (uint8_t*)file.resumeFileId.data(), nullptr); addFile(friendId, fileId, file); } void CoreFile::onFileControlCallback(Tox*, uint32_t friendId, uint32_t fileId, Tox_File_Control control, void* vCore) { Core* core = static_cast(vCore); CoreFile* coreFile = core->getCoreFile(); ToxFile* file = coreFile->findFile(friendId, fileId); if (!file) { qWarning("onFileControlCallback: No such file in queue"); return; } if (control == TOX_FILE_CONTROL_CANCEL) { if (file->fileKind != TOX_FILE_KIND_AVATAR) qDebug() << "File tranfer" << friendId << ":" << fileId << "cancelled by friend"; file->status = ToxFile::CANCELED; emit coreFile->fileTransferCancelled(*file); coreFile->removeFile(friendId, fileId); } else if (control == TOX_FILE_CONTROL_PAUSE) { qDebug() << "onFileControlCallback: Received pause for file " << friendId << ":" << fileId; file->pauseStatus.remotePause(); file->status = ToxFile::PAUSED; emit coreFile->fileTransferRemotePausedUnpaused(*file, true); } else if (control == TOX_FILE_CONTROL_RESUME) { if (file->direction == ToxFile::SENDING && file->fileKind == TOX_FILE_KIND_AVATAR) qDebug() << "Avatar transfer" << fileId << "to friend" << friendId << "accepted"; else qDebug() << "onFileControlCallback: Received resume for file " << friendId << ":" << fileId; file->pauseStatus.remoteResume(); file->status = file->pauseStatus.paused() ? ToxFile::PAUSED : ToxFile::TRANSMITTING; emit coreFile->fileTransferRemotePausedUnpaused(*file, false); } else { qWarning() << "Unhandled file control " << control << " for file " << friendId << ':' << fileId; } } void CoreFile::onFileDataCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint64_t pos, size_t length, void* vCore) { Core* core = static_cast(vCore); CoreFile* coreFile = core->getCoreFile(); ToxFile* file = coreFile->findFile(friendId, fileId); if (!file) { qWarning("onFileDataCallback: No such file in queue"); return; } // If we reached EOF, ack and cleanup the transfer if (!length) { file->status = ToxFile::FINISHED; if (file->fileKind != TOX_FILE_KIND_AVATAR) { emit coreFile->fileTransferFinished(*file); emit coreFile->fileUploadFinished(file->filePath); } coreFile->removeFile(friendId, fileId); return; } std::unique_ptr data(new uint8_t[length]); int64_t nread; if (file->fileKind == TOX_FILE_KIND_AVATAR) { QByteArray chunk = file->avatarData.mid(pos, length); nread = chunk.size(); memcpy(data.get(), chunk.data(), nread); } else { file->file->seek(pos); nread = file->file->read((char*)data.get(), length); if (nread <= 0) { qWarning("onFileDataCallback: Failed to read from file"); file->status = ToxFile::CANCELED; emit coreFile->fileTransferCancelled(*file); tox_file_send_chunk(tox, friendId, fileId, pos, nullptr, 0, nullptr); coreFile->removeFile(friendId, fileId); return; } file->bytesSent += length; file->hashGenerator->addData((const char*)data.get(), length); } if (!tox_file_send_chunk(tox, friendId, fileId, pos, data.get(), nread, nullptr)) { qWarning("onFileDataCallback: Failed to send data chunk"); return; } if (file->fileKind != TOX_FILE_KIND_AVATAR) { emit coreFile->fileTransferInfo(*file); } } void CoreFile::onFileRecvChunkCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint64_t position, const uint8_t* data, size_t length, void* vCore) { Core* core = static_cast(vCore); CoreFile* coreFile = core->getCoreFile(); ToxFile* file = coreFile->findFile(friendId, fileId); if (!file) { qWarning("onFileRecvChunkCallback: No such file in queue"); tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); return; } if (file->bytesSent != position) { qWarning("onFileRecvChunkCallback: Received a chunk out-of-order, aborting transfer"); if (file->fileKind != TOX_FILE_KIND_AVATAR) { file->status = ToxFile::CANCELED; emit coreFile->fileTransferCancelled(*file); } tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); coreFile->removeFile(friendId, fileId); return; } if (!length) { file->status = ToxFile::FINISHED; if (file->fileKind == TOX_FILE_KIND_AVATAR) { QPixmap pic; pic.loadFromData(file->avatarData); if (!pic.isNull()) { qDebug() << "Got" << file->avatarData.size() << "bytes of avatar data from" << friendId; emit core->friendAvatarChanged(core->getFriendPublicKey(friendId), file->avatarData); } } else { emit coreFile->fileTransferFinished(*file); emit coreFile->fileDownloadFinished(file->filePath); } coreFile->removeFile(friendId, fileId); return; } if (file->fileKind == TOX_FILE_KIND_AVATAR) { file->avatarData.append((char*)data, length); } else { file->file->write((char*)data, length); } file->bytesSent += length; file->hashGenerator->addData((const char*)data, length); if (file->fileKind != TOX_FILE_KIND_AVATAR) { emit coreFile->fileTransferInfo(*file); } } void CoreFile::onConnectionStatusChanged(uint32_t friendId, Status::Status state) { bool isOffline = state == Status::Status::Offline; // TODO: Actually resume broken file transfers // We need to: // - Start a new file transfer with the same 32byte file ID with toxcore // - Seek to the correct position again // - Update the fileNum in our ToxFile // - Update the users of our signals to check the 32byte tox file ID, not the uint32_t file_num // (fileId) ToxFile::FileStatus status = !isOffline ? ToxFile::TRANSMITTING : ToxFile::BROKEN; for (uint64_t key : fileMap.keys()) { if (key >> 32 != friendId) continue; fileMap[key].status = status; emit fileTransferBrokenUnbroken(fileMap[key], isOffline); } } qTox/src/core/corefile.h000066400000000000000000000101301415623743500154760ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef COREFILE_H #define COREFILE_H #include #include "toxfile.h" #include "src/core/core.h" #include "src/core/toxpk.h" #include "src/model/status.h" #include "src/util/compatiblerecursivemutex.h" #include #include #include #include #include #include #include struct Tox; class CoreFile; using CoreFilePtr = std::unique_ptr; class CoreFile : public QObject { Q_OBJECT public: void handleAvatarOffer(uint32_t friendId, uint32_t fileId, bool accept); static CoreFilePtr makeCoreFile(Core* core, Tox* tox, CompatibleRecursiveMutex& coreLoopLock); void sendFile(uint32_t friendId, QString filename, QString filePath, long long filesize); void sendAvatarFile(uint32_t friendId, const QByteArray& data); void pauseResumeFile(uint32_t friendId, uint32_t fileId); void cancelFileSend(uint32_t friendId, uint32_t fileId); void cancelFileRecv(uint32_t friendId, uint32_t fileId); void rejectFileRecvRequest(uint32_t friendId, uint32_t fileId); void acceptFileRecvRequest(uint32_t friendId, uint32_t fileId, QString path); unsigned corefileIterationInterval(); signals: void fileSendStarted(ToxFile file); void fileReceiveRequested(ToxFile file); void fileTransferAccepted(ToxFile file); void fileTransferCancelled(ToxFile file); void fileTransferFinished(ToxFile file); void fileUploadFinished(const QString& path); void fileDownloadFinished(const QString& path); void fileTransferPaused(ToxFile file); void fileTransferInfo(ToxFile file); void fileTransferRemotePausedUnpaused(ToxFile file, bool paused); void fileTransferBrokenUnbroken(ToxFile file, bool broken); void fileNameChanged(const ToxPk& friendPk); void fileSendFailed(uint32_t friendId, const QString& fname); private: CoreFile(Tox* core, CompatibleRecursiveMutex& coreLoopLock); ToxFile* findFile(uint32_t friendId, uint32_t fileId); void addFile(uint32_t friendId, uint32_t fileId, const ToxFile& file); void removeFile(uint32_t friendId, uint32_t fileId); static constexpr uint64_t getFriendKey(uint32_t friendId, uint32_t fileId) { return (static_cast(friendId) << 32) + fileId; } static void connectCallbacks(Tox& tox); static void onFileReceiveCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint32_t kind, uint64_t filesize, const uint8_t* fname, size_t fnameLen, void* vCore); static void onFileControlCallback(Tox* tox, uint32_t friendId, uint32_t fileId, Tox_File_Control control, void* vCore); static void onFileDataCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint64_t pos, size_t length, void* vCore); static void onFileRecvChunkCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint64_t position, const uint8_t* data, size_t length, void* vCore); static QString getCleanFileName(QString filename); private slots: void onConnectionStatusChanged(uint32_t friendId, Status::Status state); private: QHash fileMap; Tox* tox; CompatibleRecursiveMutex* coreLoopLock = nullptr; }; #endif // COREFILE_H qTox/src/core/dhtserver.cpp000066400000000000000000000025311415623743500162550ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "dhtserver.h" /** * @brief Compare equal operator * @param other the compared instance * @return true, if equal; false otherwise */ bool DhtServer::operator==(const DhtServer& other) const { return this == &other || (port == other.port && address == other.address && userId == other.userId && name == other.name); } /** * @brief Compare not equal operator * @param other the compared instance * @return true, if not equal; false otherwise */ bool DhtServer::operator!=(const DhtServer& other) const { return !(*this == other); } qTox/src/core/dhtserver.h000066400000000000000000000020421415623743500157170ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef DHT_SERVER_H #define DHT_SERVER_H #include struct DhtServer { QString name; QString userId; QString address; quint16 port; bool operator==(const DhtServer& other) const; bool operator!=(const DhtServer& other) const; }; #endif // DHT_SERVER_H qTox/src/core/groupid.cpp000066400000000000000000000040231415623743500157160ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupid.h" #include #include #include #include /** * @class GroupId * @brief This class represents a long term persistent group identifier. */ /** * @brief The default constructor. Creates an empty Tox group ID. */ GroupId::GroupId() : ContactId() { } /** * @brief The copy constructor. * @param other GroupId to copy */ GroupId::GroupId(const GroupId& other) : ContactId(other.id) { } /** * @brief Constructs a GroupId from bytes. * @param rawId The bytes to construct the GroupId from. The lenght must be exactly * TOX_CONFERENCE_UID_SIZE, else the GroupId will be empty. */ GroupId::GroupId(const QByteArray& rawId) : ContactId([rawId](){ assert(rawId.length() == TOX_CONFERENCE_UID_SIZE); return rawId;}()) { } /** * @brief Constructs a GroupId from bytes. * @param rawId The bytes to construct the GroupId from, will read exactly * TOX_CONFERENCE_UID_SIZE from the specified buffer. */ GroupId::GroupId(const uint8_t* rawId) : ContactId(QByteArray(reinterpret_cast(rawId), TOX_CONFERENCE_UID_SIZE)) { } /** * @brief Get size of public id in bytes. * @return Size of public id in bytes. */ int GroupId::getSize() const { return TOX_CONFERENCE_UID_SIZE; } qTox/src/core/groupid.h000066400000000000000000000021351415623743500153650ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUPID_H #define GROUPID_H #include "src/core/contactid.h" #include #include class GroupId : public ContactId { public: GroupId(); GroupId(const GroupId& other); explicit GroupId(const QByteArray& rawId); explicit GroupId(const uint8_t* rawId); int getSize() const override; }; #endif // GROUPID_H qTox/src/core/icorefriendmessagesender.h000066400000000000000000000023271415623743500207560ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ICORE_FRIEND_MESSAGE_SENDER_H #define ICORE_FRIEND_MESSAGE_SENDER_H #include "receiptnum.h" #include #include class ICoreFriendMessageSender { public: virtual ~ICoreFriendMessageSender() = default; virtual bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) = 0; virtual bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) = 0; }; #endif /* ICORE_FRIEND_MESSAGE_SENDER_H */ qTox/src/core/icoregroupmessagesender.h000066400000000000000000000021701415623743500206370ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ICORE_GROUP_MESSAGE_SENDER_H #define ICORE_GROUP_MESSAGE_SENDER_H #include class ICoreGroupMessageSender { public: virtual ~ICoreGroupMessageSender() = default; virtual void sendGroupAction(int groupId, const QString& message) = 0; virtual void sendGroupMessage(int groupId, const QString& message) = 0; }; #endif /*ICORE_GROUP_MESSAGE_SENDER_H*/ qTox/src/core/icoregroupquery.h000066400000000000000000000027521415623743500171650ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ICORE_GROUP_QUERY_H #define ICORE_GROUP_QUERY_H #include "groupid.h" #include "toxpk.h" #include #include #include class ICoreGroupQuery { public: virtual ~ICoreGroupQuery() = default; virtual GroupId getGroupPersistentId(uint32_t groupNumber) const = 0; virtual uint32_t getGroupNumberPeers(int groupId) const = 0; virtual QString getGroupPeerName(int groupId, int peerId) const = 0; virtual ToxPk getGroupPeerPk(int groupId, int peerId) const = 0; virtual QStringList getGroupPeerNames(int groupId) const = 0; virtual bool getGroupAvEnabled(int groupId) const = 0; }; #endif /*ICORE_GROUP_QUERY_H*/ qTox/src/core/icoreidhandler.h000066400000000000000000000021141415623743500166650ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ICORE_ID_HANDLER_H #define ICORE_ID_HANDLER_H #include "toxid.h" #include "toxpk.h" class ICoreIdHandler { public: virtual ~ICoreIdHandler() = default; virtual ToxId getSelfId() const = 0; virtual ToxPk getSelfPublicKey() const = 0; virtual QString getUsername() const = 0; }; #endif /*ICORE_ID_HANDLER_H*/ qTox/src/core/icoresettings.h000066400000000000000000000042131415623743500165750ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef I_CORE_SETTINGS_H #define I_CORE_SETTINGS_H #include "src/model/interface.h" #include #include #include class ICoreSettings { public: enum class ProxyType { // If changed, don't forget to update Settings::fixInvalidProxyType ptNone = 0, ptSOCKS5 = 1, ptHTTP = 2 }; virtual ~ICoreSettings() = default; virtual bool getEnableIPv6() const = 0; virtual void setEnableIPv6(bool enable) = 0; virtual bool getForceTCP() const = 0; virtual void setForceTCP(bool enable) = 0; virtual bool getEnableLanDiscovery() const = 0; virtual void setEnableLanDiscovery(bool enable) = 0; virtual QString getProxyAddr() const = 0; virtual void setProxyAddr(const QString& address) = 0; virtual ProxyType getProxyType() const = 0; virtual void setProxyType(ProxyType type) = 0; virtual quint16 getProxyPort() const = 0; virtual void setProxyPort(quint16 port) = 0; virtual QNetworkProxy getProxy() const = 0; DECLARE_SIGNAL(enableIPv6Changed, bool enabled); DECLARE_SIGNAL(forceTCPChanged, bool enabled); DECLARE_SIGNAL(enableLanDiscoveryChanged, bool enabled); DECLARE_SIGNAL(proxyTypeChanged, ICoreSettings::ProxyType type); DECLARE_SIGNAL(proxyAddressChanged, const QString& address); DECLARE_SIGNAL(proxyPortChanged, quint16 port); }; #endif // I_CORE_SETTINGS_H qTox/src/core/receiptnum.h000066400000000000000000000017711415623743500160740ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef RECEIPT_NUM_H #define RECEIPT_NUM_H #include "src/util/strongtype.h" #include #include using ReceiptNum = NamedType; Q_DECLARE_METATYPE(ReceiptNum); #endif /* RECEIPT_NUM_H */ qTox/src/core/toxcall.cpp000066400000000000000000000201121415623743500157100ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/toxcall.h" #include "src/audio/audio.h" #include "src/core/coreav.h" #include "src/persistence/settings.h" #include "src/video/camerasource.h" #include "src/video/corevideosource.h" #include "src/model/group.h" #include #include /** * @var uint32_t ToxCall::callId * @brief Could be a friendNum or groupNum, must uniquely identify the call. Do not modify! * * @var bool ToxCall::inactive * @brief True while we're not participating. (stopped group call, ringing but hasn't started yet, * ...) * * @var bool ToxFriendCall::videoEnabled * @brief True if our user asked for a video call, sending and recieving. * * @var bool ToxFriendCall::nullVideoBitrate * @brief True if our video bitrate is zero, i.e. if the device is closed. * * @var TOXAV_FRIEND_CALL_STATE ToxFriendCall::state * @brief State of the peer (not ours!) * * @var QMap ToxGroupCall::peers * @brief Keeps sources for users in group calls. */ ToxCall::ToxCall(bool VideoEnabled, CoreAV& av, IAudioControl& audio) : av{&av} , audio(audio) , videoEnabled{VideoEnabled} , audioSource(audio.makeSource()) {} ToxCall::~ToxCall() { if (videoEnabled) { QObject::disconnect(videoInConn); CameraSource::getInstance().unsubscribe(); } } bool ToxCall::isActive() const { return active; } void ToxCall::setActive(bool value) { active = value; } bool ToxCall::getMuteVol() const { return muteVol; } void ToxCall::setMuteVol(bool value) { muteVol = value; } bool ToxCall::getMuteMic() const { return muteMic; } void ToxCall::setMuteMic(bool value) { muteMic = value; } bool ToxCall::getVideoEnabled() const { return videoEnabled; } void ToxCall::setVideoEnabled(bool value) { videoEnabled = value; } bool ToxCall::getNullVideoBitrate() const { return nullVideoBitrate; } void ToxCall::setNullVideoBitrate(bool value) { nullVideoBitrate = value; } CoreVideoSource* ToxCall::getVideoSource() const { return videoSource; } ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av, IAudioControl& audio) : ToxCall(VideoEnabled, av, audio) , sink(audio.makeSink()) , friendId{FriendNum} { connect(audioSource.get(), &IAudioSource::frameAvailable, this, [this](const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) { this->av->sendCallAudio(this->friendId, pcm, samples, chans, rate); }); connect(audioSource.get(), &IAudioSource::invalidated, this, &ToxFriendCall::onAudioSourceInvalidated); if (sink) { audioSinkInvalid = sink->connectTo_invalidated(this, [this]() { this->onAudioSinkInvalidated(); }); } // register video if (videoEnabled) { videoSource = new CoreVideoSource(); CameraSource& source = CameraSource::getInstance(); if (source.isNone()) { source.setupDefault(); } source.subscribe(); videoInConn = QObject::connect(&source, &VideoSource::frameAvailable, [&av, FriendNum](std::shared_ptr frame) { av.sendCallVideo(FriendNum, frame); }); if (!videoInConn) { qDebug() << "Video connection not working"; } } } ToxFriendCall::~ToxFriendCall() { QObject::disconnect(audioSinkInvalid); } void ToxFriendCall::onAudioSourceInvalidated() { auto newSrc = audio.makeSource(); connect(newSrc.get(), &IAudioSource::frameAvailable, this, [this](const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) { this->av->sendCallAudio(this->friendId, pcm, samples, chans, rate); }); audioSource = std::move(newSrc); connect(audioSource.get(), &IAudioSource::invalidated, this, &ToxFriendCall::onAudioSourceInvalidated); } void ToxFriendCall::onAudioSinkInvalidated() { auto newSink = audio.makeSink(); if (newSink) { audioSinkInvalid = newSink->connectTo_invalidated(this, [this]() { this->onAudioSinkInvalidated(); }); } sink = std::move(newSink); } TOXAV_FRIEND_CALL_STATE ToxFriendCall::getState() const { return state; } void ToxFriendCall::setState(const TOXAV_FRIEND_CALL_STATE& value) { state = value; } void ToxFriendCall::playAudioBuffer(const int16_t* data, int samples, unsigned channels, int sampleRate) const { if (sink) { sink->playAudioBuffer(data, samples, channels, sampleRate); } } ToxGroupCall::ToxGroupCall(const Group& group, CoreAV& av, IAudioControl& audio) : ToxCall(false, av, audio) , group{group} { // register audio connect(audioSource.get(), &IAudioSource::frameAvailable, this, [this](const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) { if (this->group.getPeersCount() <= 1) { return; } this->av->sendGroupCallAudio(this->group.getId(), pcm, samples, chans, rate); }); connect(audioSource.get(), &IAudioSource::invalidated, this, &ToxGroupCall::onAudioSourceInvalidated); } ToxGroupCall::~ToxGroupCall() { // disconnect all Qt connections clearPeers(); } void ToxGroupCall::onAudioSourceInvalidated() { auto newSrc = audio.makeSource(); connect(audioSource.get(), &IAudioSource::frameAvailable, [this](const int16_t* pcm, size_t samples, uint8_t chans, uint32_t rate) { if (this->group.getPeersCount() <= 1) { return; } this->av->sendGroupCallAudio(this->group.getId(), pcm, samples, chans, rate); }); audioSource = std::move(newSrc); connect(audioSource.get(), &IAudioSource::invalidated, this, &ToxGroupCall::onAudioSourceInvalidated); } void ToxGroupCall::onAudioSinkInvalidated(ToxPk peerId) { removePeer(peerId); addPeer(peerId); } void ToxGroupCall::removePeer(ToxPk peerId) { const auto& source = peers.find(peerId); if (source == peers.cend()) { qDebug() << "Peer:" << peerId.toString() << "does not have a source, can't remove"; return; } peers.erase(source); QObject::disconnect(sinkInvalid[peerId]); sinkInvalid.erase(peerId); } void ToxGroupCall::addPeer(ToxPk peerId) { std::unique_ptr newSink = audio.makeSink(); QMetaObject::Connection con; if (newSink) { con = newSink->connectTo_invalidated(this, [this, peerId]() { this->onAudioSinkInvalidated(peerId); }); } peers.emplace(peerId, std::move(newSink)); sinkInvalid.insert({peerId, con}); } bool ToxGroupCall::havePeer(ToxPk peerId) { const auto& source = peers.find(peerId); return source != peers.cend(); } void ToxGroupCall::clearPeers() { peers.clear(); for (auto con : sinkInvalid) { QObject::disconnect(con.second); } sinkInvalid.clear(); } void ToxGroupCall::playAudioBuffer(const ToxPk& peer, const int16_t* data, int samples, unsigned channels, int sampleRate) { if (!havePeer(peer)) { addPeer(peer); } const auto& source = peers.find(peer); if (source->second) { source->second->playAudioBuffer(data, samples, channels, sampleRate); } } qTox/src/core/toxcall.h000066400000000000000000000074711415623743500153720ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXCALL_H #define TOXCALL_H #include "src/audio/iaudiocontrol.h" #include "src/audio/iaudiosink.h" #include "src/audio/iaudiosource.h" #include #include #include #include #include #include #include class QTimer; class AudioFilterer; class CoreVideoSource; class CoreAV; class Group; class ToxCall : public QObject { Q_OBJECT protected: ToxCall() = delete; ToxCall(bool VideoEnabled, CoreAV& av, IAudioControl& audio); ~ToxCall(); public: ToxCall(const ToxCall& other) = delete; ToxCall(ToxCall&& other) = delete; ToxCall& operator=(const ToxCall& other) = delete; ToxCall& operator=(ToxCall&& other) = delete; bool isActive() const; void setActive(bool value); bool getMuteVol() const; void setMuteVol(bool value); bool getMuteMic() const; void setMuteMic(bool value); bool getVideoEnabled() const; void setVideoEnabled(bool value); bool getNullVideoBitrate() const; void setNullVideoBitrate(bool value); CoreVideoSource* getVideoSource() const; protected: bool active{false}; CoreAV* av{nullptr}; // audio IAudioControl& audio; bool muteMic{false}; bool muteVol{false}; // video CoreVideoSource* videoSource{nullptr}; QMetaObject::Connection videoInConn; bool videoEnabled{false}; bool nullVideoBitrate{false}; std::unique_ptr audioSource = nullptr; }; class ToxFriendCall : public ToxCall { Q_OBJECT public: ToxFriendCall() = delete; ToxFriendCall(uint32_t friendId, bool VideoEnabled, CoreAV& av, IAudioControl& audio); ToxFriendCall(ToxFriendCall&& other) = delete; ToxFriendCall& operator=(ToxFriendCall&& other) = delete; ~ToxFriendCall(); TOXAV_FRIEND_CALL_STATE getState() const; void setState(const TOXAV_FRIEND_CALL_STATE& value); void playAudioBuffer(const int16_t* data, int samples, unsigned channels, int sampleRate) const; private slots: void onAudioSourceInvalidated(); void onAudioSinkInvalidated(); private: QMetaObject::Connection audioSinkInvalid; TOXAV_FRIEND_CALL_STATE state{TOXAV_FRIEND_CALL_STATE_NONE}; std::unique_ptr sink = nullptr; uint32_t friendId; }; class ToxGroupCall : public ToxCall { Q_OBJECT public: ToxGroupCall() = delete; ToxGroupCall(const Group& group, CoreAV& av, IAudioControl& audio); ToxGroupCall(ToxGroupCall&& other) = delete; ~ToxGroupCall(); ToxGroupCall& operator=(ToxGroupCall&& other) = delete; void removePeer(ToxPk peerId); void playAudioBuffer(const ToxPk& peer, const int16_t* data, int samples, unsigned channels, int sampleRate); private: void addPeer(ToxPk peerId); bool havePeer(ToxPk peerId); void clearPeers(); std::map> peers; std::map sinkInvalid; const Group& group; private slots: void onAudioSourceInvalidated(); void onAudioSinkInvalidated(ToxPk peerId); }; #endif // TOXCALL_H qTox/src/core/toxencrypt.cpp000066400000000000000000000301521415623743500164660ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toxencrypt.h" #include #include #include #include #include // functions for nice debug output static QString getKeyDerivationError(TOX_ERR_KEY_DERIVATION error); static QString getEncryptionError(TOX_ERR_ENCRYPTION error); static QString getDecryptionError(TOX_ERR_DECRYPTION error); static QString getSaltError(TOX_ERR_GET_SALT error); /** * @class ToxEncrypt * @brief Encapsulates the toxencrypsave API. * Since key derivation is work intensive and to avoid storing plaintext * passwords in memory, use a ToxEncrypt object and encrypt() or decrypt() * when you have to encrypt or decrypt more than once with the same password. */ /** * @brief Frees the passKey before destruction. */ ToxEncrypt::~ToxEncrypt() { tox_pass_key_free(passKey); } /** * @brief Constructs a ToxEncrypt object from a Tox_Pass_Key. * @param key Derived key to use for encryption and decryption. */ ToxEncrypt::ToxEncrypt(Tox_Pass_Key* key) : passKey{key} { } /** * @brief Gets the minimum number of bytes needed for isEncrypted() * @return Minimum number of bytes needed to check if data was encrypted * using this module. */ int ToxEncrypt::getMinBytes() { return TOX_PASS_ENCRYPTION_EXTRA_LENGTH; } /** * @brief Checks if the data was encrypted by this module. * @param ciphertext The data to check. * @return True if the data was encrypted using this module, false otherwise. */ bool ToxEncrypt::isEncrypted(const QByteArray& ciphertext) { if (ciphertext.length() < TOX_PASS_ENCRYPTION_EXTRA_LENGTH) { return false; } return tox_is_data_encrypted(reinterpret_cast(ciphertext.constData())); } /** * @brief Encrypts the plaintext with the given password. * @return Encrypted data or empty QByteArray on failure. * @param password Password to encrypt the data. * @param plaintext The data to encrypt. */ QByteArray ToxEncrypt::encryptPass(const QString& password, const QByteArray& plaintext) { if (password.length() == 0) { qWarning() << "Empty password supplied, probably not what you intended."; } QByteArray pass = password.toUtf8(); QByteArray ciphertext(plaintext.length() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0x00); TOX_ERR_ENCRYPTION error; tox_pass_encrypt(reinterpret_cast(plaintext.constData()), static_cast(plaintext.size()), reinterpret_cast(pass.constData()), static_cast(pass.size()), reinterpret_cast(ciphertext.data()), &error); if (error != TOX_ERR_ENCRYPTION_OK) { qCritical() << getEncryptionError(error); return QByteArray{}; } return ciphertext; } /** * @brief Decrypts data encrypted with this module. * @return The plaintext or an empty QByteArray on failure. * @param password The password used to encrypt the data. * @param ciphertext The encrypted data. */ QByteArray ToxEncrypt::decryptPass(const QString& password, const QByteArray& ciphertext) { if (!isEncrypted(ciphertext)) { qWarning() << "The data was not encrypted using this module or it's corrupted."; return QByteArray{}; } if (password.length() == 0) { qDebug() << "Empty password supplied, probably not what you intended."; } QByteArray pass = password.toUtf8(); QByteArray plaintext(ciphertext.length() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0x00); TOX_ERR_DECRYPTION error; tox_pass_decrypt(reinterpret_cast(ciphertext.constData()), static_cast(ciphertext.size()), reinterpret_cast(pass.constData()), static_cast(pass.size()), reinterpret_cast(plaintext.data()), &error); if (error != TOX_ERR_DECRYPTION_OK) { qWarning() << getDecryptionError(error); return QByteArray{}; } return plaintext; } /** * @brief Factory method for the ToxEncrypt object. * @param password Password to use for encryption. * @return A std::unique_ptr containing a ToxEncrypt object on success, or an * or an empty std::unique_ptr on failure. * * Derives a key from the password and a new random salt. */ std::unique_ptr ToxEncrypt::makeToxEncrypt(const QString& password) { const QByteArray pass = password.toUtf8(); TOX_ERR_KEY_DERIVATION error; Tox_Pass_Key* const passKey = tox_pass_key_derive( reinterpret_cast(pass.constData()), static_cast(pass.length()), &error); if (error != TOX_ERR_KEY_DERIVATION_OK) { tox_pass_key_free(passKey); qCritical() << getKeyDerivationError(error); return std::unique_ptr{}; } return std::unique_ptr(new ToxEncrypt(passKey)); } /** * @brief Factory method for the ToxEncrypt object. * @param password Password to use for encryption. * @param toxSave The data to read the salt for decryption from. * @return A std::unique_ptr containing a ToxEncrypt object on success, or an * or an empty std::unique_ptr on failure. * * Derives a key from the password and the salt read from toxSave. */ std::unique_ptr ToxEncrypt::makeToxEncrypt(const QString& password, const QByteArray& toxSave) { if (!isEncrypted(toxSave)) { qWarning() << "The data was not encrypted using this module or it's corrupted."; return std::unique_ptr{}; } TOX_ERR_GET_SALT saltError; uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast(toxSave.constData()), salt, &saltError); if (saltError != TOX_ERR_GET_SALT_OK) { qWarning() << getSaltError(saltError); return std::unique_ptr{}; } QByteArray pass = password.toUtf8(); TOX_ERR_KEY_DERIVATION keyError; Tox_Pass_Key* const passKey = tox_pass_key_derive_with_salt( reinterpret_cast(pass.constData()), static_cast(pass.length()), salt, &keyError); if (keyError != TOX_ERR_KEY_DERIVATION_OK) { tox_pass_key_free(passKey); qWarning() << getKeyDerivationError(keyError); return std::unique_ptr{}; } return std::unique_ptr(new ToxEncrypt(passKey)); } /** * @brief Encrypts the plaintext with the stored key. * @return Encrypted data or empty QByteArray on failure. * @param plaintext The data to encrypt. */ QByteArray ToxEncrypt::encrypt(const QByteArray& plaintext) const { if (!passKey) { qCritical() << "The passKey is invalid."; return QByteArray{}; } QByteArray ciphertext(plaintext.length() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0x00); TOX_ERR_ENCRYPTION error; tox_pass_key_encrypt(passKey, reinterpret_cast(plaintext.constData()), static_cast(plaintext.size()), reinterpret_cast(ciphertext.data()), &error); if (error != TOX_ERR_ENCRYPTION_OK) { qCritical() << getEncryptionError(error); return QByteArray{}; } return ciphertext; } /** * @brief Decrypts data encrypted with this module, using the stored key. * @return The plaintext or an empty QByteArray on failure. * @param ciphertext The encrypted data. */ QByteArray ToxEncrypt::decrypt(const QByteArray& ciphertext) const { if (!isEncrypted(ciphertext)) { qWarning() << "The data was not encrypted using this module or it's corrupted."; return QByteArray{}; } QByteArray plaintext(ciphertext.length() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0x00); TOX_ERR_DECRYPTION error; tox_pass_key_decrypt(passKey, reinterpret_cast(ciphertext.constData()), static_cast(ciphertext.size()), reinterpret_cast(plaintext.data()), &error); if (error != TOX_ERR_DECRYPTION_OK) { qWarning() << getDecryptionError(error); return QByteArray{}; } return plaintext; } /** * @brief Gets the error string for TOX_ERR_KEY_DERIVATION errors. * @param error The error number. * @return The verbose error message. */ QString getKeyDerivationError(TOX_ERR_KEY_DERIVATION error) { switch (error) { case TOX_ERR_KEY_DERIVATION_OK: return QStringLiteral("The function returned successfully."); case TOX_ERR_KEY_DERIVATION_NULL: return QStringLiteral( "One of the arguments to the function was NULL when it was not expected."); case TOX_ERR_KEY_DERIVATION_FAILED: return QStringLiteral( "The crypto lib was unable to derive a key from the given passphrase."); default: return QStringLiteral("Unknown key derivation error."); } } /** * @brief Gets the error string for TOX_ERR_ENCRYPTION errors. * @param error The error number. * @return The verbose error message. */ QString getEncryptionError(TOX_ERR_ENCRYPTION error) { switch (error) { case TOX_ERR_ENCRYPTION_OK: return QStringLiteral("The function returned successfully."); case TOX_ERR_ENCRYPTION_NULL: return QStringLiteral( "One of the arguments to the function was NULL when it was not expected."); case TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED: return QStringLiteral( "The crypto lib was unable to derive a key from the given passphrase."); case TOX_ERR_ENCRYPTION_FAILED: return QStringLiteral("The encryption itself failed."); default: return QStringLiteral("Unknown encryption error."); } } /** * @brief Gets the error string for TOX_ERR_DECRYPTION errors. * @param error The error number. * @return The verbose error message. */ QString getDecryptionError(TOX_ERR_DECRYPTION error) { switch (error) { case TOX_ERR_DECRYPTION_OK: return QStringLiteral("The function returned successfully."); case TOX_ERR_DECRYPTION_NULL: return QStringLiteral( "One of the arguments to the function was NULL when it was not expected."); case TOX_ERR_DECRYPTION_INVALID_LENGTH: return QStringLiteral( "The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes."); case TOX_ERR_DECRYPTION_BAD_FORMAT: return QStringLiteral("The input data is missing the magic number or is corrupted."); case TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED: return QStringLiteral("The crypto lib was unable to derive a key from the given passphrase."); case TOX_ERR_DECRYPTION_FAILED: return QStringLiteral("Decryption failed. Either the data was corrupted or the password/key was incorrect."); default: return QStringLiteral("Unknown decryption error."); } } /** * @brief Gets the error string for TOX_ERR_GET_SALT errors. * @param error The error number. * @return The verbose error message. */ QString getSaltError(TOX_ERR_GET_SALT error) { switch (error) { case TOX_ERR_GET_SALT_OK: return QStringLiteral("The function returned successfully."); case TOX_ERR_GET_SALT_NULL: return QStringLiteral( "One of the arguments to the function was NULL when it was not expected."); case TOX_ERR_GET_SALT_BAD_FORMAT: return QStringLiteral("The input data is missing the magic number or is corrupted."); default: return QStringLiteral("Unknown salt error."); } } qTox/src/core/toxencrypt.h000066400000000000000000000034541415623743500161400ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXENCRYPT_H #define TOXENCRYPT_H #include #include #include struct Tox_Pass_Key; class ToxEncrypt { public: ~ToxEncrypt(); ToxEncrypt() = delete; ToxEncrypt(const ToxEncrypt& other) = delete; ToxEncrypt& operator=(const ToxEncrypt& other) = delete; static int getMinBytes(); static bool isEncrypted(const QByteArray& ciphertext); static QByteArray encryptPass(const QString& password, const QByteArray& plaintext); static QByteArray decryptPass(const QString& password, const QByteArray& ciphertext); static std::unique_ptr makeToxEncrypt(const QString& password); static std::unique_ptr makeToxEncrypt(const QString& password, const QByteArray& toxSave); QByteArray encrypt(const QByteArray& plaintext) const; QByteArray decrypt(const QByteArray& ciphertext) const; private: explicit ToxEncrypt(Tox_Pass_Key* key); private: Tox_Pass_Key* passKey = nullptr; }; #endif // TOXENCRYPT_H qTox/src/core/toxfile.cpp000066400000000000000000000040521415623743500157210ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/toxfile.h" #include #include #include #define TOX_HEX_ID_LENGTH 2 * TOX_ADDRESS_SIZE /** * @file corestructs.h * @brief Some headers use Core structs but don't need to include all of core.h * * They should include this file directly instead to reduce compilation times * * @var uint8_t ToxFile::fileKind * @brief Data file (default) or avatar */ /** * @brief ToxFile constructor */ ToxFile::ToxFile(uint32_t fileNum, uint32_t friendId, QString filename, QString filePath, FileDirection Direction) : fileKind{TOX_FILE_KIND_DATA} , fileNum(fileNum) , friendId(friendId) , fileName{filename} , filePath{filePath} , file{new QFile(filePath)} , bytesSent{0} , filesize{0} , status{INITIALIZING} , direction{Direction} {} bool ToxFile::operator==(const ToxFile& other) const { return (fileNum == other.fileNum) && (friendId == other.friendId) && (direction == other.direction); } bool ToxFile::operator!=(const ToxFile& other) const { return !(*this == other); } void ToxFile::setFilePath(QString path) { filePath = path; file->setFileName(path); } bool ToxFile::open(bool write) { return write ? file->open(QIODevice::ReadWrite) : file->open(QIODevice::ReadOnly); } qTox/src/core/toxfile.h000066400000000000000000000044101415623743500153640ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CORESTRUCTS_H #define CORESTRUCTS_H #include "src/core/toxfilepause.h" #include #include #include class QFile; class QTimer; struct ToxFile { // Note do not change values, these are directly inserted into the DB in their // current form, changing order would mess up database state! enum FileStatus { INITIALIZING = 0, PAUSED = 1, TRANSMITTING = 2, BROKEN = 3, CANCELED = 4, FINISHED = 5, }; // Note do not change values, these are directly inserted into the DB in their // current form (can add fields though as db representation is an int) enum FileDirection : bool { SENDING = 0, RECEIVING = 1, }; ToxFile() = default; ToxFile(uint32_t FileNum, uint32_t FriendId, QString FileName, QString filePath, FileDirection Direction); bool operator==(const ToxFile& other) const; bool operator!=(const ToxFile& other) const; void setFilePath(QString path); bool open(bool write); uint8_t fileKind; uint32_t fileNum; uint32_t friendId; QString fileName; QString filePath; std::shared_ptr file; quint64 bytesSent; quint64 filesize; FileStatus status; FileDirection direction; QByteArray avatarData; QByteArray resumeFileId; std::shared_ptr hashGenerator = std::make_shared(QCryptographicHash::Sha256); ToxFilePause pauseStatus; }; #endif // CORESTRUCTS_H qTox/src/core/toxfilepause.h000066400000000000000000000031561415623743500164300ustar00rootroot00000000000000/* Copyright © 2018-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOX_FILE_PAUSE_H #define TOX_FILE_PAUSE_H class ToxFilePause { public: void localPause() { localPauseState = true; } void localResume() { localPauseState = false; } void localPauseToggle() { localPauseState = !localPauseState; } void remotePause() { remotePauseState = true; } void remoteResume() { remotePauseState = false; } void remotePauseToggle() { remotePauseState = !remotePauseState; } bool localPaused() const { return localPauseState; } bool remotePaused() const { return remotePauseState; } bool paused() const { return localPauseState || remotePauseState; } private: bool localPauseState = false; bool remotePauseState = false; }; #endif // TOX_FILE_PAUSE_H qTox/src/core/toxid.cpp000066400000000000000000000163121415623743500154000ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toxid.h" #include "toxpk.h" #include #include #include // Tox doesn't publicly define these #define NOSPAM_BYTES 4 #define CHECKSUM_BYTES 2 #define PUBLIC_KEY_HEX_CHARS (2 * TOX_PUBLIC_KEY_SIZE) #define NOSPAM_HEX_CHARS (2 * NOSPAM_BYTES) #define CHECKSUM_HEX_CHARS (2 * CHECKSUM_BYTES) #define TOXID_HEX_CHARS (2 * TOX_ADDRESS_SIZE) const QRegularExpression ToxId::ToxIdRegEx(QString("(^|\\s)[A-Fa-f0-9]{%1}($|\\s)").arg(TOXID_HEX_CHARS)); /** * @class ToxId * @brief This class represents a Tox ID. * * An ID is composed of 32 bytes long public key, 4 bytes long NoSpam * and 2 bytes long checksum. * * e.g. * @code * | C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30 | C8BA3AB9 | BEB9 * | / | * | / NoSpam | Checksum * | Public Key (PK), 32 bytes, 64 characters / 4 bytes | 2 bytes * | | 8 characters| 4 characters * @endcode */ /** * @brief The default constructor. Creates an empty Tox ID. */ ToxId::ToxId() : toxId() { } /** * @brief The copy constructor. * @param other ToxId to copy */ ToxId::ToxId(const ToxId& other) : toxId(other.toxId) { } /** * @brief Create a Tox ID from a QString. * * If the given rawId is not a valid Tox ID, but can be a Public Key then: * publicKey == rawId and noSpam == 0 == checkSum. * If the given rawId isn't a valid Public Key or Tox ID a ToxId with all zero bytes is created. * * @param id Tox ID string to convert to ToxId object */ ToxId::ToxId(const QString& id) { // TODO: remove construction from PK only if (isToxId(id)) { toxId = QByteArray::fromHex(id.toLatin1()); } else if (id.length() >= PUBLIC_KEY_HEX_CHARS) { toxId = QByteArray::fromHex(id.left(PUBLIC_KEY_HEX_CHARS).toLatin1()); } else { toxId = QByteArray(); // invalid id string } } /** * @brief Create a Tox ID from a QByteArray. * * If the given rawId is not a valid Tox ID, but can be a Public Key then: * publicKey == rawId and noSpam == 0 == checkSum. * If the given rawId isn't a valid Public Key or Tox ID a ToxId with all zero bytes is created. * * @param rawId Tox ID bytes to convert to ToxId object */ ToxId::ToxId(const QByteArray& rawId) { constructToxId(rawId); } /** * @brief Create a Tox ID from uint8_t bytes and lenght, convenience function for toxcore interface. * * If the given rawId is not a valid Tox ID, but can be a Public Key then: * publicKey == rawId and noSpam == 0 == checkSum. * If the given rawId isn't a valid Public Key or Tox ID a ToxId with all zero bytes is created. * * @param rawId Pointer to bytes to convert to ToxId object * @param len Number of bytes to read. Must be TOX_SECRET_KEY_SIZE for a Public Key or * TOX_ADDRESS_SIZE for a Tox ID. */ ToxId::ToxId(const uint8_t* rawId, int len) { QByteArray tmpId(reinterpret_cast(rawId), len); constructToxId(tmpId); } void ToxId::constructToxId(const QByteArray& rawId) { // TODO: remove construction from PK only if (rawId.length() == TOX_SECRET_KEY_SIZE) { toxId = QByteArray(rawId); // construct from PK only } else if (rawId.length() == TOX_ADDRESS_SIZE && isToxId(rawId.toHex().toUpper())) { toxId = QByteArray(rawId); // construct from full toxid } else { toxId = QByteArray(); // invalid id } } /** * @brief Compares the equality of the Public Key. * @param other Tox ID to compare. * @return True if both Tox IDs have the same public keys, false otherwise. */ bool ToxId::operator==(const ToxId& other) const { return getPublicKey() == other.getPublicKey(); } /** * @brief Compares the inequality of the Public Key. * @param other Tox ID to compare. * @return True if both Tox IDs have different public keys, false otherwise. */ bool ToxId::operator!=(const ToxId& other) const { return getPublicKey() != other.getPublicKey(); } /** * @brief Returns the Tox ID converted to QString. * Is equal to getPublicKey() if the Tox ID was constructed from only a Public Key. * @return The Tox ID as QString. */ QString ToxId::toString() const { return toxId.toHex().toUpper(); } /** * @brief Clears all elements of the Tox ID. */ void ToxId::clear() { toxId.clear(); } /** * @brief Gets the ToxID as bytes, convenience function for toxcore interface. * @return The ToxID as uint8_t* if isValid() is true, else a nullptr. */ const uint8_t* ToxId::getBytes() const { if (isValid()) { return reinterpret_cast(toxId.constData()); } return nullptr; } /** * @brief Gets the Public Key part of the ToxID * @return Public Key of the ToxID */ ToxPk ToxId::getPublicKey() const { auto const pkBytes = toxId.left(TOX_PUBLIC_KEY_SIZE); if (pkBytes.isEmpty()) { return ToxPk{}; } else { return ToxPk{pkBytes}; } } /** * @brief Returns the NoSpam value converted to QString. * @return The NoSpam value as QString or "" if the ToxId was constructed from a Public Key. */ QString ToxId::getNoSpamString() const { if (toxId.length() == TOX_ADDRESS_SIZE) { return toxId.mid(TOX_PUBLIC_KEY_SIZE, NOSPAM_BYTES).toHex().toUpper(); } return {}; } /** * @brief Check, that id is a valid Tox ID. * @param id Tox ID to check. * @return True if id is a valid Tox ID, false otherwise. * @note Validates the checksum. */ bool ToxId::isValidToxId(const QString& id) { return isToxId(id) && ToxId(id).isValid(); } /** * @brief Check, that id is probably a valid Tox ID. * @param id Tox ID to check. * @return True if the string can be a ToxID, false otherwise. * @note Doesn't validate checksum. */ bool ToxId::isToxId(const QString& id) { return id.length() == TOXID_HEX_CHARS && id.contains(ToxIdRegEx); } /** * @brief Check it it's a valid Tox ID by verifying the checksum * @return True if it is a valid Tox ID, false otherwise. */ bool ToxId::isValid() const { if (toxId.length() != TOX_ADDRESS_SIZE) { return false; } const int size = TOX_PUBLIC_KEY_SIZE + NOSPAM_BYTES; QByteArray data = toxId.left(size); QByteArray checksum = toxId.right(CHECKSUM_BYTES); QByteArray calculated(CHECKSUM_BYTES, 0x00); for (int i = 0; i < size; i++) { calculated[i % 2] = calculated[i % 2] ^ data[i]; } return calculated == checksum; } qTox/src/core/toxid.h000066400000000000000000000033351415623743500150460ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXID_H #define TOXID_H #include "toxpk.h" #include #include #include class ToxId { public: ToxId(); ToxId(const ToxId& other); explicit ToxId(const QString& id); explicit ToxId(const QByteArray& rawId); explicit ToxId(const uint8_t* rawId, int len); ToxId& operator=(const ToxId& other) = default; ToxId& operator=(ToxId&& other) = default; bool operator==(const ToxId& other) const; bool operator!=(const ToxId& other) const; QString toString() const; void clear(); bool isValid() const; static bool isValidToxId(const QString& id); static bool isToxId(const QString& id); const uint8_t* getBytes() const; QByteArray getToxId() const; ToxPk getPublicKey() const; QString getNoSpamString() const; private: void constructToxId(const QByteArray& rawId); public: static const QRegularExpression ToxIdRegEx; private: QByteArray toxId; }; #endif // TOXID_H qTox/src/core/toxlogger.cpp000066400000000000000000000043201415623743500162570ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toxlogger.h" #include #include #include #include #include namespace ToxLogger { namespace { QByteArray cleanPath(const char *file) { // for privacy, make the path relative to the c-toxcore source directory const QRegularExpression pathCleaner(QLatin1String{"[\\s|\\S]*c-toxcore."}); QByteArray cleanedPath = QString{file}.remove(pathCleaner).toUtf8(); cleanedPath.append('\0'); return cleanedPath; } } // namespace /** * @brief Log message handler for toxcore log messages * @note See tox.h for the parameter definitions */ void onLogMessage(Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, const char *message, void *user_data) { const QByteArray cleanedPath = cleanPath(file); switch (level) { case TOX_LOG_LEVEL_TRACE: return; // trace level generates too much noise to enable by default case TOX_LOG_LEVEL_DEBUG: QMessageLogger(cleanedPath.data(), line, func).debug() << message; break; case TOX_LOG_LEVEL_INFO: QMessageLogger(cleanedPath.data(), line, func).info() << message; break; case TOX_LOG_LEVEL_WARNING: QMessageLogger(cleanedPath.data(), line, func).warning() << message; break; case TOX_LOG_LEVEL_ERROR: QMessageLogger(cleanedPath.data(), line, func).critical() << message; break; } } } // namespace ToxLogger qTox/src/core/toxlogger.h000066400000000000000000000020431415623743500157240ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXLOGGER_H #define TOXLOGGER_H #include #include namespace ToxLogger { void onLogMessage(Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, const char *message, void *user_data); } #endif // TOXLOGGER_H qTox/src/core/toxoptions.cpp000066400000000000000000000127471415623743500165070ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toxoptions.h" #include "src/core/icoresettings.h" #include "src/core/toxlogger.h" #include #include #include /** * @brief The ToxOptions class wraps the Tox_Options struct and the matching * proxy address data. This is needed to ensure both have equal lifetime and * are correctly deleted. */ ToxOptions::ToxOptions(Tox_Options* options, const QByteArray& proxyAddrData) : options(options) , proxyAddrData(proxyAddrData) {} ToxOptions::~ToxOptions() { tox_options_free(options); } ToxOptions::ToxOptions(ToxOptions&& from) { options = from.options; proxyAddrData.swap(from.proxyAddrData); from.options = nullptr; from.proxyAddrData.clear(); } const char* ToxOptions::getProxyAddrData() const { return proxyAddrData.constData(); } ToxOptions::operator Tox_Options*() { return options; } /** * @brief Initializes a ToxOptions instance * @param savedata Previously saved Tox data * @return ToxOptions instance initialized to create Tox instance */ std::unique_ptr ToxOptions::makeToxOptions(const QByteArray& savedata, const ICoreSettings* s) { Tox_Options* tox_opts = tox_options_new(nullptr); if (!tox_opts) { qWarning() << "failed to create Tox_Options"; return {}; } // need to init proxyAddr here, because we need it to construct ToxOptions const QString proxyAddr = s == nullptr ? QString{} : s->getProxyAddr(); auto toxOptions = std::unique_ptr(new ToxOptions(tox_opts, proxyAddr.toUtf8())); // register log first, to get messages as early as possible tox_options_set_log_callback(*toxOptions, ToxLogger::onLogMessage); // savedata tox_options_set_savedata_type(*toxOptions, savedata.isNull() ? TOX_SAVEDATA_TYPE_NONE : TOX_SAVEDATA_TYPE_TOX_SAVE); tox_options_set_savedata_data(*toxOptions, reinterpret_cast(savedata.data()), savedata.size()); if(s == nullptr) { qDebug() << "Using Tox default settings"; return toxOptions; } // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be // disabled in options. const bool enableIPv6 = s->getEnableIPv6(); bool forceTCP = s->getForceTCP(); // LAN requiring UDP is a toxcore limitation, ideally wouldn't be related const bool enableLanDiscovery = s->getEnableLanDiscovery() && !forceTCP; ICoreSettings::ProxyType proxyType = s->getProxyType(); quint16 proxyPort = s->getProxyPort(); if (!enableLanDiscovery) { qWarning() << "Core starting without LAN discovery. Peers can only be found through DHT."; } if (enableIPv6) { qDebug() << "Core starting with IPv6 enabled"; } else if (enableLanDiscovery) { qWarning() << "Core starting with IPv6 disabled. LAN discovery may not work properly."; } // No proxy by default tox_options_set_proxy_type(*toxOptions, TOX_PROXY_TYPE_NONE); tox_options_set_proxy_host(*toxOptions, nullptr); tox_options_set_proxy_port(*toxOptions, 0); if (proxyType != ICoreSettings::ProxyType::ptNone) { if (static_cast(proxyAddr.length()) > tox_max_hostname_length()) { qWarning() << "proxy address" << proxyAddr << "is too long"; } else if (!proxyAddr.isEmpty() && proxyPort > 0) { qDebug() << "using proxy" << proxyAddr << ":" << proxyPort; // protection against changings in Tox_Proxy_Type enum if (proxyType == ICoreSettings::ProxyType::ptSOCKS5) { tox_options_set_proxy_type(*toxOptions, TOX_PROXY_TYPE_SOCKS5); } else if (proxyType == ICoreSettings::ProxyType::ptHTTP) { tox_options_set_proxy_type(*toxOptions, TOX_PROXY_TYPE_HTTP); } tox_options_set_proxy_host(*toxOptions, toxOptions->getProxyAddrData()); tox_options_set_proxy_port(*toxOptions, proxyPort); if (!forceTCP) { qDebug() << "Proxy and UDP enabled, this is a security risk, forcing TCP only"; forceTCP = true; } } } // network options tox_options_set_udp_enabled(*toxOptions, !forceTCP); tox_options_set_ipv6_enabled(*toxOptions, enableIPv6); tox_options_set_local_discovery_enabled(*toxOptions, enableLanDiscovery); tox_options_set_start_port(*toxOptions, 0); tox_options_set_end_port(*toxOptions, 0); return toxOptions; } bool ToxOptions::getIPv6Enabled() const { return tox_options_get_ipv6_enabled(options); } void ToxOptions::setIPv6Enabled(bool enabled) { tox_options_set_ipv6_enabled(options, enabled); } qTox/src/core/toxoptions.h000066400000000000000000000026641415623743500161510ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXOPTIONS_H #define TOXOPTIONS_H #include #include class ICoreSettings; struct Tox_Options; class ToxOptions { public: ~ToxOptions(); ToxOptions(ToxOptions&& from); operator Tox_Options*(); const char* getProxyAddrData() const; static std::unique_ptr makeToxOptions(const QByteArray& savedata, const ICoreSettings* s); bool getIPv6Enabled() const; void setIPv6Enabled(bool enabled); private: ToxOptions(Tox_Options* options, const QByteArray& proxyAddrData); private: Tox_Options* options = nullptr; QByteArray proxyAddrData; }; #endif // TOXOPTIONS_H qTox/src/core/toxpk.cpp000066400000000000000000000037351415623743500154230ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toxpk.h" #include #include #include #include /** * @class ToxPk * @brief This class represents a Tox Public Key, which is a part of Tox ID. */ /** * @brief The default constructor. Creates an empty Tox key. */ ToxPk::ToxPk() : ContactId() { } /** * @brief The copy constructor. * @param other ToxPk to copy */ ToxPk::ToxPk(const ToxPk& other) : ContactId(other.id) { } /** * @brief Constructs a ToxPk from bytes. * @param rawId The bytes to construct the ToxPk from. The lenght must be exactly * TOX_PUBLIC_KEY_SIZE, else the ToxPk will be empty. */ ToxPk::ToxPk(const QByteArray& rawId) : ContactId([rawId](){ assert(rawId.length() == TOX_PUBLIC_KEY_SIZE); return rawId;}()) { } /** * @brief Constructs a ToxPk from bytes. * @param rawId The bytes to construct the ToxPk from, will read exactly * TOX_PUBLIC_KEY_SIZE from the specified buffer. */ ToxPk::ToxPk(const uint8_t* rawId) : ContactId(QByteArray(reinterpret_cast(rawId), TOX_PUBLIC_KEY_SIZE)) { } /** * @brief Get size of public key in bytes. * @return Size of public key in bytes. */ int ToxPk::getSize() const { return TOX_PUBLIC_KEY_SIZE; } qTox/src/core/toxpk.h000066400000000000000000000021131415623743500150550ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXPK_H #define TOXPK_H #include "src/core/contactid.h" #include #include class ToxPk : public ContactId { public: ToxPk(); ToxPk(const ToxPk& other); explicit ToxPk(const QByteArray& rawId); explicit ToxPk(const uint8_t* rawId); int getSize() const override; }; #endif // TOXPK_H qTox/src/core/toxstring.cpp000066400000000000000000000046001415623743500163070ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toxstring.h" #include #include #include #include /** * @class ToxString * @brief Helper to convert safely between strings in the c-toxcore representation and QString. */ /** * @brief Creates a ToxString from a QString. * @param string Input text. */ ToxString::ToxString(const QString& text) : ToxString(text.toUtf8()) { } /** * @brief Creates a ToxString from bytes in a QByteArray. * @param text Input text. */ ToxString::ToxString(const QByteArray& text) : string(text) { } /** * @brief Creates a ToxString from the representation used by c-toxcore. * @param text Pointer to the beginning of the text. * @param length Number of bytes to read from the beginning. */ ToxString::ToxString(const uint8_t* text, size_t length) { assert(length <= INT_MAX); string = QByteArray(reinterpret_cast(text), length); } /** * @brief Returns a pointer to the beginning of the string data. * @return Pointer to the beginning of the string data. */ const uint8_t* ToxString::data() const { return reinterpret_cast(string.constData()); } /** * @brief Get the number of bytes in the string. * @return Number of bytes in the string. */ size_t ToxString::size() const { return string.size(); } /** * @brief Gets the string as QString. * @return QString representation of the string. */ QString ToxString::getQString() const { return QString::fromUtf8(string); } /** * @brief getBytes Gets the bytes of the string. * @return Bytes of the string as QByteArray. */ QByteArray ToxString::getBytes() const { return QByteArray(string); } qTox/src/core/toxstring.h000066400000000000000000000023101415623743500157500ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXSTRING_H #define TOXSTRING_H #include #include #include class ToxString { public: explicit ToxString(const QString& text); explicit ToxString(const QByteArray& text); ToxString(const uint8_t* text, size_t length); const uint8_t* data() const; size_t size() const; QString getQString() const; QByteArray getBytes() const; private: QByteArray string; }; #endif // TOXSTRING_H qTox/src/friendlist.cpp000066400000000000000000000050631415623743500154650ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "friendlist.h" #include "src/model/friend.h" #include "src/persistence/settings.h" #include "src/core/contactid.h" #include "src/core/toxpk.h" #include #include #include QHash FriendList::friendList; QHash FriendList::id2key; Friend* FriendList::addFriend(uint32_t friendId, const ToxPk& friendPk) { auto friendChecker = friendList.find(friendPk); if (friendChecker != friendList.end()) { qWarning() << "addFriend: friendPk already taken"; } QString alias = Settings::getInstance().getFriendAlias(friendPk); Friend* newfriend = new Friend(friendId, friendPk, alias); friendList[friendPk] = newfriend; id2key[friendId] = friendPk; return newfriend; } Friend* FriendList::findFriend(const ToxPk& friendPk) { auto f_it = friendList.find(friendPk); if (f_it != friendList.end()) { return *f_it; } return nullptr; } const ToxPk& FriendList::id2Key(uint32_t friendId) { return id2key[friendId]; } void FriendList::removeFriend(const ToxPk& friendPk, bool fake) { auto f_it = friendList.find(friendPk); if (f_it != friendList.end()) { if (!fake) Settings::getInstance().removeFriendSettings(f_it.value()->getPublicKey()); friendList.erase(f_it); } } void FriendList::clear() { for (auto friendptr : friendList) delete friendptr; friendList.clear(); } QList FriendList::getAllFriends() { return friendList.values(); } QString FriendList::decideNickname(const ToxPk& friendPk, const QString& origName) { Friend* f = FriendList::findFriend(friendPk); if (f != nullptr) { return f->getDisplayedName(); } else if (!origName.isEmpty()) { return origName; } else { return friendPk.toString(); } } qTox/src/friendlist.h000066400000000000000000000027551415623743500151370ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FRIENDLIST_H #define FRIENDLIST_H #include template class QList; template class QHash; class Friend; class QByteArray; class QString; class ToxPk; class FriendList { public: static Friend* addFriend(uint32_t friendId, const ToxPk& friendPk); static Friend* findFriend(const ToxPk& friendPk); static const ToxPk& id2Key(uint32_t friendId); static QList getAllFriends(); static void removeFriend(const ToxPk& friendPk, bool fake = false); static void clear(); static QString decideNickname(const ToxPk& friendPk, const QString& origName); private: static QHash friendList; static QHash id2key; }; #endif // FRIENDLIST_H qTox/src/grouplist.cpp000066400000000000000000000044011415623743500153450ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "grouplist.h" #include "src/core/core.h" #include "src/model/group.h" #include #include QHash GroupList::groupList; QHash GroupList::id2key; Group* GroupList::addGroup(int groupNum, const GroupId& groupId, const QString& name, bool isAvGroupchat, const QString& selfName) { auto checker = groupList.find(groupId); if (checker != groupList.end()) qWarning() << "addGroup: groupId already taken"; // TODO: Core instance is bad but grouplist is also an instance so we can // deal with this later auto core = Core::getInstance(); Group* newGroup = new Group(groupNum, groupId, name, isAvGroupchat, selfName, *core, *core); groupList[groupId] = newGroup; id2key[groupNum] = groupId; return newGroup; } Group* GroupList::findGroup(const GroupId& groupId) { auto g_it = groupList.find(groupId); if (g_it != groupList.end()) return *g_it; return nullptr; } const GroupId& GroupList::id2Key(uint32_t groupNum) { return id2key[groupNum]; } void GroupList::removeGroup(const GroupId& groupId, bool /*fake*/) { auto g_it = groupList.find(groupId); if (g_it != groupList.end()) { groupList.erase(g_it); } } QList GroupList::getAllGroups() { QList res; for (auto it : groupList) res.append(it); return res; } void GroupList::clear() { for (auto groupptr : groupList) delete groupptr; groupList.clear(); } qTox/src/grouplist.h000066400000000000000000000027151415623743500150200ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUPLIST_H #define GROUPLIST_H #include "src/core/groupid.h" template class QHash; template class QList; class Group; class QString; class GroupList { public: static Group* addGroup(int groupId, const GroupId& persistentGroupId, const QString& name, bool isAvGroupchat, const QString& selfName); static Group* findGroup(const GroupId& groupId); static const GroupId& id2Key(uint32_t groupNum); static void removeGroup(const GroupId& groupId, bool fake = false); static QList getAllGroups(); static void clear(); private: static QHash groupList; static QHash id2key; }; #endif // GROUPLIST_H qTox/src/ipc.cpp000066400000000000000000000233141415623743500140740ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/ipc.h" #include #include #include #include #include #include /** * @var time_t IPC::lastEvent * @brief When last event was posted. * * @var time_t IPC::lastProcessed * @brief When processEvents() ran last time */ /** * @class IPC * @brief Inter-process communication */ IPC::IPC(uint32_t profileId) : profileId{profileId} , globalMemory{"qtox-" IPC_PROTOCOL_VERSION} { qRegisterMetaType("IPCEventHandler"); timer.setInterval(EVENT_TIMER_MS); timer.setSingleShot(true); connect(&timer, &QTimer::timeout, this, &IPC::processEvents); // The first started instance gets to manage the shared memory by taking ownership // Every time it processes events it updates the global shared timestamp "lastProcessed" // If the timestamp isn't updated, that's a timeout and someone else can take ownership // This is a safety measure, in case one of the clients crashes // If the owner exits normally, it can set the timestamp to 0 first to immediately give // ownership std::default_random_engine randEngine((std::random_device())()); std::uniform_int_distribution distribution; globalId = distribution(randEngine); qDebug() << "Our global IPC ID is " << globalId; if (globalMemory.create(sizeof(IPCMemory))) { if (globalMemory.lock()) { IPCMemory* mem = global(); memset(mem, 0, sizeof(IPCMemory)); mem->globalId = globalId; mem->lastProcessed = time(nullptr); globalMemory.unlock(); } else { qWarning() << "Couldn't lock to take ownership"; } } else if (globalMemory.attach()) { qDebug() << "Attaching to the global shared memory"; } else { qDebug() << "Failed to attach to the global shared memory, giving up. Error:" << globalMemory.error(); return; // We won't be able to do any IPC without being attached, let's get outta here } processEvents(); } IPC::~IPC() { if (!globalMemory.lock()) { qWarning() << "Failed to lock in ~IPC"; return; } if (isCurrentOwnerNoLock()) { global()->globalId = 0; } globalMemory.unlock(); } /** * @brief Post IPC event. * @param name Name to set in IPC event. * @param data Data to set in IPC event (default QByteArray()). * @param dest Settings::getCurrentProfileId() or 0 (main instance, default). * @return Time the event finished or 0 on error. */ time_t IPC::postEvent(const QString& name, const QByteArray& data, uint32_t dest) { QByteArray binName = name.toUtf8(); if (binName.length() > (int32_t)sizeof(IPCEvent::name)) { return 0; } if (data.length() > (int32_t)sizeof(IPCEvent::data)) { return 0; } if (!globalMemory.lock()) { qDebug() << "Failed to lock in postEvent()"; return 0; } IPCEvent* evt = nullptr; IPCMemory* mem = global(); time_t result = 0; for (uint32_t i = 0; !evt && i < EVENT_QUEUE_SIZE; ++i) { if (mem->events[i].posted == 0) { evt = &mem->events[i]; } } if (evt) { memset(evt, 0, sizeof(IPCEvent)); memcpy(evt->name, binName.constData(), binName.length()); memcpy(evt->data, data.constData(), data.length()); mem->lastEvent = evt->posted = result = qMax(mem->lastEvent + 1, time(nullptr)); evt->dest = dest; evt->sender = getpid(); qDebug() << "postEvent " << name << "to" << dest; } globalMemory.unlock(); return result; } bool IPC::isCurrentOwner() { if (globalMemory.lock()) { const bool isOwner = isCurrentOwnerNoLock(); globalMemory.unlock(); return isOwner; } else { qWarning() << "isCurrentOwner failed to lock, returning false"; return false; } } /** * @brief Register a handler for an IPC event * @param handler The handler callback. Should not block for more than a second, at worst */ void IPC::registerEventHandler(const QString& name, IPCEventHandler handler) { eventHandlers[name] = handler; } bool IPC::isEventAccepted(time_t time) { bool result = false; if (!globalMemory.lock()) { return result; } if (difftime(global()->lastProcessed, time) > 0) { IPCMemory* mem = global(); for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; ++i) { if (mem->events[i].posted == time && mem->events[i].processed) { result = mem->events[i].accepted; break; } } } globalMemory.unlock(); return result; } bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout /*=-1*/) { bool result = false; time_t start = time(nullptr); forever { result = isEventAccepted(postTime); if (result || (timeout > 0 && difftime(time(nullptr), start) >= timeout)) { break; } qApp->processEvents(); QThread::msleep(0); } return result; } bool IPC::isAttached() const { return globalMemory.isAttached(); } void IPC::setProfileId(uint32_t profileId) { this->profileId = profileId; } /** * @brief Only called when global memory IS LOCKED. * @return nullptr if no evnts present, IPC event otherwise */ IPC::IPCEvent* IPC::fetchEvent() { IPCMemory* mem = global(); for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; ++i) { IPCEvent* evt = &mem->events[i]; // Garbage-collect events that were not processed in EVENT_GC_TIMEOUT // and events that were processed and EVENT_GC_TIMEOUT passed after // so sending instance has time to react to those events. if ((evt->processed && difftime(time(nullptr), evt->processed) > EVENT_GC_TIMEOUT) || (!evt->processed && difftime(time(nullptr), evt->posted) > EVENT_GC_TIMEOUT)) { memset(evt, 0, sizeof(IPCEvent)); } if (evt->posted && !evt->processed && evt->sender != getpid() && (evt->dest == profileId || (evt->dest == 0 && isCurrentOwnerNoLock()))) { return evt; } } return nullptr; } bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg) { bool result = false; if (QThread::currentThread() == qApp->thread()) { result = handler(arg); } else { QMetaObject::invokeMethod(this, "runEventHandler", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(IPCEventHandler, handler), Q_ARG(const QByteArray&, arg)); } return result; } void IPC::processEvents() { if (!globalMemory.lock()) { timer.start(); return; } IPCMemory* mem = global(); if (mem->globalId == globalId) { // We're the owner, let's process those events mem->lastProcessed = time(nullptr); } else { // Only the owner processes events. But if the previous owner's dead, we can take // ownership now if (difftime(time(nullptr), mem->lastProcessed) >= OWNERSHIP_TIMEOUT_S) { qDebug() << "Previous owner timed out, taking ownership" << mem->globalId << "->" << globalId; // Ignore events that were not meant for this instance memset(mem, 0, sizeof(IPCMemory)); mem->globalId = globalId; mem->lastProcessed = time(nullptr); } // Non-main instance is limited to events destined for specific profile it runs } while (IPCEvent* evt = fetchEvent()) { QString name = QString::fromUtf8(evt->name); auto it = eventHandlers.find(name); if (it != eventHandlers.end()) { evt->accepted = runEventHandler(it.value(), evt->data); qDebug() << "Processed event:" << name << "posted:" << evt->posted << "accepted:" << evt->accepted; if (evt->dest == 0) { // Global events should be processed only by instance that accepted event. // Otherwise global // event would be consumed by very first instance that gets to check it. if (evt->accepted) { evt->processed = time(nullptr); } } else { evt->processed = time(nullptr); } } else { qDebug() << "Received event:" << name << "without handler"; qDebug() << "Available handlers:" << eventHandlers.keys(); } } globalMemory.unlock(); timer.start(); } /** * @brief Only called when global memory IS LOCKED. * @return true if owner, false if not owner or if error */ bool IPC::isCurrentOwnerNoLock() { const void* const data = globalMemory.data(); if (!data) { qWarning() << "isCurrentOwnerNoLock failed to access the memory, returning false"; return false; } return (*static_cast(data) == globalId); } IPC::IPCMemory* IPC::global() { return static_cast(globalMemory.data()); } qTox/src/ipc.h000066400000000000000000000046541415623743500135470ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IPC_H #define IPC_H #include #include #include #include #include #include using IPCEventHandler = std::function; #define IPC_PROTOCOL_VERSION "2" class IPC : public QObject { Q_OBJECT protected: static const int EVENT_TIMER_MS = 1000; static const int EVENT_GC_TIMEOUT = 5; static const int EVENT_QUEUE_SIZE = 32; static const int OWNERSHIP_TIMEOUT_S = 5; public: IPC(uint32_t profileId); ~IPC(); struct IPCEvent { uint32_t dest; int32_t sender; char name[16]; char data[128]; time_t posted; time_t processed; uint32_t flags; bool accepted; bool global; }; struct IPCMemory { uint64_t globalId; time_t lastEvent; time_t lastProcessed; IPCEvent events[IPC::EVENT_QUEUE_SIZE]; }; time_t postEvent(const QString& name, const QByteArray& data = QByteArray(), uint32_t dest = 0); bool isCurrentOwner(); void registerEventHandler(const QString& name, IPCEventHandler handler); bool isEventAccepted(time_t time); bool waitUntilAccepted(time_t time, int32_t timeout = -1); bool isAttached() const; public slots: void setProfileId(uint32_t profileId); private: IPCMemory* global(); bool runEventHandler(IPCEventHandler handler, const QByteArray& arg); IPCEvent* fetchEvent(); void processEvents(); bool isCurrentOwnerNoLock(); private: QTimer timer; uint64_t globalId; uint32_t profileId; QSharedMemory globalMemory; QMap eventHandlers; }; #endif // IPC_H qTox/src/loginscreen.ui000066400000000000000000000334311415623743500154650ustar00rootroot00000000000000 LoginScreen 0 0 410 200 410 200 415 200 qTox :/img/icons/qtox.svg:/img/icons/qtox.svg 160 0 250 200 true 0 0 0 0 0 0 QLayout::SetMaximumSize QFormLayout::AllNonFixedFieldsGrow Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Qt::AlignCenter 9 9 9 9 9 9 Username: Qt::AlignCenter Username input field 64 Password: Qt::AlignCenter Password input field, you can leave it empty (no password), or type at least 6 characters Confirm: Qt::AlignCenter Password confirmation field 0 Password strength: %p% Qt::Horizontal 40 20 Create a new profile button Create Profile Qt::Vertical 20 40 Qt::Vertical 20 40 0 0 0 0 0 0 6 9 9 9 9 Qt::Vertical 20 40 Username: Qt::AlignCenter Profile list List of profiles Password: Qt::AlignCenter Password input If the profile does not have a password, qTox can skip the login screen Load automatically checkbox Load automatically Qt::Vertical 20 40 Import profile Import Qt::Horizontal 40 20 Load selected profile button Load 10 160 135 1 QFrame::Plain Qt::Horizontal 0 130 160 22 New profile creation page New Profile 0 170 160 22 Load Profile Loading existing profile page Load Profile 30 15 100 100 :/img/login_logo.svg true PasswordEdit QLineEdit
src/widget/passwordedit.h
qTox/src/main.cpp000066400000000000000000000347431415623743500142550ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/audio/audio.h" #include "src/ipc.h" #include "src/net/toxuri.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/persistence/toxsave.h" #include "src/video/camerasource.h" #include "src/widget/loginscreen.h" #include "src/widget/translator.h" #include "widget/widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(Q_OS_OSX) #include "platform/install_osx.h" #endif #if defined(Q_OS_UNIX) #include "platform/posixsignalnotifier.h" #endif #ifdef LOG_TO_FILE static QAtomicPointer logFileFile = nullptr; static QList* logBuffer = new QList(); // Store log messages until log file opened QMutex* logBufferMutex = new QMutex(); #endif void cleanup() { // force save early even though destruction saves, because Windows OS will // close qTox before cleanup() is finished if logging out or shutting down, // once the top level window has exited, which occurs in ~Widget within // ~Nexus. Re-ordering Nexus destruction is not trivial. auto& s = Settings::getInstance(); s.saveGlobal(); s.savePersonal(); s.sync(); Nexus::destroyInstance(); CameraSource::destroyInstance(); Settings::destroyInstance(); qDebug() << "Cleanup success"; #ifdef LOG_TO_FILE #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) FILE* f = logFileFile.loadRelaxed(); #else FILE* f = logFileFile.load(); #endif if (f != nullptr) { fclose(f); #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) logFileFile.storeRelaxed(nullptr); // atomically disable logging to file #else logFileFile.store(nullptr); // atomically disable logging to file #endif } #endif } void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QString& msg) { // Silence qWarning spam due to bug in QTextBrowser (trying to open a file for base64 images) if (ctxt.function == QString("virtual bool QFSFileEngine::open(QIODevice::OpenMode)") && msg == QString("QFSFileEngine::open: No file name specified")) return; QString file = ctxt.file; // We're not using QT_MESSAGELOG_FILE here, because that can be 0, NULL, or // nullptr in release builds. QString path = QString(__FILE__); path = path.left(path.lastIndexOf('/') + 1); if (file.startsWith(path)) { file = file.mid(path.length()); } // Time should be in UTC to save user privacy on log sharing QTime time = QDateTime::currentDateTime().toUTC().time(); QString LogMsg = QString("[%1 UTC] %2:%3 : ").arg(time.toString("HH:mm:ss.zzz")).arg(file).arg(ctxt.line); switch (type) { case QtDebugMsg: LogMsg += "Debug"; break; case QtInfoMsg: LogMsg += "Info"; break; case QtWarningMsg: LogMsg += "Warning"; break; case QtCriticalMsg: LogMsg += "Critical"; break; case QtFatalMsg: LogMsg += "Fatal"; break; default: break; } LogMsg += ": " + msg + "\n"; QByteArray LogMsgBytes = LogMsg.toUtf8(); fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), stderr); #ifdef LOG_TO_FILE #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) FILE* logFilePtr = logFileFile.loadRelaxed(); // atomically load the file pointer #else FILE* logFilePtr = logFileFile.load(); // atomically load the file pointer #endif if (!logFilePtr) { logBufferMutex->lock(); if (logBuffer) logBuffer->append(LogMsgBytes); logBufferMutex->unlock(); } else { logBufferMutex->lock(); if (logBuffer) { // empty logBuffer to file foreach (QByteArray msg, *logBuffer) fwrite(msg.constData(), 1, msg.size(), logFilePtr); delete logBuffer; // no longer needed logBuffer = nullptr; } logBufferMutex->unlock(); fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), logFilePtr); fflush(logFilePtr); } #endif } int main(int argc, char* argv[]) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif qInstallMessageHandler(logMessageHandler); // initialize random number generator #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) qsrand(time(nullptr)); #endif std::unique_ptr a(new QApplication(argc, argv)); #if defined(Q_OS_UNIX) // PosixSignalNotifier is used only for terminating signals, // so it's connected directly to quit() without any filtering. QObject::connect(&PosixSignalNotifier::globalInstance(), &PosixSignalNotifier::activated, a.get(), &QApplication::quit); PosixSignalNotifier::watchCommonTerminatingSignals(); #endif a->setApplicationName("qTox"); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) a->setDesktopFileName("io.github.qtox.qTox"); #endif a->setApplicationVersion("\nGit commit: " + QString(GIT_VERSION)); // Install Unicode 6.1 supporting font // Keep this as close to the beginning of `main()` as possible, otherwise // on systems that have poor support for Unicode qTox will look bad. if (QFontDatabase::addApplicationFont("://font/DejaVuSans.ttf") == -1) { qWarning() << "Couldn't load font"; } #if defined(Q_OS_OSX) // TODO: Add setting to enable this feature. // osx::moveToAppFolder(); osx::migrateProfiles(); #endif #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) qsrand(time(nullptr)); #endif Settings& settings = Settings::getInstance(); QString locale = settings.getTranslation(); Translator::translate(locale); // Process arguments QCommandLineParser parser; parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION)); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse")); parser.addOption( QCommandLineOption(QStringList() << "p" << "profile", QObject::tr("Starts new instance and loads specified profile."), QObject::tr("profile"))); parser.addOption( QCommandLineOption(QStringList() << "l" << "login", QObject::tr("Starts new instance and opens the login screen."))); parser.addOption(QCommandLineOption(QStringList() << "I" << "IPv6", QObject::tr("Sets IPv6 /. Default is ON."), QObject::tr("on/off"))); parser.addOption(QCommandLineOption(QStringList() << "U" << "UDP", QObject::tr("Sets UDP /. Default is ON."), QObject::tr("on/off"))); parser.addOption( QCommandLineOption(QStringList() << "L" << "LAN", QObject::tr( "Sets LAN discovery /. UDP off overrides. Default is ON."), QObject::tr("on/off"))); parser.addOption(QCommandLineOption(QStringList() << "P" << "proxy", QObject::tr("Sets proxy settings. Default is NONE."), QObject::tr("(SOCKS5/HTTP/NONE):(ADDRESS):(PORT)"))); parser.process(*a); uint32_t profileId = settings.getCurrentProfileId(); IPC ipc(profileId); if (ipc.isAttached()) { QObject::connect(&settings, &Settings::currentProfileIdChanged, &ipc, &IPC::setProfileId); } else { qWarning() << "Can't init IPC, maybe we're in a jail? Continuing with reduced multi-client functionality."; } // For the auto-updater if (sodium_init() < 0) { qCritical() << "Can't init libsodium"; return EXIT_FAILURE; } #ifdef LOG_TO_FILE QString logFileDir = settings.getAppCacheDirPath(); QDir(logFileDir).mkpath("."); QString logfile = logFileDir + "qtox.log"; FILE* mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a"); // Trim log file if over 1MB if (QFileInfo(logfile).size() > 1000000) { qDebug() << "Log file over 1MB, rotating..."; // close old logfile (need for windows) if (mainLogFilePtr) fclose(mainLogFilePtr); QDir dir(logFileDir); // Check if log.1 already exists, and if so, delete it if (dir.remove(logFileDir + "qtox.log.1")) qDebug() << "Removed old log successfully"; else qWarning() << "Unable to remove old log file"; if (!dir.rename(logFileDir + "qtox.log", logFileDir + "qtox.log.1")) qCritical() << "Unable to move logs"; // open a new logfile mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a"); } if (!mainLogFilePtr) qCritical() << "Couldn't open logfile" << logfile; #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) logFileFile.storeRelaxed(mainLogFilePtr); // atomically set the logFile #else logFileFile.store(mainLogFilePtr); // atomically set the logFile #endif #endif // Windows platform plugins DLL hell fix QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); a->addLibraryPath("platforms"); qDebug() << "commit: " << GIT_VERSION; QString profileName; bool autoLogin = settings.getAutoLogin(); uint32_t ipcDest = 0; bool doIpc = ipc.isAttached(); QString eventType, firstParam; if (parser.isSet("p")) { profileName = parser.value("p"); if (!Profile::exists(profileName)) { qWarning() << "-p profile" << profileName + ".tox" << "doesn't exist, opening login screen"; doIpc = false; autoLogin = false; } else { ipcDest = Settings::makeProfileId(profileName); autoLogin = true; } } else if (parser.isSet("l")) { doIpc = false; autoLogin = false; } else { profileName = settings.getCurrentProfile(); } if (parser.positionalArguments().empty()) { eventType = "activate"; } else { firstParam = parser.positionalArguments()[0]; // Tox URIs. If there's already another qTox instance running, we ask it to handle the URI // and we exit // Otherwise we start a new qTox instance and process it ourselves if (firstParam.startsWith("tox:")) { eventType = "uri"; } else if (firstParam.endsWith(".tox")) { eventType = "save"; } else { qCritical() << "Invalid argument"; return EXIT_FAILURE; } } if (doIpc && !ipc.isCurrentOwner()) { time_t event = ipc.postEvent(eventType, firstParam.toUtf8(), ipcDest); // If someone else processed it, we're done here, no need to actually start qTox if (ipc.waitUntilAccepted(event, 2)) { if (eventType == "activate") { qDebug() << "Another qTox instance is already running. If you want to start a second " "instance, please open login screen (qtox -l) or start with a profile (qtox " "-p )."; } else { qDebug() << "Event" << eventType << "was handled by other client."; } return EXIT_SUCCESS; } } if (!Settings::verifyProxySettings(parser)) { return -1; } // TODO(sudden6): remove once we get rid of Nexus Nexus& nexus = Nexus::getInstance(); // TODO(kriby): Consider moving application initializing variables into a globalSettings object // note: Because Settings is shouldering global settings as well as model specific ones it // cannot be integrated into a central model object yet nexus.setSettings(&settings); // Autologin // TODO (kriby): Shift responsibility of linking views to model objects from nexus // Further: generate view instances separately (loginScreen, mainGUI, audio) Profile* profile = nullptr; if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) { profile = Profile::loadProfile(profileName, &parser); if (!profile) { QMessageBox::information(nullptr, QObject::tr("Error"), QObject::tr("Failed to load profile automatically.")); } } if (profile) { nexus.bootstrapWithProfile(profile); } else { nexus.setParser(&parser); int returnval = nexus.showLogin(profileName); if (returnval == QDialog::Rejected) { return -1; } } if (ipc.isAttached()) { // Start to accept Inter-process communication ipc.registerEventHandler("uri", &toxURIEventHandler); ipc.registerEventHandler("save", &toxSaveEventHandler); ipc.registerEventHandler("activate", &toxActivateEventHandler); } // Event was not handled by already running instance therefore we handle it ourselves if (eventType == "uri") handleToxURI(firstParam.toUtf8()); else if (eventType == "save") handleToxSave(firstParam.toUtf8()); QObject::connect(a.get(), &QApplication::aboutToQuit, cleanup); // Run int errorcode = a->exec(); qDebug() << "Exit with status" << errorcode; return errorcode; } qTox/src/mainwindow.ui000066400000000000000000001155471415623743500153420ustar00rootroot00000000000000 MainWindow 0 0 775 420 775 420 qTox :/img/icons/qtox.svg:/img/icons/qtox.svg 0 0 0 0 0 0 16777215 16777215 true false Qt::Horizontal 6 false 0 0 0 0 0 0 0 true 0 0 0 Qt::Horizontal QSizePolicy::Maximum 5 20 0 true 1 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 240 240 240 255 255 255 255 255 255 255 255 255 11 75 true PointingHandCursor Open profile Open profile page when clicked Your name Qt::PlainText 1 0 193 193 193 193 193 193 193 193 193 193 193 193 14 14 14 14 14 14 8 Status message input Set your status message that will be shown to others Your status Qt::PlainText Qt::Horizontal QSizePolicy::Fixed 10 20 0 0 20 40 Qt::NoFocus Status Set availability status 10 10 false false false true 0 0 0 0 Contact search Contact search input for known friends true 0 0 Sorting and visibility Set friends sorting and visibility ... QToolButton::InstantPopup Qt::ToolButtonTextBesideIcon Qt::NoArrow 0 0 Qt::LeftToRight true QFrame::NoFrame Qt::ScrollBarAlwaysOff true 0 0 775 283 true 0 0 0 0 0 0 0 55 35 55 35 Qt::NoFocus Add friends Add friends Open Add friends page false :/img/add.svg:/img/add.svg 15 15 false false false 55 35 Qt::NoFocus Create a group chat Groupchat Open groupchat management page false :/img/group.svg:/img/group.svg 15 15 true 55 35 55 35 Qt::NoFocus View completed file transfers File transfers history Open File transfers history :/img/transfer.svg:/img/transfer.svg 15 15 true 55 35 55 35 Qt::NoFocus Change your settings Settings Open Settings :/img/settings.svg:/img/settings.svg 15 15 true Close Ctrl+Q CroppingLabel QLabel
src/widget/tool/croppinglabel.h
NotificationScrollArea QScrollArea
src/widget/notificationscrollarea.h
1
qTox/src/model/000077500000000000000000000000001415623743500137125ustar00rootroot00000000000000qTox/src/model/about/000077500000000000000000000000001415623743500150245ustar00rootroot00000000000000qTox/src/model/about/aboutfriend.cpp000066400000000000000000000074611415623743500200420ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "aboutfriend.h" #include "src/model/friend.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/persistence/ifriendsettings.h" AboutFriend::AboutFriend(const Friend* f, IFriendSettings* const s) : f{f} , settings{s} { s->connectTo_contactNoteChanged(this, [=](const ToxPk& pk, const QString& note) { emit noteChanged(note); }); s->connectTo_autoAcceptCallChanged(this, [=](const ToxPk& pk, IFriendSettings::AutoAcceptCallFlags flag) { emit autoAcceptCallChanged(flag); }); s->connectTo_autoAcceptDirChanged(this, [=](const ToxPk& pk, const QString& dir) { emit autoAcceptDirChanged(dir); }); s->connectTo_autoGroupInviteChanged(this, [=](const ToxPk& pk, bool enable) { emit autoGroupInviteChanged(enable); }); } QString AboutFriend::getName() const { return f->getDisplayedName(); } QString AboutFriend::getStatusMessage() const { return f->getStatusMessage(); } ToxPk AboutFriend::getPublicKey() const { return f->getPublicKey(); } QPixmap AboutFriend::getAvatar() const { const ToxPk pk = f->getPublicKey(); const QPixmap avatar = Nexus::getProfile()->loadAvatar(pk); return avatar.isNull() ? QPixmap(QStringLiteral(":/img/contact_dark.svg")) : avatar; } QString AboutFriend::getNote() const { const ToxPk pk = f->getPublicKey(); return settings->getContactNote(pk); } void AboutFriend::setNote(const QString& note) { const ToxPk pk = f->getPublicKey(); settings->setContactNote(pk, note); settings->saveFriendSettings(pk); } QString AboutFriend::getAutoAcceptDir() const { const ToxPk pk = f->getPublicKey(); return settings->getAutoAcceptDir(pk); } void AboutFriend::setAutoAcceptDir(const QString& path) { const ToxPk pk = f->getPublicKey(); settings->setAutoAcceptDir(pk, path); settings->saveFriendSettings(pk); } IFriendSettings::AutoAcceptCallFlags AboutFriend::getAutoAcceptCall() const { const ToxPk pk = f->getPublicKey(); return settings->getAutoAcceptCall(pk); } void AboutFriend::setAutoAcceptCall(IFriendSettings::AutoAcceptCallFlags flag) { const ToxPk pk = f->getPublicKey(); settings->setAutoAcceptCall(pk, flag); settings->saveFriendSettings(pk); } bool AboutFriend::getAutoGroupInvite() const { const ToxPk pk = f->getPublicKey(); return settings->getAutoGroupInvite(pk); } void AboutFriend::setAutoGroupInvite(bool enabled) { const ToxPk pk = f->getPublicKey(); settings->setAutoGroupInvite(pk, enabled); settings->saveFriendSettings(pk); } bool AboutFriend::clearHistory() { const ToxPk pk = f->getPublicKey(); History* const history = Nexus::getProfile()->getHistory(); if (history) { history->removeFriendHistory(pk.toString()); return true; } return false; } bool AboutFriend::isHistoryExistence() { History* const history = Nexus::getProfile()->getHistory(); if (history) { const ToxPk pk = f->getPublicKey(); return history->historyExists(pk); } return false; } qTox/src/model/about/aboutfriend.h000066400000000000000000000046021415623743500175010ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ABOUT_FRIEND_H #define ABOUT_FRIEND_H #include "iaboutfriend.h" #include "src/model/interface.h" #include "src/persistence/ifriendsettings.h" #include class Friend; class IFriendSettings; class AboutFriend : public QObject, public IAboutFriend { Q_OBJECT public: AboutFriend(const Friend* f, IFriendSettings* const settings); QString getName() const override; QString getStatusMessage() const override; ToxPk getPublicKey() const override; QPixmap getAvatar() const override; QString getNote() const override; void setNote(const QString& note) override; QString getAutoAcceptDir() const override; void setAutoAcceptDir(const QString& path) override; IFriendSettings::AutoAcceptCallFlags getAutoAcceptCall() const override; void setAutoAcceptCall(IFriendSettings::AutoAcceptCallFlags flag) override; bool getAutoGroupInvite() const override; void setAutoGroupInvite(bool enabled) override; bool clearHistory() override; bool isHistoryExistence() override; SIGNAL_IMPL(AboutFriend, nameChanged, const QString&) SIGNAL_IMPL(AboutFriend, statusChanged, const QString&) SIGNAL_IMPL(AboutFriend, publicKeyChanged, const QString&) SIGNAL_IMPL(AboutFriend, avatarChanged, const QPixmap&) SIGNAL_IMPL(AboutFriend, noteChanged, const QString&) SIGNAL_IMPL(AboutFriend, autoAcceptDirChanged, const QString&) SIGNAL_IMPL(AboutFriend, autoAcceptCallChanged, IFriendSettings::AutoAcceptCallFlags) SIGNAL_IMPL(AboutFriend, autoGroupInviteChanged, bool) private: const Friend* const f; IFriendSettings* const settings; }; #endif // ABOUT_FRIEND_H qTox/src/model/about/iaboutfriend.h000066400000000000000000000042421415623743500176520ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef I_ABOUT_FRIEND_H #define I_ABOUT_FRIEND_H #include "src/model/interface.h" #include "src/persistence/ifriendsettings.h" #include class IAboutFriend { public: virtual ~IAboutFriend() = default; virtual QString getName() const = 0; virtual QString getStatusMessage() const = 0; virtual ToxPk getPublicKey() const = 0; virtual QPixmap getAvatar() const = 0; virtual QString getNote() const = 0; virtual void setNote(const QString& note) = 0; virtual QString getAutoAcceptDir() const = 0; virtual void setAutoAcceptDir(const QString& path) = 0; virtual IFriendSettings::AutoAcceptCallFlags getAutoAcceptCall() const = 0; virtual void setAutoAcceptCall(IFriendSettings::AutoAcceptCallFlags flag) = 0; virtual bool getAutoGroupInvite() const = 0; virtual void setAutoGroupInvite(bool enabled) = 0; virtual bool clearHistory() = 0; virtual bool isHistoryExistence() = 0; /* signals */ DECLARE_SIGNAL(nameChanged, const QString&); DECLARE_SIGNAL(statusChanged, const QString&); DECLARE_SIGNAL(publicKeyChanged, const QString&); DECLARE_SIGNAL(avatarChanged, const QPixmap&); DECLARE_SIGNAL(noteChanged, const QString&); DECLARE_SIGNAL(autoAcceptDirChanged, const QString&); DECLARE_SIGNAL(autoAcceptCallChanged, IFriendSettings::AutoAcceptCallFlags); DECLARE_SIGNAL(autoGroupInviteChanged, bool); }; #endif // I_ABOUT_FRIEND_H qTox/src/model/chathistory.cpp000066400000000000000000000427341415623743500167710ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chathistory.h" #include "src/persistence/settings.h" #include "src/widget/form/chatform.h" namespace { /** * @brief Determines if the given idx needs to be loaded from history * @param[in] idx index to check * @param[in] sessionChatLog SessionChatLog containing currently loaded items * @return True if load is needed */ bool needsLoadFromHistory(ChatLogIdx idx, const SessionChatLog& sessionChatLog) { return idx < sessionChatLog.getFirstIdx(); } /** * @brief Finds the first item in sessionChatLog that contains a message * @param[in] sessionChatLog * @return index of first message */ ChatLogIdx findFirstMessage(const SessionChatLog& sessionChatLog) { auto it = sessionChatLog.getFirstIdx(); while (it < sessionChatLog.getNextIdx()) { if (sessionChatLog.at(it).getContentType() == ChatLogItem::ContentType::message) { return it; } it++; } return ChatLogIdx(-1); } /** * @brief Handles presence of aciton prefix in content * @param[in/out] content * @return True if was an action */ bool handleActionPrefix(QString& content) { // Unfortunately due to legacy reasons we have to continue // inserting and parsing for ACTION_PREFIX in our messages even // though we have the ability to something more intelligent now // that we aren't owned by chatform logic auto isAction = content.startsWith(ChatForm::ACTION_PREFIX, Qt::CaseInsensitive); if (isAction) { content.remove(0, ChatForm::ACTION_PREFIX.size()); } return isAction; } } // namespace ChatHistory::ChatHistory(Friend& f_, History* history_, const ICoreIdHandler& coreIdHandler, const Settings& settings_, IMessageDispatcher& messageDispatcher) : f(f_) , history(history_) , settings(settings_) , coreIdHandler(coreIdHandler) , sessionChatLog(getInitialChatLogIdx(), coreIdHandler) { connect(&messageDispatcher, &IMessageDispatcher::messageComplete, this, &ChatHistory::onMessageComplete); connect(&messageDispatcher, &IMessageDispatcher::messageReceived, this, &ChatHistory::onMessageReceived); if (canUseHistory()) { // Defer messageSent callback until we finish firing off all our unsent messages. // If it was connected all our unsent messages would be re-added ot history again dispatchUnsentMessages(messageDispatcher); } // Now that we've fired off our unsent messages we can connect the message connect(&messageDispatcher, &IMessageDispatcher::messageSent, this, &ChatHistory::onMessageSent); // NOTE: this has to be done _after_ sending all sent messages since initial // state of the message has to be marked according to our dispatch state constexpr auto defaultNumMessagesToLoad = 100; auto firstChatLogIdx = sessionChatLog.getFirstIdx().get() < defaultNumMessagesToLoad ? ChatLogIdx(0) : sessionChatLog.getFirstIdx() - defaultNumMessagesToLoad; if (canUseHistory()) { loadHistoryIntoSessionChatLog(firstChatLogIdx); } // We don't manage any of the item updates ourselves, we just forward along // the underlying sessionChatLog's updates connect(&sessionChatLog, &IChatLog::itemUpdated, this, &IChatLog::itemUpdated); } const ChatLogItem& ChatHistory::at(ChatLogIdx idx) const { if (canUseHistory()) { ensureIdxInSessionChatLog(idx); } return sessionChatLog.at(idx); } SearchResult ChatHistory::searchForward(SearchPos startIdx, const QString& phrase, const ParameterSearch& parameter) const { if (startIdx.logIdx >= getNextIdx()) { SearchResult res; res.found = false; return res; } if (canUseHistory()) { ensureIdxInSessionChatLog(startIdx.logIdx); } return sessionChatLog.searchForward(startIdx, phrase, parameter); } SearchResult ChatHistory::searchBackward(SearchPos startIdx, const QString& phrase, const ParameterSearch& parameter) const { auto res = sessionChatLog.searchBackward(startIdx, phrase, parameter); if (res.found || !canUseHistory()) { return res; } auto earliestMessage = findFirstMessage(sessionChatLog); auto earliestMessageDate = (earliestMessage == ChatLogIdx(-1)) ? QDateTime::currentDateTime() : sessionChatLog.at(earliestMessage).getContentAsMessage().message.timestamp; // Roundabout way of getting the first idx but I don't want to have to // deal with re-implementing so we'll just piece what we want together... // // If the double disk access is real bad we can optimize this by adding // another function to history auto dateWherePhraseFound = history->getDateWhereFindPhrase(f.getPublicKey().toString(), earliestMessageDate, phrase, parameter); auto loadIdx = history->getNumMessagesForFriendBeforeDate(f.getPublicKey(), dateWherePhraseFound); loadHistoryIntoSessionChatLog(ChatLogIdx(loadIdx)); // Reset search pos to the message we just loaded to avoid a double search startIdx.logIdx = ChatLogIdx(loadIdx); startIdx.numMatches = 0; return sessionChatLog.searchBackward(startIdx, phrase, parameter); } ChatLogIdx ChatHistory::getFirstIdx() const { if (canUseHistory()) { return ChatLogIdx(0); } else { return sessionChatLog.getFirstIdx(); } } ChatLogIdx ChatHistory::getNextIdx() const { return sessionChatLog.getNextIdx(); } std::vector ChatHistory::getDateIdxs(const QDate& startDate, size_t maxDates) const { if (canUseHistory()) { auto counts = history->getNumMessagesForFriendBeforeDateBoundaries(f.getPublicKey(), startDate, maxDates); std::vector ret; std::transform(counts.begin(), counts.end(), std::back_inserter(ret), [&](const History::DateIdx& historyDateIdx) { DateChatLogIdxPair pair; pair.date = historyDateIdx.date; pair.idx.get() = historyDateIdx.numMessagesIn; return pair; }); // Do not re-search in the session chat log. If we have history the query to the history should have been sufficient return ret; } else { return sessionChatLog.getDateIdxs(startDate, maxDates); } } void ChatHistory::onFileUpdated(const ToxPk& sender, const ToxFile& file) { if (canUseHistory()) { switch (file.status) { case ToxFile::INITIALIZING: { // Note: There is some implcit coupling between history and the current // chat log. Both rely on generating a new id based on the state of // initializing. If this is changed in the session chat log we'll end up // with a different order when loading from history history->addNewFileMessage(f.getPublicKey().toString(), file.resumeFileId, file.fileName, file.filePath, file.filesize, sender.toString(), QDateTime::currentDateTime(), f.getDisplayedName()); break; } case ToxFile::CANCELED: case ToxFile::FINISHED: case ToxFile::BROKEN: { const bool isSuccess = file.status == ToxFile::FINISHED; history->setFileFinished(file.resumeFileId, isSuccess, file.filePath, file.hashGenerator->result()); break; } case ToxFile::PAUSED: case ToxFile::TRANSMITTING: default: break; } } sessionChatLog.onFileUpdated(sender, file); } void ChatHistory::onFileTransferRemotePausedUnpaused(const ToxPk& sender, const ToxFile& file, bool paused) { sessionChatLog.onFileTransferRemotePausedUnpaused(sender, file, paused); } void ChatHistory::onFileTransferBrokenUnbroken(const ToxPk& sender, const ToxFile& file, bool broken) { sessionChatLog.onFileTransferBrokenUnbroken(sender, file, broken); } void ChatHistory::onMessageReceived(const ToxPk& sender, const Message& message) { if (canUseHistory()) { auto friendPk = f.getPublicKey().toString(); auto displayName = f.getDisplayedName(); auto content = message.content; if (message.isAction) { content = ChatForm::ACTION_PREFIX + content; } history->addNewMessage(friendPk, content, friendPk, message.timestamp, true, displayName); } sessionChatLog.onMessageReceived(sender, message); } void ChatHistory::onMessageSent(DispatchedMessageId id, const Message& message) { if (canUseHistory()) { auto selfPk = coreIdHandler.getSelfPublicKey().toString(); auto friendPk = f.getPublicKey().toString(); auto content = message.content; if (message.isAction) { content = ChatForm::ACTION_PREFIX + content; } auto username = coreIdHandler.getUsername(); auto onInsertion = [this, id](RowId historyId) { handleDispatchedMessage(id, historyId); }; history->addNewMessage(friendPk, content, selfPk, message.timestamp, false, username, onInsertion); } sessionChatLog.onMessageSent(id, message); } void ChatHistory::onMessageComplete(DispatchedMessageId id) { if (canUseHistory()) { completeMessage(id); } sessionChatLog.onMessageComplete(id); } /** * @brief Forces the given index and all future indexes to be in the chatlog * @param[in] idx * @note Marked const since this doesn't change _external_ state of the class. We still have all the same items at all the same indexes, we've just stuckem in ram */ void ChatHistory::ensureIdxInSessionChatLog(ChatLogIdx idx) const { if (needsLoadFromHistory(idx, sessionChatLog)) { loadHistoryIntoSessionChatLog(idx); } } /** * @brief Unconditionally loads the given index and all future messages that * are not in the session chat log into the session chat log * @param[in] idx * @note Marked const since this doesn't change _external_ state of the class. We still have all the same items at all the same indexes, we've just stuckem in ram * @note no end idx as we always load from start -> latest. In the future we * could have a less contiguous history */ void ChatHistory::loadHistoryIntoSessionChatLog(ChatLogIdx start) const { if (!needsLoadFromHistory(start, sessionChatLog)) { return; } auto end = sessionChatLog.getFirstIdx(); // We know that both history and us have a start index of 0 so the type // conversion should be safe assert(getFirstIdx() == ChatLogIdx(0)); auto messages = history->getMessagesForFriend(f.getPublicKey(), start.get(), end.get()); assert(messages.size() == end.get() - start.get()); ChatLogIdx nextIdx = start; for (const auto& message : messages) { // Note that message.id is _not_ a valid conversion here since it is a // global id not a per-chat id like the ChatLogIdx auto currentIdx = nextIdx++; auto sender = ToxId(message.sender).getPublicKey(); switch (message.content.getType()) { case HistMessageContentType::file: { const auto date = message.timestamp; const auto file = message.content.asFile(); const auto chatLogFile = ChatLogFile{date, file}; sessionChatLog.insertFileAtIdx(currentIdx, sender, message.dispName, chatLogFile); break; } case HistMessageContentType::message: { auto messageContent = message.content.asMessage(); auto isAction = handleActionPrefix(messageContent); // It's okay to skip the message processor here. The processor is // meant to convert between boundaries of our internal // representation. We already had to go through the processor before // we hit IMessageDispatcher's signals which history listens for. // Items added to history have already been sent so we know they already // reflect what was sent/received. auto processedMessage = Message{isAction, messageContent, message.timestamp}; auto dispatchedMessageIt = std::find_if(dispatchedMessageRowIdMap.begin(), dispatchedMessageRowIdMap.end(), [&](RowId dispatchedId) { return dispatchedId == message.id; }); assert((message.state != MessageState::pending && dispatchedMessageIt == dispatchedMessageRowIdMap.end()) || (message.state == MessageState::pending && dispatchedMessageIt != dispatchedMessageRowIdMap.end())); auto chatLogMessage = ChatLogMessage{message.state, processedMessage}; switch (message.state) { case MessageState::complete: sessionChatLog.insertCompleteMessageAtIdx(currentIdx, sender, message.dispName, chatLogMessage); break; case MessageState::pending: sessionChatLog.insertIncompleteMessageAtIdx(currentIdx, sender, message.dispName, chatLogMessage, dispatchedMessageIt.key()); break; case MessageState::broken: sessionChatLog.insertBrokenMessageAtIdx(currentIdx, sender, message.dispName, chatLogMessage); break; } break; } } } assert(nextIdx == end); } /** * @brief Sends any unsent messages in history to the underlying message dispatcher * @param[in] messageDispatcher */ void ChatHistory::dispatchUnsentMessages(IMessageDispatcher& messageDispatcher) { auto unsentMessages = history->getUndeliveredMessagesForFriend(f.getPublicKey()); for (auto& message : unsentMessages) { // We should only store messages as unsent, if this changes in the // future we need to extend this logic assert(message.content.getType() == HistMessageContentType::message); auto messageContent = message.content.asMessage(); auto isAction = handleActionPrefix(messageContent); // NOTE: timestamp will be generated in messageDispatcher but we haven't // hooked up our history callback so it will not be shown in our chatlog // with the new timestamp. This is intentional as everywhere else we use // attempted send time (which is whenever the it was initially inserted // into history auto dispatchIds = messageDispatcher.sendMessage(isAction, messageContent); // We should only send a single message, but in the odd case where we end // up having to split more than when we added the message to history we'll // just associate the last dispatched id with the history message handleDispatchedMessage(dispatchIds.second, message.id); // We don't add the messages to the underlying chatlog since // 1. We don't even know the ChatLogIdx of this message // 2. We only want to display the latest N messages on boot by default, // even if there are more than N messages that haven't been sent } } void ChatHistory::handleDispatchedMessage(DispatchedMessageId dispatchId, RowId historyId) { auto completedMessageIt = completedMessages.find(dispatchId); if (completedMessageIt == completedMessages.end()) { dispatchedMessageRowIdMap.insert(dispatchId, historyId); } else { history->markAsDelivered(historyId); completedMessages.erase(completedMessageIt); } } void ChatHistory::completeMessage(DispatchedMessageId id) { auto dispatchedMessageIt = dispatchedMessageRowIdMap.find(id); if (dispatchedMessageIt == dispatchedMessageRowIdMap.end()) { completedMessages.insert(id); } else { history->markAsDelivered(*dispatchedMessageIt); dispatchedMessageRowIdMap.erase(dispatchedMessageIt); } } bool ChatHistory::canUseHistory() const { return history && settings.getEnableLogging(); } /** * @brief Gets the initial chat log index for a sessionChatLog with 0 items loaded from history. * Needed to keep history indexes in sync with chat log indexes * @param[in] history * @param[in] f * @return Initial chat log index */ ChatLogIdx ChatHistory::getInitialChatLogIdx() const { if (canUseHistory()) { return ChatLogIdx(history->getNumMessagesForFriend(f.getPublicKey())); } return ChatLogIdx(0); } qTox/src/model/chathistory.h000066400000000000000000000060771415623743500164360ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHAT_HISTORY_H #define CHAT_HISTORY_H #include "ichatlog.h" #include "sessionchatlog.h" #include "src/persistence/history.h" #include class Settings; class ChatHistory : public IChatLog { Q_OBJECT public: ChatHistory(Friend& f_, History* history_, const ICoreIdHandler& coreIdHandler, const Settings& settings, IMessageDispatcher& messageDispatcher); const ChatLogItem& at(ChatLogIdx idx) const override; SearchResult searchForward(SearchPos startIdx, const QString& phrase, const ParameterSearch& parameter) const override; SearchResult searchBackward(SearchPos startIdx, const QString& phrase, const ParameterSearch& parameter) const override; ChatLogIdx getFirstIdx() const override; ChatLogIdx getNextIdx() const override; std::vector getDateIdxs(const QDate& startDate, size_t maxDates) const override; public slots: void onFileUpdated(const ToxPk& sender, const ToxFile& file); void onFileTransferRemotePausedUnpaused(const ToxPk& sender, const ToxFile& file, bool paused); void onFileTransferBrokenUnbroken(const ToxPk& sender, const ToxFile& file, bool broken); private slots: void onMessageReceived(const ToxPk& sender, const Message& message); void onMessageSent(DispatchedMessageId id, const Message& message); void onMessageComplete(DispatchedMessageId id); private: void ensureIdxInSessionChatLog(ChatLogIdx idx) const; void loadHistoryIntoSessionChatLog(ChatLogIdx start) const; void dispatchUnsentMessages(IMessageDispatcher& messageDispatcher); void handleDispatchedMessage(DispatchedMessageId dispatchId, RowId historyId); void completeMessage(DispatchedMessageId id); bool canUseHistory() const; ChatLogIdx getInitialChatLogIdx() const; Friend& f; History* history; const Settings& settings; const ICoreIdHandler& coreIdHandler; mutable SessionChatLog sessionChatLog; // If a message completes before it's inserted into history it will end up // in this set QSet completedMessages; // If a message is inserted into history before it gets a completion // callback it will end up in this map QMap dispatchedMessageRowIdMap; }; #endif /*CHAT_HISTORY_H*/ qTox/src/model/chatlogitem.cpp000066400000000000000000000101531415623743500167160ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chatlogitem.h" #include "src/core/core.h" #include "src/friendlist.h" #include "src/grouplist.h" #include "src/model/friend.h" #include "src/model/group.h" #include namespace { /** * Helper template to get the correct deleter function for our type erased unique_ptr */ template struct ChatLogItemDeleter { static void doDelete(void* ptr) { delete static_cast(ptr); } }; QString resolveToxPk(const ToxPk& pk) { Friend* f = FriendList::findFriend(pk); if (f) { return f->getDisplayedName(); } for (Group* it : GroupList::getAllGroups()) { QString res = it->resolveToxId(pk); if (!res.isEmpty()) { return res; } } return pk.toString(); } QString resolveSenderNameFromSender(const ToxPk& sender) { // TODO: Remove core instance const Core* core = Core::getInstance(); // In unit tests we don't have a core instance so we just stringize the key if (!core) { return sender.toString(); } bool isSelf = sender == core->getSelfId().getPublicKey(); QString myNickName = core->getUsername().isEmpty() ? sender.toString() : core->getUsername(); return isSelf ? myNickName : resolveToxPk(sender); } } // namespace ChatLogItem::ChatLogItem(ToxPk sender_, ChatLogFile file_) : ChatLogItem(std::move(sender_), ContentType::fileTransfer, ContentPtr(new ChatLogFile(std::move(file_)), ChatLogItemDeleter::doDelete)) {} ChatLogItem::ChatLogItem(ToxPk sender_, ChatLogMessage message_) : ChatLogItem(sender_, ContentType::message, ContentPtr(new ChatLogMessage(std::move(message_)), ChatLogItemDeleter::doDelete)) {} ChatLogItem::ChatLogItem(ToxPk sender_, ContentType contentType_, ContentPtr content_) : sender(std::move(sender_)) , displayName(resolveSenderNameFromSender(sender)) , contentType(contentType_) , content(std::move(content_)) {} const ToxPk& ChatLogItem::getSender() const { return sender; } ChatLogItem::ContentType ChatLogItem::getContentType() const { return contentType; } ChatLogFile& ChatLogItem::getContentAsFile() { assert(contentType == ContentType::fileTransfer); return *static_cast(content.get()); } const ChatLogFile& ChatLogItem::getContentAsFile() const { assert(contentType == ContentType::fileTransfer); return *static_cast(content.get()); } ChatLogMessage& ChatLogItem::getContentAsMessage() { assert(contentType == ContentType::message); return *static_cast(content.get()); } const ChatLogMessage& ChatLogItem::getContentAsMessage() const { assert(contentType == ContentType::message); return *static_cast(content.get()); } QDateTime ChatLogItem::getTimestamp() const { switch (contentType) { case ChatLogItem::ContentType::message: { const auto& message = getContentAsMessage(); return message.message.timestamp; } case ChatLogItem::ContentType::fileTransfer: { const auto& file = getContentAsFile(); return file.timestamp; } } assert(false); return QDateTime(); } void ChatLogItem::setDisplayName(QString name) { displayName = name; } const QString& ChatLogItem::getDisplayName() const { return displayName; } qTox/src/model/chatlogitem.h000066400000000000000000000036721415623743500163730ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHAT_LOG_ITEM_H #define CHAT_LOG_ITEM_H #include "src/core/toxfile.h" #include "src/core/toxpk.h" #include "src/model/message.h" #include "src/persistence/history.h" #include struct ChatLogMessage { MessageState state; Message message; }; struct ChatLogFile { QDateTime timestamp; ToxFile file; }; class ChatLogItem { private: using ContentPtr = std::unique_ptr; public: enum class ContentType { message, fileTransfer, }; ChatLogItem(ToxPk sender, ChatLogFile file); ChatLogItem(ToxPk sender, ChatLogMessage message); const ToxPk& getSender() const; ContentType getContentType() const; ChatLogFile& getContentAsFile(); const ChatLogFile& getContentAsFile() const; ChatLogMessage& getContentAsMessage(); const ChatLogMessage& getContentAsMessage() const; QDateTime getTimestamp() const; void setDisplayName(QString name); const QString& getDisplayName() const; private: ChatLogItem(ToxPk sender, ContentType contentType, ContentPtr content); ToxPk sender; QString displayName; ContentType contentType; ContentPtr content; }; #endif /*CHAT_LOG_ITEM_H*/ qTox/src/model/chatroom/000077500000000000000000000000001415623743500155265ustar00rootroot00000000000000qTox/src/model/chatroom/chatroom.h000066400000000000000000000016661415623743500175240ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef MODEL_CHATROOM_H #define MODEL_CHATROOM_H #include "src/model/contact.h" class Chatroom { public: virtual Contact* getContact() = 0; }; #endif /* MODEL_CHATROOM_H */ qTox/src/model/chatroom/friendchatroom.cpp000066400000000000000000000120351415623743500212370ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/grouplist.h" #include "src/model/chatroom/friendchatroom.h" #include "src/model/dialogs/idialogsmanager.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/model/status.h" #include "src/persistence/settings.h" #include "src/widget/contentdialog.h" #include namespace { QString getShortName(const QString& name) { constexpr auto MAX_NAME_LENGTH = 30; if (name.length() <= MAX_NAME_LENGTH) { return name; } return name.left(MAX_NAME_LENGTH).trimmed() + "…"; } } FriendChatroom::FriendChatroom(Friend* frnd, IDialogsManager* dialogsManager) : frnd{frnd} , dialogsManager{dialogsManager} { } Friend* FriendChatroom::getFriend() { return frnd; } Contact* FriendChatroom::getContact() { return frnd; } void FriendChatroom::setActive(bool _active) { if (active != _active) { active = _active; emit activeChanged(active); } } bool FriendChatroom::canBeInvited() const { return Status::isOnline(frnd->getStatus()); } int FriendChatroom::getCircleId() const { return Settings::getInstance().getFriendCircleID(frnd->getPublicKey()); } QString FriendChatroom::getCircleName() const { const auto circleId = getCircleId(); return Settings::getInstance().getCircleName(circleId); } void FriendChatroom::inviteToNewGroup() { auto core = Core::getInstance(); const auto friendId = frnd->getId(); const auto groupId = core->createGroup(); core->groupInviteFriend(friendId, groupId); } QString FriendChatroom::getAutoAcceptDir() const { const auto pk = frnd->getPublicKey(); return Settings::getInstance().getAutoAcceptDir(pk); } void FriendChatroom::setAutoAcceptDir(const QString& dir) { const auto pk = frnd->getPublicKey(); Settings::getInstance().setAutoAcceptDir(pk, dir); } void FriendChatroom::disableAutoAccept() { setAutoAcceptDir(QString{}); } bool FriendChatroom::autoAcceptEnabled() const { return getAutoAcceptDir().isEmpty(); } void FriendChatroom::inviteFriend(const Group* group) { const auto friendId = frnd->getId(); const auto groupId = group->getId(); Core::getInstance()->groupInviteFriend(friendId, groupId); } QVector FriendChatroom::getGroups() const { QVector groups; for (const auto group : GroupList::getAllGroups()) { const auto name = getShortName(group->getName()); const GroupToDisplay groupToDisplay = { name, group }; groups.push_back(groupToDisplay); } return groups; } /** * @brief Return sorted list of circles exclude current circle. */ QVector FriendChatroom::getOtherCircles() const { QVector circles; const auto currentCircleId = getCircleId(); const auto& s = Settings::getInstance(); for (int i = 0; i < s.getCircleCount(); ++i) { if (i == currentCircleId) { continue; } const auto name = getShortName(s.getCircleName(i)); const CircleToDisplay circle = { name, i }; circles.push_back(circle); } std::sort(circles.begin(), circles.end(), [](const CircleToDisplay& a, const CircleToDisplay& b) -> bool { QCollator collator; collator.setNumericMode(true); return collator.compare(a.name, b.name) < 0; }); return circles; } void FriendChatroom::resetEventFlags() { frnd->setEventFlag(false); } bool FriendChatroom::possibleToOpenInNewWindow() const { const auto friendPk = frnd->getPublicKey(); const auto dialogs = dialogsManager->getFriendDialogs(friendPk); return !dialogs || dialogs->chatroomCount() > 1; } bool FriendChatroom::canBeRemovedFromWindow() const { const auto friendPk = frnd->getPublicKey(); const auto dialogs = dialogsManager->getFriendDialogs(friendPk); return dialogs && dialogs->hasContact(friendPk); } bool FriendChatroom::friendCanBeRemoved() const { const auto friendPk = frnd->getPublicKey(); const auto dialogs = dialogsManager->getFriendDialogs(friendPk); return !dialogs || !dialogs->hasContact(friendPk); } void FriendChatroom::removeFriendFromDialogs() { const auto friendPk = frnd->getPublicKey(); auto dialogs = dialogsManager->getFriendDialogs(friendPk); dialogs->removeFriend(friendPk); } qTox/src/model/chatroom/friendchatroom.h000066400000000000000000000041241415623743500207040ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FRIEND_CHATROOM_H #define FRIEND_CHATROOM_H #include "chatroom.h" #include #include #include class IDialogsManager; class Friend; class Group; struct GroupToDisplay { QString name; Group* group; }; struct CircleToDisplay { QString name; int circleId; }; class FriendChatroom : public QObject, public Chatroom { Q_OBJECT public: FriendChatroom(Friend* frnd, IDialogsManager* dialogsManager); Contact* getContact() override; public slots: Friend* getFriend(); void setActive(bool active); bool canBeInvited() const; int getCircleId() const; QString getCircleName() const; void inviteToNewGroup(); void inviteFriend(const Group* group); bool autoAcceptEnabled() const; QString getAutoAcceptDir() const; void disableAutoAccept(); void setAutoAcceptDir(const QString& dir); QVector getGroups() const; QVector getOtherCircles() const; void resetEventFlags(); bool possibleToOpenInNewWindow() const; bool canBeRemovedFromWindow() const; bool friendCanBeRemoved() const; void removeFriendFromDialogs(); signals: void activeChanged(bool activated); private: bool active{false}; Friend* frnd{nullptr}; IDialogsManager* dialogsManager{nullptr}; }; #endif // FRIEND_H qTox/src/model/chatroom/groupchatroom.cpp000066400000000000000000000050561415623743500211310ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupchatroom.h" #include "src/core/core.h" #include "src/core/toxpk.h" #include "src/friendlist.h" #include "src/model/dialogs/idialogsmanager.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/model/status.h" #include "src/persistence/settings.h" GroupChatroom::GroupChatroom(Group* group, IDialogsManager* dialogsManager) : group{group} , dialogsManager{dialogsManager} { } Contact* GroupChatroom::getContact() { return group; } Group* GroupChatroom::getGroup() { return group; } bool GroupChatroom::hasNewMessage() const { return group->getEventFlag(); } void GroupChatroom::resetEventFlags() { group->setEventFlag(false); group->setMentionedFlag(false); } bool GroupChatroom::friendExists(const ToxPk& pk) { return FriendList::findFriend(pk) != nullptr; } void GroupChatroom::inviteFriend(const ToxPk& pk) { const Friend* frnd = FriendList::findFriend(pk); const auto friendId = frnd->getId(); const auto groupId = group->getId(); const auto canInvite = Status::isOnline(frnd->getStatus()); if (canInvite) { Core::getInstance()->groupInviteFriend(friendId, groupId); } } bool GroupChatroom::possibleToOpenInNewWindow() const { const auto groupId = group->getPersistentId(); const auto dialogs = dialogsManager->getGroupDialogs(groupId); return !dialogs || dialogs->chatroomCount() > 1; } bool GroupChatroom::canBeRemovedFromWindow() const { const auto groupId = group->getPersistentId(); const auto dialogs = dialogsManager->getGroupDialogs(groupId); return dialogs && dialogs->hasContact(groupId); } void GroupChatroom::removeGroupFromDialogs() { const auto groupId = group->getPersistentId(); auto dialogs = dialogsManager->getGroupDialogs(groupId); dialogs->removeGroup(groupId); } qTox/src/model/chatroom/groupchatroom.h000066400000000000000000000027311415623743500205730ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUP_CHATROOM_H #define GROUP_CHATROOM_H #include "chatroom.h" #include class IDialogsManager; class Group; class ToxPk; class GroupChatroom : public QObject, public Chatroom { Q_OBJECT public: GroupChatroom(Group* group, IDialogsManager* dialogsManager); Contact* getContact() override; Group* getGroup(); bool hasNewMessage() const; void resetEventFlags(); bool friendExists(const ToxPk& pk); void inviteFriend(const ToxPk& pk); bool possibleToOpenInNewWindow() const; bool canBeRemovedFromWindow() const; void removeGroupFromDialogs(); private: Group* group{nullptr}; IDialogsManager* dialogsManager{nullptr}; }; #endif /* GROUP_CHATROOM_H */ qTox/src/model/contact.cpp000066400000000000000000000015051415623743500160520ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "contact.h" #include Contact::~Contact() { } qTox/src/model/contact.h000066400000000000000000000024711415623743500155220ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CONTACT_H #define CONTACT_H #include "src/core/contactid.h" #include #include class Contact : public QObject { Q_OBJECT public: virtual ~Contact() = 0; virtual void setName(const QString& name) = 0; virtual QString getDisplayedName() const = 0; virtual uint32_t getId() const = 0; virtual const ContactId& getPersistentId() const = 0; virtual void setEventFlag(bool flag) = 0; virtual bool getEventFlag() const = 0; signals: void displayedNameChanged(const QString& newName); }; #endif // CONTACT_H qTox/src/model/dialogs/000077500000000000000000000000001415623743500153345ustar00rootroot00000000000000qTox/src/model/dialogs/idialogs.h000066400000000000000000000023251415623743500173020ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef I_DIALOGS_H #define I_DIALOGS_H class ContactId; class GroupId; class ToxPk; class IDialogs { public: virtual ~IDialogs() = default; virtual bool hasContact(const ContactId& contactId) const = 0; virtual bool isContactActive(const ContactId& contactId) const = 0; virtual void removeFriend(const ToxPk& friendPk) = 0; virtual void removeGroup(const GroupId& groupId) = 0; virtual int chatroomCount() const = 0; }; #endif // I_DIALOGS_H qTox/src/model/dialogs/idialogsmanager.h000066400000000000000000000021411415623743500206310ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef I_DIALOGS_MANAGER #define I_DIALOGS_MANAGER #include "idialogs.h" class GroupId; class ToxPk; class IDialogsManager { public: virtual ~IDialogsManager() = default; virtual IDialogs* getFriendDialogs(const ToxPk& friendPk) const = 0; virtual IDialogs* getGroupDialogs(const GroupId& groupId) const = 0; }; #endif // I_DIALOGS_MANAGER qTox/src/model/friend.cpp000066400000000000000000000105411415623743500156660ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "friend.h" #include "src/model/status.h" #include "src/persistence/profile.h" #include "src/widget/form/chatform.h" Friend::Friend(uint32_t friendId, const ToxPk& friendPk, const QString& userAlias, const QString& userName) : userName{userName} , userAlias{userAlias} , friendPk{friendPk} , friendId{friendId} , hasNewEvents{false} , friendStatus{Status::Status::Offline} { if (userName.isEmpty()) { this->userName = friendPk.toString(); } } /** * @brief Friend::setName sets a new username for the friend * @param _name new username, sets the public key if _name is empty */ void Friend::setName(const QString& _name) { QString name = _name; if (name.isEmpty()) { name = friendPk.toString(); } // save old displayed name to be able to compare for changes const auto oldDisplayed = getDisplayedName(); if (userName == userAlias) { userAlias.clear(); // Because userAlias was set on name change before (issue #5013) // we clear alias if equal to old name so that name change is visible. // TODO: We should not modify alias on setName. } if (userName != name) { userName = name; emit nameChanged(friendPk, name); } const auto newDisplayed = getDisplayedName(); if (oldDisplayed != newDisplayed) { emit displayedNameChanged(newDisplayed); } } /** * @brief Friend::setAlias sets the alias for the friend * @param alias new alias, removes it if set to an empty string */ void Friend::setAlias(const QString& alias) { if (userAlias == alias) { return; } emit aliasChanged(friendPk, alias); // save old displayed name to be able to compare for changes const auto oldDisplayed = getDisplayedName(); userAlias = alias; const auto newDisplayed = getDisplayedName(); if (oldDisplayed != newDisplayed) { emit displayedNameChanged(newDisplayed); } } void Friend::setStatusMessage(const QString& message) { if (statusMessage != message) { statusMessage = message; emit statusMessageChanged(friendPk, message); } } QString Friend::getStatusMessage() const { return statusMessage; } /** * @brief Friend::getDisplayedName Gets the name that should be displayed for a user * @return a QString containing either alias, username or public key * @note This function and corresponding signal should be preferred over getting * the name or alias directly. */ QString Friend::getDisplayedName() const { if (userAlias.isEmpty()) { return userName; } return userAlias; } bool Friend::hasAlias() const { return !userAlias.isEmpty(); } QString Friend::getUserName() const { return userName; } const ToxPk& Friend::getPublicKey() const { return friendPk; } uint32_t Friend::getId() const { return friendId; } const ContactId& Friend::getPersistentId() const { return friendPk; } void Friend::setEventFlag(bool flag) { hasNewEvents = flag; } bool Friend::getEventFlag() const { return hasNewEvents; } void Friend::setStatus(Status::Status s) { if (friendStatus != s) { auto oldStatus = friendStatus; friendStatus = s; emit statusChanged(friendPk, friendStatus); if (!Status::isOnline(oldStatus) && Status::isOnline(friendStatus)) { emit onlineOfflineChanged(friendPk, true); } else if (Status::isOnline(oldStatus) && !Status::isOnline(friendStatus)) { emit onlineOfflineChanged(friendPk, false); } } } Status::Status Friend::getStatus() const { return friendStatus; } qTox/src/model/friend.h000066400000000000000000000045641415623743500153430ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FRIEND_H #define FRIEND_H #include "contact.h" #include "src/core/core.h" #include "src/core/toxid.h" #include "src/core/contactid.h" #include "src/model/status.h" #include #include class Friend : public Contact { Q_OBJECT public: Friend(uint32_t friendId, const ToxPk& friendPk, const QString& userAlias = {}, const QString &userName = {}); Friend(const Friend& other) = delete; Friend& operator=(const Friend& other) = delete; void setName(const QString& name) override; void setAlias(const QString& name); QString getDisplayedName() const override; bool hasAlias() const; QString getUserName() const; void setStatusMessage(const QString& message); QString getStatusMessage() const; void setEventFlag(bool f) override; bool getEventFlag() const override; const ToxPk& getPublicKey() const; uint32_t getId() const override; const ContactId& getPersistentId() const override; void setStatus(Status::Status s); Status::Status getStatus() const; signals: void nameChanged(const ToxPk& friendId, const QString& name); void aliasChanged(const ToxPk& friendId, QString alias); void statusChanged(const ToxPk& friendId, Status::Status status); void onlineOfflineChanged(const ToxPk& friendId, bool isOnline); void statusMessageChanged(const ToxPk& friendId, const QString& message); void loadChatHistory(); public slots: private: QString userName; QString userAlias; QString statusMessage; ToxPk friendPk; uint32_t friendId; bool hasNewEvents; Status::Status friendStatus; }; #endif // FRIEND_H qTox/src/model/friendmessagedispatcher.cpp000066400000000000000000000075171415623743500213130ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "friendmessagedispatcher.h" #include "src/persistence/settings.h" #include "src/model/status.h" namespace { /** * @brief Sends message to friend using messageSender * @param[in] messageSender * @param[in] f * @param[in] message * @param[out] receipt */ bool sendMessageToCore(ICoreFriendMessageSender& messageSender, const Friend& f, const Message& message, ReceiptNum& receipt) { uint32_t friendId = f.getId(); auto sendFn = message.isAction ? std::mem_fn(&ICoreFriendMessageSender::sendAction) : std::mem_fn(&ICoreFriendMessageSender::sendMessage); return sendFn(messageSender, friendId, message.content, receipt); } } // namespace FriendMessageDispatcher::FriendMessageDispatcher(Friend& f_, MessageProcessor processor_, ICoreFriendMessageSender& messageSender_) : f(f_) , messageSender(messageSender_) , offlineMsgEngine(&f_, &messageSender_) , processor(std::move(processor_)) { connect(&f, &Friend::onlineOfflineChanged, this, &FriendMessageDispatcher::onFriendOnlineOfflineChanged); } /** * @see IMessageSender::sendMessage */ std::pair FriendMessageDispatcher::sendMessage(bool isAction, const QString& content) { const auto firstId = nextMessageId; auto lastId = nextMessageId; for (const auto& message : processor.processOutgoingMessage(isAction, content)) { auto messageId = nextMessageId++; lastId = messageId; auto onOfflineMsgComplete = [this, messageId] { emit this->messageComplete(messageId); }; ReceiptNum receipt; bool messageSent = false; if (Status::isOnline(f.getStatus())) { messageSent = sendMessageToCore(messageSender, f, message, receipt); } if (!messageSent) { offlineMsgEngine.addUnsentMessage(message, onOfflineMsgComplete); } else { offlineMsgEngine.addSentMessage(receipt, message, onOfflineMsgComplete); } emit this->messageSent(messageId, message); } return std::make_pair(firstId, lastId); } /** * @brief Handles received message from toxcore * @param[in] isAction True if action message * @param[in] content Unprocessed toxcore message */ void FriendMessageDispatcher::onMessageReceived(bool isAction, const QString& content) { emit this->messageReceived(f.getPublicKey(), processor.processIncomingMessage(isAction, content)); } /** * @brief Handles received receipt from toxcore * @param[in] receipt receipt id */ void FriendMessageDispatcher::onReceiptReceived(ReceiptNum receipt) { offlineMsgEngine.onReceiptReceived(receipt); } /** * @brief Handles status change for friend * @note Parameters just to fit slot api */ void FriendMessageDispatcher::onFriendOnlineOfflineChanged(const ToxPk&, bool isOnline) { if (isOnline) { offlineMsgEngine.deliverOfflineMsgs(); } } /** * @brief Clears all currently outgoing messages */ void FriendMessageDispatcher::clearOutgoingMessages() { offlineMsgEngine.removeAllMessages(); } qTox/src/model/friendmessagedispatcher.h000066400000000000000000000036731415623743500207570ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FRIEND_MESSAGE_DISPATCHER_H #define FRIEND_MESSAGE_DISPATCHER_H #include "src/core/icorefriendmessagesender.h" #include "src/model/friend.h" #include "src/model/imessagedispatcher.h" #include "src/model/message.h" #include "src/persistence/offlinemsgengine.h" #include #include #include class FriendMessageDispatcher : public IMessageDispatcher { Q_OBJECT public: FriendMessageDispatcher(Friend& f, MessageProcessor processor, ICoreFriendMessageSender& messageSender); std::pair sendMessage(bool isAction, const QString& content) override; void onMessageReceived(bool isAction, const QString& content); void onReceiptReceived(ReceiptNum receipt); void clearOutgoingMessages(); private slots: void onFriendOnlineOfflineChanged(const ToxPk& key, bool isOnline); private: Friend& f; DispatchedMessageId nextMessageId = DispatchedMessageId(0); ICoreFriendMessageSender& messageSender; OfflineMsgEngine offlineMsgEngine; MessageProcessor processor; }; #endif /* IMESSAGE_DISPATCHER_H */ qTox/src/model/group.cpp000066400000000000000000000142471415623743500155620ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "group.h" #include "friend.h" #include "src/core/contactid.h" #include "src/core/core.h" #include "src/core/coreav.h" #include "src/core/groupid.h" #include "src/core/toxpk.h" #include "src/friendlist.h" #include "src/persistence/settings.h" #include "src/widget/form/groupchatform.h" #include "src/widget/groupwidget.h" #include static const int MAX_GROUP_TITLE_LENGTH = 128; Group::Group(int groupId, const GroupId persistentGroupId, const QString& name, bool isAvGroupchat, const QString& selfName, ICoreGroupQuery& groupQuery, ICoreIdHandler& idHandler) : selfName{selfName} , title{name} , toxGroupNum(groupId) , groupId{persistentGroupId} , avGroupchat{isAvGroupchat} , groupQuery(groupQuery) , idHandler(idHandler) { // in groupchats, we only notify on messages containing your name <-- dumb // sound notifications should be on all messages, but system popup notification // on naming is appropriate hasNewMessages = 0; userWasMentioned = 0; regeneratePeerList(); } void Group::setName(const QString& newTitle) { const QString shortTitle = newTitle.left(MAX_GROUP_TITLE_LENGTH); if (!shortTitle.isEmpty() && title != shortTitle) { title = shortTitle; emit displayedNameChanged(title); emit titleChangedByUser(title); emit titleChanged(selfName, title); } } void Group::setTitle(const QString& author, const QString& newTitle) { const QString shortTitle = newTitle.left(MAX_GROUP_TITLE_LENGTH); if (!shortTitle.isEmpty() && title != shortTitle) { title = shortTitle; emit displayedNameChanged(title); emit titleChanged(author, title); } } QString Group::getName() const { return title; } QString Group::getDisplayedName() const { return getName(); } void Group::regeneratePeerList() { // NOTE: there's a bit of a race here. Core emits a signal for both groupPeerlistChanged and // groupPeerNameChanged back-to-back when a peer joins our group. If we get both before we // process this slot, core->getGroupPeerNames will contain the new peer name, and we'll ignore // the name changed signal, and emit a single userJoined with the correct name. But, if we // receive the name changed signal a little later, we will emit userJoined before we have their // username, using just their ToxPk, then shortly after emit another peerNameChanged signal. // This can cause double-updated to UI and chatlog, but is unavoidable given the API of toxcore. QStringList peers = groupQuery.getGroupPeerNames(toxGroupNum); const auto oldPeerNames = peerDisplayNames; peerDisplayNames.clear(); const int nPeers = peers.size(); for (int i = 0; i < nPeers; ++i) { const auto pk = groupQuery.getGroupPeerPk(toxGroupNum, i); if (pk == idHandler.getSelfPublicKey()) { peerDisplayNames[pk] = idHandler.getUsername(); } else { peerDisplayNames[pk] = FriendList::decideNickname(pk, peers[i]); } } for (const auto& pk : oldPeerNames.keys()) { if (!peerDisplayNames.contains(pk)) { emit userLeft(pk, oldPeerNames.value(pk)); stopAudioOfDepartedPeers(pk); } } for (const auto& pk : peerDisplayNames.keys()) { if (!oldPeerNames.contains(pk)) { emit userJoined(pk, peerDisplayNames.value(pk)); } } for (const auto& pk : peerDisplayNames.keys()) { if (oldPeerNames.contains(pk) && oldPeerNames.value(pk) != peerDisplayNames.value(pk)) { emit peerNameChanged(pk, oldPeerNames.value(pk), peerDisplayNames.value(pk)); } } if (oldPeerNames.size() != nPeers) { emit numPeersChanged(nPeers); } } void Group::updateUsername(ToxPk pk, const QString newName) { const QString displayName = FriendList::decideNickname(pk, newName); assert(peerDisplayNames.contains(pk)); if (peerDisplayNames[pk] != displayName) { // there could be no actual change even if their username changed due to an alias being set const auto oldName = peerDisplayNames[pk]; peerDisplayNames[pk] = displayName; emit peerNameChanged(pk, oldName, displayName); } } bool Group::isAvGroupchat() const { return avGroupchat; } uint32_t Group::getId() const { return toxGroupNum; } const GroupId& Group::getPersistentId() const { return groupId; } int Group::getPeersCount() const { return peerDisplayNames.size(); } /** * @brief Gets the PKs and names of all peers * @return PKs and names of all peers, including our own PK and name */ const QMap& Group::getPeerList() const { return peerDisplayNames; } void Group::setEventFlag(bool f) { hasNewMessages = f; } bool Group::getEventFlag() const { return hasNewMessages; } void Group::setMentionedFlag(bool f) { userWasMentioned = f; } bool Group::getMentionedFlag() const { return userWasMentioned; } QString Group::resolveToxId(const ToxPk& id) const { auto it = peerDisplayNames.find(id); if (it != peerDisplayNames.end()) { return *it; } return QString(); } void Group::setSelfName(const QString& name) { selfName = name; } QString Group::getSelfName() const { return selfName; } void Group::stopAudioOfDepartedPeers(const ToxPk& peerPk) { if (avGroupchat) { Core* core = Core::getInstance(); core->getAv()->invalidateGroupCallPeerSource(toxGroupNum, peerPk); } } qTox/src/model/group.h000066400000000000000000000054341415623743500152250ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUP_H #define GROUP_H #include "contact.h" #include "src/core/contactid.h" #include "src/core/groupid.h" #include "src/core/icoregroupquery.h" #include "src/core/icoreidhandler.h" #include "src/core/toxpk.h" #include #include #include class Group : public Contact { Q_OBJECT public: Group(int groupId, const GroupId persistentGroupId, const QString& name, bool isAvGroupchat, const QString& selfName, ICoreGroupQuery& groupQuery, ICoreIdHandler& idHandler); bool isAvGroupchat() const; uint32_t getId() const override; const GroupId& getPersistentId() const override; int getPeersCount() const; void regeneratePeerList(); const QMap& getPeerList() const; bool peerHasNickname(ToxPk pk); void setEventFlag(bool f) override; bool getEventFlag() const override; void setMentionedFlag(bool f); bool getMentionedFlag() const; void updateUsername(ToxPk pk, const QString newName); void setName(const QString& newTitle) override; void setTitle(const QString& author, const QString& newTitle); QString getName() const; QString getDisplayedName() const override; QString resolveToxId(const ToxPk& id) const; void setSelfName(const QString& name); QString getSelfName() const; signals: void titleChangedByUser(const QString& title); void titleChanged(const QString& author, const QString& title); void userJoined(const ToxPk& user, const QString& name); void userLeft(const ToxPk& user, const QString& name); void numPeersChanged(int numPeers); void peerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName); private: void stopAudioOfDepartedPeers(const ToxPk& peerPk); private: ICoreGroupQuery& groupQuery; ICoreIdHandler& idHandler; QString selfName; QString title; QMap peerDisplayNames; bool hasNewMessages; bool userWasMentioned; int toxGroupNum; const GroupId groupId; bool avGroupchat; }; #endif // GROUP_H qTox/src/model/groupinvite.cpp000066400000000000000000000030151415623743500167700ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupinvite.h" /** * @class GroupInvite * * @brief This class contains information needed to create a group invite */ GroupInvite::GroupInvite(uint32_t friendId, uint8_t inviteType, const QByteArray& data) : friendId{friendId} , type{inviteType} , invite{data} , date{QDateTime::currentDateTime()} { } bool GroupInvite::operator==(const GroupInvite& other) const { return friendId == other.friendId && type == other.type && invite == other.invite && date == other.date; } uint32_t GroupInvite::getFriendId() const { return friendId; } uint8_t GroupInvite::getType() const { return type; } QByteArray GroupInvite::getInvite() const { return invite; } QDateTime GroupInvite::getInviteDate() const { return date; } qTox/src/model/groupinvite.h000066400000000000000000000024621415623743500164420ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUPINVITE_H #define GROUPINVITE_H #include #include #include class GroupInvite { public: GroupInvite() = default; GroupInvite(uint32_t friendId, uint8_t inviteType, const QByteArray& data); bool operator==(const GroupInvite& other) const; uint32_t getFriendId() const; uint8_t getType() const; QByteArray getInvite() const; QDateTime getInviteDate() const; private: uint32_t friendId{0}; uint8_t type{0}; QByteArray invite; QDateTime date; }; #endif // GROUPINVITE_H qTox/src/model/groupmessagedispatcher.cpp000066400000000000000000000062231415623743500211710ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupmessagedispatcher.h" #include "src/persistence/igroupsettings.h" #include GroupMessageDispatcher::GroupMessageDispatcher(Group& g_, MessageProcessor processor_, ICoreIdHandler& idHandler_, ICoreGroupMessageSender& messageSender_, const IGroupSettings& groupSettings_) : group(g_) , processor(processor_) , idHandler(idHandler_) , messageSender(messageSender_) , groupSettings(groupSettings_) { processor.enableMentions(); } std::pair GroupMessageDispatcher::sendMessage(bool isAction, QString const& content) { const auto firstMessageId = nextMessageId; auto lastMessageId = firstMessageId; for (auto const& message : processor.processOutgoingMessage(isAction, content)) { auto messageId = nextMessageId++; lastMessageId = messageId; if (group.getPeersCount() != 1) { if (message.isAction) { messageSender.sendGroupAction(group.getId(), message.content); } else { messageSender.sendGroupMessage(group.getId(), message.content); } } // Emit both signals since we do not have receipts for groups // // NOTE: We could in theory keep track of our sent message and wait for // toxcore to send it back to us to indicate a completed message, but // this isn't necessarily the design of toxcore and associating the // received message back would be difficult. emit this->messageSent(messageId, message); emit this->messageComplete(messageId); } return std::make_pair(firstMessageId, lastMessageId); } /** * @brief Processes and dispatches received message from toxcore * @param[in] sender * @param[in] isAction True if is action * @param[in] content Message content */ void GroupMessageDispatcher::onMessageReceived(const ToxPk& sender, bool isAction, QString const& content) { bool isSelf = sender == idHandler.getSelfPublicKey(); if (isSelf) { return; } if (groupSettings.getBlackList().contains(sender.toString())) { qDebug() << "onGroupMessageReceived: Filtered:" << sender.toString(); return; } emit messageReceived(sender, processor.processIncomingMessage(isAction, content)); } qTox/src/model/groupmessagedispatcher.h000066400000000000000000000036271415623743500206430ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUP_MESSAGE_DISPATCHER_H #define GROUP_MESSAGE_DISPATCHER_H #include "src/core/icoregroupmessagesender.h" #include "src/core/icoreidhandler.h" #include "src/model/group.h" #include "src/model/imessagedispatcher.h" #include "src/model/message.h" #include #include #include class IGroupSettings; class GroupMessageDispatcher : public IMessageDispatcher { Q_OBJECT public: GroupMessageDispatcher(Group& group, MessageProcessor processor, ICoreIdHandler& idHandler, ICoreGroupMessageSender& messageSender, const IGroupSettings& groupSettings); std::pair sendMessage(bool isAction, QString const& content) override; void onMessageReceived(ToxPk const& sender, bool isAction, QString const& content); private: Group& group; MessageProcessor processor; ICoreIdHandler& idHandler; ICoreGroupMessageSender& messageSender; const IGroupSettings& groupSettings; DispatchedMessageId nextMessageId{0}; }; #endif /* IMESSAGE_DISPATCHER_H */ qTox/src/model/ichatlog.h000066400000000000000000000105001415623743500156510ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ICHAT_LOG_H #define ICHAT_LOG_H #include "message.h" #include "src/core/core.h" #include "src/core/toxfile.h" #include "src/core/toxpk.h" #include "src/friendlist.h" #include "src/grouplist.h" #include "src/model/chatlogitem.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/persistence/history.h" #include "src/util/strongtype.h" #include "src/widget/searchtypes.h" #include using ChatLogIdx = NamedType; Q_DECLARE_METATYPE(ChatLogIdx); struct SearchPos { // Index to the chat log item we want ChatLogIdx logIdx; // Number of matches we've had. This is always number of matches from the // start even if we're searching backwards. size_t numMatches; bool operator==(const SearchPos& other) const { return tie() == other.tie(); } bool operator!=(const SearchPos& other) const { return tie() != other.tie(); } bool operator<(const SearchPos& other) const { return tie() < other.tie(); } std::tuple tie() const { return std::tie(logIdx, numMatches); } }; struct SearchResult { bool found; SearchPos pos; size_t start; size_t len; // This is unfortunately needed to shoehorn our API into the highlighting // API of above classes. They expect to re-search the same thing we did // for some reason QRegularExpression exp; }; class IChatLog : public QObject { Q_OBJECT public: virtual ~IChatLog() = default; /** * @brief Returns reference to item at idx * @param[in] idx * @return Variant type referencing either a ToxFile or Message * @pre idx must be between currentFirstIdx() and currentLastIdx() */ virtual const ChatLogItem& at(ChatLogIdx idx) const = 0; /** * @brief searches forwards through the chat log until phrase is found according to parameter * @param[in] startIdx inclusive start idx * @param[in] phrase phrase to find (may be modified by parameter) * @param[in] parameter search parameters */ virtual SearchResult searchForward(SearchPos startIdx, const QString& phrase, const ParameterSearch& parameter) const = 0; /** * @brief searches backwards through the chat log until phrase is found according to parameter * @param[in] startIdx inclusive start idx * @param[in] phrase phrase to find (may be modified by parameter) * @param[in] parameter search parameters */ virtual SearchResult searchBackward(SearchPos startIdx, const QString& phrase, const ParameterSearch& parameter) const = 0; /** * @brief The underlying chat log instance may not want to start at 0 * @return Current first valid index to call at() with */ virtual ChatLogIdx getFirstIdx() const = 0; /** * @return current last valid index to call at() with */ virtual ChatLogIdx getNextIdx() const = 0; struct DateChatLogIdxPair { QDate date; ChatLogIdx idx; }; /** * @brief Gets indexes for each new date starting at startDate * @param[in] startDate date to start searching from * @param[in] maxDates maximum number of dates to be returned */ virtual std::vector getDateIdxs(const QDate& startDate, size_t maxDates) const = 0; signals: void itemUpdated(ChatLogIdx idx); }; #endif /*ICHAT_LOG_H*/ qTox/src/model/imessagedispatcher.h000066400000000000000000000041601415623743500177300ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IMESSAGE_DISPATCHER_H #define IMESSAGE_DISPATCHER_H #include "src/model/friend.h" #include "src/model/message.h" #include #include #include using DispatchedMessageId = NamedType; Q_DECLARE_METATYPE(DispatchedMessageId); class IMessageDispatcher : public QObject { Q_OBJECT public: virtual ~IMessageDispatcher() = default; /** * @brief Sends message to associated chat * @param[in] isAction True if is action message * @param[in] content Message content * @return Pair of first and last dispatched message IDs */ virtual std::pair sendMessage(bool isAction, const QString& content) = 0; signals: /** * @brief Emitted when a message is received and processed */ void messageReceived(const ToxPk& sender, const Message& message); /** * @brief Emitted when a message is processed and sent * @param id message id for completion * @param message sent message */ void messageSent(DispatchedMessageId id, const Message& message); /** * @brief Emitted when a receiver report is received from the associated chat * @param id Id of message that is completed */ void messageComplete(DispatchedMessageId id); }; #endif /* IMESSAGE_DISPATCHER_H */ qTox/src/model/interface.h000066400000000000000000000050461415623743500160300ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef INTERFACE_H #define INTERFACE_H #include #include /** * @file interface.h * * Qt doesn't support QObject multiple inheritance. But for declaring signals * in class, it should be inherit QObject. To avoid this issue, interface can * provide some pure virtual methods, which allow to connect to some signal. * * This file provides macros to make signals declaring easly. With this macros * signal-like method will be declared and implemented in one line each. * * @example * class IExample { * public: * // Like signal: void valueChanged(int value) const; * // Declare `connectTo_valueChanged` method. * DECLARE_SIGNAL(valueChanged, int value); * }; * * class Example : public QObject, public IExample { * public: * // Declare real signal and implement `connectTo_valueChanged` * SIGNAL_IMPL(Example, valueChanged, int value); * }; */ #define DECLARE_SIGNAL(name, ...) \ using Slot_##name = std::function; \ virtual QMetaObject::Connection connectTo_##name(QObject *receiver, Slot_##name slot) const = 0 /** * @def DECLARE_SIGNAL * @brief Decalre signal-like method. Should be used in interface */ #define DECLARE_SIGNAL(name, ...) \ using Slot_##name = std::function; \ virtual QMetaObject::Connection connectTo_##name(QObject *receiver, Slot_##name slot) const = 0 /** * @def SIGNAL_IMPL * @brief Declare signal and implement signal-like method. */ #define SIGNAL_IMPL(classname, name, ...) \ using Slot_##name = std::function; \ Q_SIGNAL void name(__VA_ARGS__); \ QMetaObject::Connection connectTo_##name(QObject *receiver, Slot_##name slot) const override { \ return connect(this, &classname::name, receiver, slot); \ } #endif // INTERFACE_H qTox/src/model/message.cpp000066400000000000000000000075111415623743500160460ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "message.h" #include "friend.h" #include "src/core/core.h" void MessageProcessor::SharedParams::onUserNameSet(const QString& username) { QString sanename = username; sanename.remove(QRegularExpression("[\\t\\n\\v\\f\\r\\x0000]")); nameMention = QRegularExpression("\\b" + QRegularExpression::escape(username) + "\\b", QRegularExpression::CaseInsensitiveOption); sanitizedNameMention = QRegularExpression("\\b" + QRegularExpression::escape(sanename) + "\\b", QRegularExpression::CaseInsensitiveOption); } /** * @brief Set the public key on which a message should be highlighted * @param pk ToxPk in its hex string form */ void MessageProcessor::SharedParams::setPublicKey(const QString& pk) { // no sanitization needed, we expect a ToxPk in its string form pubKeyMention = QRegularExpression("\\b" + pk + "\\b", QRegularExpression::CaseInsensitiveOption); } MessageProcessor::MessageProcessor(const MessageProcessor::SharedParams& sharedParams) : sharedParams(sharedParams) {} /** * @brief Converts an outgoing message into one (or many) sanitized Message(s) */ std::vector MessageProcessor::processOutgoingMessage(bool isAction, QString const& content) { std::vector ret; QStringList splitMsgs = Core::splitMessage(content); ret.reserve(splitMsgs.size()); QDateTime timestamp = QDateTime::currentDateTime(); std::transform(splitMsgs.begin(), splitMsgs.end(), std::back_inserter(ret), [&](const QString& part) { Message message; message.isAction = isAction; message.content = part; message.timestamp = timestamp; return message; }); return ret; } /** * @brief Converts an incoming message into a sanitized Message */ Message MessageProcessor::processIncomingMessage(bool isAction, QString const& message) { QDateTime timestamp = QDateTime::currentDateTime(); auto ret = Message{}; ret.isAction = isAction; ret.content = message; ret.timestamp = timestamp; if (detectingMentions) { auto nameMention = sharedParams.GetNameMention(); auto sanitizedNameMention = sharedParams.GetSanitizedNameMention(); auto pubKeyMention = sharedParams.GetPublicKeyMention(); for (auto const& mention : {nameMention, sanitizedNameMention, pubKeyMention}) { auto matchIt = mention.globalMatch(ret.content); if (!matchIt.hasNext()) { continue; } auto match = matchIt.next(); auto pos = static_cast(match.capturedStart()); auto length = static_cast(match.capturedLength()); // skip matches on empty usernames if (length == 0) { continue; } ret.metadata.push_back({MessageMetadataType::selfMention, pos, pos + length}); break; } } return ret; } qTox/src/model/message.h000066400000000000000000000057111415623743500155130ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef MESSAGE_H #define MESSAGE_H #include #include #include #include class Friend; // NOTE: This could be extended in the future to handle all text processing (see // ChatMessage::createChatMessage) enum class MessageMetadataType { selfMention, }; // May need to be extended in the future to have a more varianty type (imagine // if we wanted to add message replies and shoved a reply id in here) struct MessageMetadata { MessageMetadataType type; // Indicates start position within a Message::content size_t start; // Indicates end position within a Message::content size_t end; }; struct Message { bool isAction; QString content; QDateTime timestamp; std::vector metadata; }; class MessageProcessor { public: /** * Parameters needed by all message processors. Used to reduce duplication * of expensive data looked at by all message processors */ class SharedParams { public: QRegularExpression GetNameMention() const { return nameMention; } QRegularExpression GetSanitizedNameMention() const { return sanitizedNameMention; } QRegularExpression GetPublicKeyMention() const { return pubKeyMention; } void onUserNameSet(const QString& username); void setPublicKey(const QString& pk); private: QRegularExpression nameMention; QRegularExpression sanitizedNameMention; QRegularExpression pubKeyMention; }; MessageProcessor(const SharedParams& sharedParams); std::vector processOutgoingMessage(bool isAction, QString const& content); Message processIncomingMessage(bool isAction, QString const& message); /** * @brief Enables mention detection in the processor */ inline void enableMentions() { detectingMentions = true; } /** * @brief Disables mention detection in the processor */ inline void disableMentions() { detectingMentions = false; }; private: bool detectingMentions = false; const SharedParams& sharedParams; }; #endif /*MESSAGE_H*/ qTox/src/model/profile/000077500000000000000000000000001415623743500153525ustar00rootroot00000000000000qTox/src/model/profile/iprofileinfo.h000066400000000000000000000041241415623743500202110ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/model/interface.h" #include class ToxId; class IProfileInfo { public: enum class RenameResult { OK, EmptyName, ProfileAlreadyExists, Error }; enum class SaveResult { OK, EmptyPath, NoWritePermission, Error }; enum class SetAvatarResult { OK, EmptyPath, CanNotOpen, CanNotRead, TooLarge }; virtual ~IProfileInfo() = default; virtual bool setPassword(const QString& password) = 0; virtual bool deletePassword() = 0; virtual bool isEncrypted() const = 0; virtual void copyId() const = 0; virtual void setUsername(const QString& name) = 0; virtual void setStatusMessage(const QString& status) = 0; virtual QString getProfileName() const = 0; virtual RenameResult renameProfile(const QString& name) = 0; virtual SaveResult exportProfile(const QString& path) const = 0; virtual QStringList removeProfile() = 0; virtual void logout() = 0; virtual void copyQr(const QImage& image) const = 0; virtual SaveResult saveQr(const QImage& image, const QString& path) const = 0; virtual SetAvatarResult setAvatar(const QString& path) = 0; virtual void removeAvatar() = 0; DECLARE_SIGNAL(idChanged, const ToxId&); DECLARE_SIGNAL(usernameChanged, const QString&); DECLARE_SIGNAL(statusMessageChanged, const QString&); }; qTox/src/model/profile/profileinfo.cpp000066400000000000000000000240451415623743500203770ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "profileinfo.h" #include "src/core/core.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/model/toxclientstandards.h" #include #include #include #include #include /** * @class ProfileInfo * @brief Implement interface, that provides invormation about self profile. * Also, provide methods to work with profile file. * @note Should be used only when QAppliaction constructed. */ /** * @brief ProfileInfo constructor. * @param core Pointer to Tox Core. * @param profile Pointer to Profile. * @note All pointers parameters shouldn't be null. */ ProfileInfo::ProfileInfo(Core* core, Profile* profile) : profile{profile} , core{core} { connect(core, &Core::idSet, this, &ProfileInfo::idChanged); connect(core, &Core::usernameSet, this, &ProfileInfo::usernameChanged); connect(core, &Core::statusMessageSet, this, &ProfileInfo::statusMessageChanged); } /** * @brief Set a user password for profile. * @param password New password. * @return True on success, false otherwise. */ bool ProfileInfo::setPassword(const QString& password) { QString errorMsg = profile->setPassword(password); return errorMsg.isEmpty(); } /** * @brief Delete a user password for profile. * @return True on success, false otherwise. */ bool ProfileInfo::deletePassword() { QString errorMsg = profile->setPassword(""); return errorMsg.isEmpty(); } /** * @brief Check if current profile is encrypted. * @return True if encrypted, false otherwise. */ bool ProfileInfo::isEncrypted() const { return profile->isEncrypted(); } /** * @brief Copy self ToxId to clipboard. */ void ProfileInfo::copyId() const { ToxId selfId = core->getSelfId(); QString txt = selfId.toString(); QClipboard* clip = QApplication::clipboard(); clip->setText(txt, QClipboard::Clipboard); if (clip->supportsSelection()) { clip->setText(txt, QClipboard::Selection); } } /** * @brief Set self user name. * @param name New name. */ void ProfileInfo::setUsername(const QString& name) { core->setUsername(name); } /** * @brief Set self status message. * @param status New status message. */ void ProfileInfo::setStatusMessage(const QString& status) { core->setStatusMessage(status); } /** * @brief Get name of tox profile file. * @return Profile name. */ QString ProfileInfo::getProfileName() const { return profile->getName(); } /** * @brief Remove characters not supported for profile name from string. * @param src Source string. * @return Sanitized string. */ static QString sanitize(const QString& src) { QString name = src; // these are pretty much Windows banned filename characters QList banned{'/', '\\', ':', '<', '>', '"', '|', '?', '*'}; for (QChar c : banned) { name.replace(c, '_'); } // also remove leading and trailing periods if (name[0] == '.') { name[0] = '_'; } if (name.endsWith('.')) { name[name.length() - 1] = '_'; } return name; } /** * @brief Rename profile file. * @param name New profile name. * @return Result code of rename operation. */ IProfileInfo::RenameResult ProfileInfo::renameProfile(const QString& name) { QString cur = profile->getName(); if (name.isEmpty()) { return RenameResult::EmptyName; } QString newName = sanitize(name); if (Profile::exists(newName)) { return RenameResult::ProfileAlreadyExists; } if (!profile->rename(name)) { return RenameResult::Error; } return RenameResult::OK; } // TODO: Find out what is dangerous? /** * @brief Dangerous way to find out if a path is writable. * @param filepath Path to file which should be deleted. * @return True, if file writeable, false otherwise. */ static bool tryRemoveFile(const QString& filepath) { QFile tmp(filepath); bool writable = tmp.open(QIODevice::WriteOnly); tmp.remove(); return writable; } /** * @brief Save profile in custom place. * @param path Path to save profile. * @return Result code of save operation. */ IProfileInfo::SaveResult ProfileInfo::exportProfile(const QString& path) const { QString current = profile->getName() + Core::TOX_EXT; if (path.isEmpty()) { return SaveResult::EmptyPath; } if (!tryRemoveFile(path)) { return SaveResult::NoWritePermission; } if (!QFile::copy(Settings::getInstance().getSettingsDirPath() + current, path)) { return SaveResult::Error; } return SaveResult::OK; } /** * @brief Remove profile. * @return List of files, which couldn't be removed automaticaly. */ QStringList ProfileInfo::removeProfile() { QStringList manualDeleteFiles = profile->remove(); QMetaObject::invokeMethod(&Nexus::getInstance(), "showLogin"); return manualDeleteFiles; } /** * @brief Log out from current profile. */ void ProfileInfo::logout() { // TODO(kriby): Refactor all of these invokeMethod calls with connect() properly when possible Settings::getInstance().saveGlobal(); QMetaObject::invokeMethod(&Nexus::getInstance(), "showLogin", Q_ARG(QString, Settings::getInstance().getCurrentProfile())); } /** * @brief Copy image to clipboard. * @param image Image to copy. */ void ProfileInfo::copyQr(const QImage& image) const { QApplication::clipboard()->setImage(image); } /** * @brief Save image to file. * @param image Image to save. * @param path Path to save. * @return Result code of save operation. */ IProfileInfo::SaveResult ProfileInfo::saveQr(const QImage& image, const QString& path) const { QString current = profile->getName() + ".png"; if (path.isEmpty()) { return SaveResult::EmptyPath; } if (!tryRemoveFile(path)) { return SaveResult::NoWritePermission; } // nullptr - image format same as file extension, // 75-quality, png file is ~6.3kb if (!image.save(path, nullptr, 75)) { return SaveResult::Error; } return SaveResult::OK; } /** * @brief Convert QImage to png image. * @param pic Picture to convert. * @return Byte array with png image. */ QByteArray picToPng(const QImage& pic) { QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); pic.save(&buffer, "PNG"); buffer.close(); return bytes; } /** * @brief Set self avatar. * @param path Path to image, which should be the new avatar. * @return Code of set avatar operation. */ IProfileInfo::SetAvatarResult ProfileInfo::setAvatar(const QString& path) { if (path.isEmpty()) { return SetAvatarResult::EmptyPath; } QFile file(path); file.open(QIODevice::ReadOnly); if (!file.isOpen()) { return SetAvatarResult::CanNotOpen; } QByteArray avatar; const auto err = createAvatarFromFile(file, avatar); if (err == SetAvatarResult::OK) { profile->setAvatar(avatar); } return err; } /** * @brief Create an avatar from an image file * @param file Image file, which should be the new avatar. * @param avatar Output avatar of correct file type and size. * @return SetAvatarResult */ IProfileInfo::SetAvatarResult ProfileInfo::createAvatarFromFile(QFile& file, QByteArray& avatar) { QByteArray fileContents{file.readAll()}; auto err = byteArrayToPng(fileContents, avatar); if (err != SetAvatarResult::OK) { return err; } err = scalePngToAvatar(avatar); return err; } /** * @brief Create a png from image data * @param inData byte array from an image file. * @param outPng byte array which the png will be written to. * @return SetAvatarResult */ IProfileInfo::SetAvatarResult ProfileInfo::byteArrayToPng(QByteArray inData, QByteArray& outPng) { QBuffer inBuffer{&inData}; QImageReader reader{&inBuffer}; QImage image; const auto format = reader.format(); // read whole image even if we're not going to use the QImage, to make sure the image is valid if (!reader.read(&image)) { return SetAvatarResult::CanNotRead; } if (format == "png") { // FIXME: re-encode the png even though inData is already valid. This strips the metadata // since we don't have a good png metadata stripping method currently. outPng = picToPng(image); } else { outPng = picToPng(image); } return SetAvatarResult::OK; } /* * @brief Scale a png to an acceptable file size. * @param avatar byte array containing the avatar. * @return SetAvatarResult */ IProfileInfo::SetAvatarResult ProfileInfo::scalePngToAvatar(QByteArray& avatar) { // We do a first rescale to 256x256 in case the image was huge, then keep tryng from here constexpr int scaleSizes[] = {256, 128, 64, 32}; for (auto scaleSize : scaleSizes) { if (ToxClientStandards::IsValidAvatarSize(avatar.size())) break; QImage image; image.loadFromData(avatar); image = image.scaled(scaleSize, scaleSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); avatar = picToPng(image); } // If this happens, you're really doing it on purpose. if (!ToxClientStandards::IsValidAvatarSize(avatar.size())) { return SetAvatarResult::TooLarge; } return SetAvatarResult::OK; } /** * @brief Remove self avatar. */ void ProfileInfo::removeAvatar() { profile->removeSelfAvatar(); } qTox/src/model/profile/profileinfo.h000066400000000000000000000044231415623743500200420ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/model/interface.h" #include "src/core/toxpk.h" #include "iprofileinfo.h" class Core; class QFile; class QPoint; class Profile; class ProfileInfo : public QObject, public IProfileInfo { Q_OBJECT public: ProfileInfo(Core* core, Profile* profile); bool setPassword(const QString& password) override; bool deletePassword() override; bool isEncrypted() const override; void copyId() const override; void setUsername(const QString& name) override; void setStatusMessage(const QString& status) override; QString getProfileName() const override; RenameResult renameProfile(const QString& name) override; SaveResult exportProfile(const QString& path) const override; QStringList removeProfile() override; void logout() override; void copyQr(const QImage& image) const override; SaveResult saveQr(const QImage& image, const QString& path) const override; SetAvatarResult setAvatar(const QString& path) override; void removeAvatar() override; SIGNAL_IMPL(ProfileInfo, idChanged, const ToxId& id) SIGNAL_IMPL(ProfileInfo, usernameChanged, const QString& name) SIGNAL_IMPL(ProfileInfo, statusMessageChanged, const QString& message) private: IProfileInfo::SetAvatarResult createAvatarFromFile(QFile& file, QByteArray& avatar); IProfileInfo::SetAvatarResult byteArrayToPng(QByteArray inData, QByteArray& outPng); IProfileInfo::SetAvatarResult scalePngToAvatar(QByteArray& avatar); Profile* const profile; Core* const core; }; qTox/src/model/sessionchatlog.cpp000066400000000000000000000344711415623743500174540ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "sessionchatlog.h" #include "src/friendlist.h" #include #include #include namespace { /** * lower_bound needs two way comparisons. This adaptor allows us to compare * between a Message and QDateTime in both directions */ struct MessageDateAdaptor { static const QDateTime invalidDateTime; MessageDateAdaptor(const std::pair& item) : timestamp(item.second.getContentType() == ChatLogItem::ContentType::message ? item.second.getContentAsMessage().message.timestamp : invalidDateTime) {} MessageDateAdaptor(const QDateTime& timestamp) : timestamp(timestamp) {} const QDateTime& timestamp; }; const QDateTime MessageDateAdaptor::invalidDateTime; /** * @brief The search types all can be represented as some regular expression. This function * takes the input phrase and filter and generates the appropriate regular expression * @return Regular expression which finds the input */ QRegularExpression getRegexpForPhrase(const QString& phrase, FilterSearch filter) { constexpr auto regexFlags = QRegularExpression::UseUnicodePropertiesOption; constexpr auto caseInsensitiveFlags = QRegularExpression::CaseInsensitiveOption; switch (filter) { case FilterSearch::Register: return QRegularExpression(QRegularExpression::escape(phrase), regexFlags); case FilterSearch::WordsOnly: return QRegularExpression(SearchExtraFunctions::generateFilterWordsOnly(phrase), caseInsensitiveFlags); case FilterSearch::RegisterAndWordsOnly: return QRegularExpression(SearchExtraFunctions::generateFilterWordsOnly(phrase), regexFlags); case FilterSearch::RegisterAndRegular: return QRegularExpression(phrase, regexFlags); case FilterSearch::Regular: return QRegularExpression(phrase, caseInsensitiveFlags); default: return QRegularExpression(QRegularExpression::escape(phrase), caseInsensitiveFlags); } } /** * @return True if the given status indicates no future updates will come in */ bool toxFileIsComplete(ToxFile::FileStatus status) { switch (status) { case ToxFile::INITIALIZING: case ToxFile::PAUSED: case ToxFile::TRANSMITTING: return false; case ToxFile::BROKEN: case ToxFile::CANCELED: case ToxFile::FINISHED: default: return true; } } std::map::const_iterator firstItemAfterDate(QDate date, const std::map& items) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) return std::lower_bound(items.begin(), items.end(), QDateTime(date.startOfDay()), #else return std::lower_bound(items.begin(), items.end(), QDateTime(date), #endif [](const MessageDateAdaptor& a, MessageDateAdaptor const& b) { return a.timestamp.date() < b.timestamp.date(); }); } } // namespace SessionChatLog::SessionChatLog(const ICoreIdHandler& coreIdHandler) : coreIdHandler(coreIdHandler) {} /** * @brief Alternate constructor that allows for an initial index to be set */ SessionChatLog::SessionChatLog(ChatLogIdx initialIdx, const ICoreIdHandler& coreIdHandler) : coreIdHandler(coreIdHandler) , nextIdx(initialIdx) {} SessionChatLog::~SessionChatLog() = default; const ChatLogItem& SessionChatLog::at(ChatLogIdx idx) const { auto item = items.find(idx); if (item == items.end()) { std::terminate(); } return item->second; } SearchResult SessionChatLog::searchForward(SearchPos startPos, const QString& phrase, const ParameterSearch& parameter) const { if (startPos.logIdx >= getNextIdx()) { SearchResult res; res.found = false; return res; } auto currentPos = startPos; auto regexp = getRegexpForPhrase(phrase, parameter.filter); for (auto it = items.find(currentPos.logIdx); it != items.end(); ++it) { const auto& key = it->first; const auto& item = it->second; if (item.getContentType() != ChatLogItem::ContentType::message) { continue; } const auto& content = item.getContentAsMessage(); auto match = regexp.globalMatch(content.message.content, 0); auto numMatches = 0; QRegularExpressionMatch lastMatch; while (match.isValid() && numMatches <= currentPos.numMatches && match.hasNext()) { lastMatch = match.next(); numMatches++; } if (numMatches > currentPos.numMatches) { SearchResult res; res.found = true; res.pos.logIdx = key; res.pos.numMatches = numMatches; res.start = lastMatch.capturedStart(); res.len = lastMatch.capturedLength(); return res; } // After the first iteration we force this to 0 to search the whole // message currentPos.numMatches = 0; } // We should have returned from the above loop if we had found anything SearchResult ret; ret.found = false; return ret; } SearchResult SessionChatLog::searchBackward(SearchPos startPos, const QString& phrase, const ParameterSearch& parameter) const { auto currentPos = startPos; auto regexp = getRegexpForPhrase(phrase, parameter.filter); auto startIt = items.find(currentPos.logIdx); // If we don't have it we'll start at the end if (startIt == items.end()) { if (items.empty()) { SearchResult ret; ret.found = false; return ret; } startIt = std::prev(items.end()); startPos.numMatches = 0; } // Off by 1 due to reverse_iterator api auto rStartIt = std::reverse_iterator(std::next(startIt)); auto rEnd = std::reverse_iterator(items.begin()); for (auto it = rStartIt; it != rEnd; ++it) { const auto& key = it->first; const auto& item = it->second; if (item.getContentType() != ChatLogItem::ContentType::message) { continue; } const auto& content = item.getContentAsMessage(); auto match = regexp.globalMatch(content.message.content, 0); auto totalMatches = 0; auto numMatchesBeforePos = 0; QRegularExpressionMatch lastMatch; while (match.isValid() && match.hasNext()) { auto currentMatch = match.next(); totalMatches++; if (currentPos.numMatches == 0 || currentPos.numMatches > numMatchesBeforePos) { lastMatch = currentMatch; numMatchesBeforePos++; } } if ((numMatchesBeforePos < currentPos.numMatches || currentPos.numMatches == 0) && numMatchesBeforePos > 0) { SearchResult res; res.found = true; res.pos.logIdx = key; res.pos.numMatches = numMatchesBeforePos; res.start = lastMatch.capturedStart(); res.len = lastMatch.capturedLength(); return res; } // After the first iteration we force this to 0 to search the whole // message currentPos.numMatches = 0; } // We should have returned from the above loop if we had found anything SearchResult ret; ret.found = false; return ret; } ChatLogIdx SessionChatLog::getFirstIdx() const { if (items.empty()) { return nextIdx; } return items.begin()->first; } ChatLogIdx SessionChatLog::getNextIdx() const { return nextIdx; } std::vector SessionChatLog::getDateIdxs(const QDate& startDate, size_t maxDates) const { std::vector ret; auto dateIt = startDate; while (true) { auto it = firstItemAfterDate(dateIt, items); if (it == items.end()) { break; } DateChatLogIdxPair pair; pair.date = dateIt; pair.idx = it->first; ret.push_back(std::move(pair)); dateIt = dateIt.addDays(1); if (startDate.daysTo(dateIt) > maxDates && maxDates != 0) { break; } } return ret; } void SessionChatLog::insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, const QString& senderName, const ChatLogMessage& message) { auto item = ChatLogItem(sender, message); if (!senderName.isEmpty()) { item.setDisplayName(senderName); } assert(message.state == MessageState::complete); items.emplace(idx, std::move(item)); } void SessionChatLog::insertIncompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, const QString& senderName, const ChatLogMessage& message, DispatchedMessageId dispatchId) { auto item = ChatLogItem(sender, message); if (!senderName.isEmpty()) { item.setDisplayName(senderName); } assert(message.state == MessageState::pending); items.emplace(idx, std::move(item)); outgoingMessages.insert(dispatchId, idx); } void SessionChatLog::insertBrokenMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, const QString& senderName, const ChatLogMessage& message) { auto item = ChatLogItem(sender, message); if (!senderName.isEmpty()) { item.setDisplayName(senderName); } assert(message.state == MessageState::broken); items.emplace(idx, std::move(item)); } void SessionChatLog::insertFileAtIdx(ChatLogIdx idx, const ToxPk& sender, const QString& senderName, const ChatLogFile& file) { auto item = ChatLogItem(sender, file); if (!senderName.isEmpty()) { item.setDisplayName(senderName); } items.emplace(idx, std::move(item)); } /** * @brief Inserts message data into the chatlog buffer * @note Owner of SessionChatLog is in charge of attaching this to the appropriate IMessageDispatcher */ void SessionChatLog::onMessageReceived(const ToxPk& sender, const Message& message) { auto messageIdx = nextIdx++; ChatLogMessage chatLogMessage; chatLogMessage.state = MessageState::complete; chatLogMessage.message = message; items.emplace(messageIdx, ChatLogItem(sender, chatLogMessage)); emit this->itemUpdated(messageIdx); } /** * @brief Inserts message data into the chatlog buffer * @note Owner of SessionChatLog is in charge of attaching this to the appropriate IMessageDispatcher */ void SessionChatLog::onMessageSent(DispatchedMessageId id, const Message& message) { auto messageIdx = nextIdx++; ChatLogMessage chatLogMessage; chatLogMessage.state = MessageState::pending; chatLogMessage.message = message; items.emplace(messageIdx, ChatLogItem(coreIdHandler.getSelfPublicKey(), chatLogMessage)); outgoingMessages.insert(id, messageIdx); emit this->itemUpdated(messageIdx); } /** * @brief Marks the associated message as complete and notifies any listeners * @note Owner of SessionChatLog is in charge of attaching this to the appropriate IMessageDispatcher */ void SessionChatLog::onMessageComplete(DispatchedMessageId id) { auto chatLogIdxIt = outgoingMessages.find(id); if (chatLogIdxIt == outgoingMessages.end()) { qWarning() << "Failed to find outgoing message"; return; } const auto& chatLogIdx = *chatLogIdxIt; auto messageIt = items.find(chatLogIdx); if (messageIt == items.end()) { qWarning() << "Failed to look up message in chat log"; return; } messageIt->second.getContentAsMessage().state = MessageState::complete; emit this->itemUpdated(messageIt->first); } /** * @brief Updates file state in the chatlog * @note The files need to be pre-filtered for the current chat since we do no validation * @note This should be attached to any CoreFile signal that fits the signature */ void SessionChatLog::onFileUpdated(const ToxPk& sender, const ToxFile& file) { auto fileIt = std::find_if(currentFileTransfers.begin(), currentFileTransfers.end(), [&](const CurrentFileTransfer& transfer) { return transfer.file == file; }); ChatLogIdx messageIdx; if (fileIt == currentFileTransfers.end() && file.status == ToxFile::INITIALIZING) { assert(file.status == ToxFile::INITIALIZING); CurrentFileTransfer currentTransfer; currentTransfer.file = file; currentTransfer.idx = nextIdx++; currentFileTransfers.push_back(currentTransfer); const auto chatLogFile = ChatLogFile{QDateTime::currentDateTime(), file}; items.emplace(currentTransfer.idx, ChatLogItem(sender, chatLogFile)); messageIdx = currentTransfer.idx; } else if (fileIt != currentFileTransfers.end()) { messageIdx = fileIt->idx; fileIt->file = file; items.at(messageIdx).getContentAsFile().file = file; } else { // This may be a file unbroken message that we don't handle ATM return; } if (toxFileIsComplete(file.status)) { currentFileTransfers.erase(fileIt); } emit this->itemUpdated(messageIdx); } void SessionChatLog::onFileTransferRemotePausedUnpaused(const ToxPk& sender, const ToxFile& file, bool /*paused*/) { onFileUpdated(sender, file); } void SessionChatLog::onFileTransferBrokenUnbroken(const ToxPk& sender, const ToxFile& file, bool /*broken*/) { onFileUpdated(sender, file); } qTox/src/model/sessionchatlog.h000066400000000000000000000070211415623743500171100ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SESSION_CHAT_LOG_H #define SESSION_CHAT_LOG_H #include "ichatlog.h" #include "imessagedispatcher.h" #include #include struct SessionChatLogMetadata; class SessionChatLog : public IChatLog { Q_OBJECT public: SessionChatLog(const ICoreIdHandler& coreIdHandler); SessionChatLog(ChatLogIdx initialIdx, const ICoreIdHandler& coreIdHandler); ~SessionChatLog(); const ChatLogItem& at(ChatLogIdx idx) const override; SearchResult searchForward(SearchPos startIdx, const QString& phrase, const ParameterSearch& parameter) const override; SearchResult searchBackward(SearchPos startIdx, const QString& phrase, const ParameterSearch& parameter) const override; ChatLogIdx getFirstIdx() const override; ChatLogIdx getNextIdx() const override; std::vector getDateIdxs(const QDate& startDate, size_t maxDates) const override; void insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, const QString& senderName, const ChatLogMessage& message); void insertIncompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, const QString& senderName, const ChatLogMessage& message, DispatchedMessageId dispatchId); void insertBrokenMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, const QString& senderName, const ChatLogMessage& message); void insertFileAtIdx(ChatLogIdx idx, const ToxPk& sender, const QString& senderName, const ChatLogFile& file); public slots: void onMessageReceived(const ToxPk& sender, const Message& message); void onMessageSent(DispatchedMessageId id, const Message& message); void onMessageComplete(DispatchedMessageId id); void onFileUpdated(const ToxPk& sender, const ToxFile& file); void onFileTransferRemotePausedUnpaused(const ToxPk& sender, const ToxFile& file, bool paused); void onFileTransferBrokenUnbroken(const ToxPk& sender, const ToxFile& file, bool broken); private: const ICoreIdHandler& coreIdHandler; ChatLogIdx nextIdx = ChatLogIdx(0); std::map items; struct CurrentFileTransfer { ChatLogIdx idx; ToxFile file; }; /** * Short list of active file transfers in given log. This is to make it * so we don't have to search through all files that have ever been transferred * in order to find our existing transfers */ std::vector currentFileTransfers; /** * Maps DispatchedMessageIds back to ChatLogIdxs. Messages are removed when the message * is marked as completed */ QMap outgoingMessages; }; #endif /*SESSION_CHAT_LOG_H*/ qTox/src/model/status.cpp000066400000000000000000000046641415623743500157530ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #include #include #include #include namespace Status { QString getTitle(Status status) { switch (status) { case Status::Online: return QObject::tr("online", "contact status"); case Status::Away: return QObject::tr("away", "contact status"); case Status::Busy: return QObject::tr("busy", "contact status"); case Status::Offline: return QObject::tr("offline", "contact status"); case Status::Blocked: return QObject::tr("blocked", "contact status"); } assert(false); return QStringLiteral(""); } QString getAssetSuffix(Status status) { switch (status) { case Status::Online: return "online"; case Status::Away: return "away"; case Status::Busy: return "busy"; case Status::Offline: return "offline"; case Status::Blocked: return "blocked"; } assert(false); return QStringLiteral(""); } QString getIconPath(Status status, bool event) { const QString eventSuffix = event ? QStringLiteral("_notification") : QString(); const QString statusSuffix = getAssetSuffix(status); if (status == Status::Blocked) { return ":/img/status/" + statusSuffix + ".svg"; } else { return ":/img/status/" + statusSuffix + eventSuffix + ".svg"; } } bool isOnline(Status status) { return status != Status::Offline && status != Status::Blocked; } } // namespace Status qTox/src/model/status.h000066400000000000000000000023671415623743500154160ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #ifndef STATUS_H #define STATUS_H namespace Status { // Status::Status is weird, but Status is a fitting name for both the namespace and enum class.. enum class Status { Online = 0, Away, Busy, Offline, Blocked }; QString getIconPath(Status status, bool event = false); QString getTitle(Status status); QString getAssetSuffix(Status status); bool isOnline(Status status); } #endif // STATUS_H qTox/src/model/toxclientstandards.h000066400000000000000000000021771415623743500200070ustar00rootroot00000000000000/* Copyright © 2020 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXCLIENTSTANDARDS_H #define TOXCLIENTSTANDARDS_H #include namespace ToxClientStandards { // From TCS 2.2.4, max valid avatar size is 64KiB constexpr static uint64_t MaxAvatarSize = 64 * 1024; constexpr bool IsValidAvatarSize(uint64_t fileSize) { return fileSize <= MaxAvatarSize; } } // ToxClientStandards #endif // TOXCLIENTSTANDARDS_H qTox/src/net/000077500000000000000000000000001415623743500134005ustar00rootroot00000000000000qTox/src/net/avatarbroadcaster.cpp000066400000000000000000000052221415623743500175750ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "avatarbroadcaster.h" #include "src/core/core.h" #include "src/core/corefile.h" #include "src/model/status.h" #include #include /** * @class AvatarBroadcaster * * Takes care of broadcasting avatar changes to our friends in a smart way * Cache a copy of our current avatar and friends who have received it * so we don't spam avatar transfers to a friend who already has it. */ QByteArray AvatarBroadcaster::avatarData; QMap AvatarBroadcaster::friendsSentTo; static QMetaObject::Connection autoBroadcastConn; static auto autoBroadcast = [](uint32_t friendId, Status::Status) { AvatarBroadcaster::sendAvatarTo(friendId); }; /** * @brief Set our current avatar. * @param data Byte array on avater. */ void AvatarBroadcaster::setAvatar(QByteArray data) { if (avatarData == data) return; avatarData = data; friendsSentTo.clear(); QVector friends = Core::getInstance()->getFriendList(); for (uint32_t friendId : friends) sendAvatarTo(friendId); } /** * @brief Send our current avatar to this friend, if not already sent * @param friendId Id of friend to send avatar. */ void AvatarBroadcaster::sendAvatarTo(uint32_t friendId) { if (friendsSentTo.contains(friendId) && friendsSentTo[friendId]) return; if (!Core::getInstance()->isFriendOnline(friendId)) return; CoreFile* coreFile = Core::getInstance()->getCoreFile(); coreFile->sendAvatarFile(friendId, avatarData); friendsSentTo[friendId] = true; } /** * @brief Setup auto broadcast sending avatar. * @param state If true, we automatically broadcast our avatar to friends when they come online. */ void AvatarBroadcaster::enableAutoBroadcast(bool state) { QObject::disconnect(autoBroadcastConn); if (state) autoBroadcastConn = QObject::connect(Core::getInstance(), &Core::friendStatusChanged, autoBroadcast); } qTox/src/net/avatarbroadcaster.h000066400000000000000000000023021415623743500172360ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef AVATARBROADCASTER_H #define AVATARBROADCASTER_H #include #include class AvatarBroadcaster { private: AvatarBroadcaster() = delete; public: static void setAvatar(QByteArray data); static void sendAvatarTo(uint32_t friendId); static void enableAutoBroadcast(bool state = true); private: static QByteArray avatarData; static QMap friendsSentTo; }; #endif // AVATARBROADCASTER_H qTox/src/net/bootstrapnodeupdater.cpp000066400000000000000000000135421415623743500203610ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "bootstrapnodeupdater.h" #include #include #include #include #include #include #include #include namespace { const QUrl NodeListAddress{"https://nodes.tox.chat/json"}; const QLatin1String jsonNodeArrayName{"nodes"}; const QLatin1String emptyAddress{"-"}; const QRegularExpression ToxPkRegEx(QString("(^|\\s)[A-Fa-f0-9]{%1}($|\\s)").arg(64)); const QLatin1String builtinNodesFile{":/conf/nodes.json"}; } // namespace namespace NodeFields { const QLatin1String status_udp{"status_udp"}; const QLatin1String status_tcp{"status_tcp"}; const QLatin1String ipv4{"ipv4"}; const QLatin1String ipv6{"ipv6"}; const QLatin1String public_key{"public_key"}; const QLatin1String port{"port"}; const QLatin1String maintainer{"maintainer"}; // TODO(sudden6): make use of this field once we differentiate between TCP nodes, and bootstrap nodes const QLatin1String tcp_ports{"tcp_ports"}; const QStringList neededFields{status_udp, status_tcp, ipv4, ipv6, public_key, port, maintainer}; } // namespace NodeFields /** * @brief Fetches a list of currently online bootstrap nodes from node.tox.chat * @param proxy Proxy to use for the lookup, must outlive this object */ BootstrapNodeUpdater::BootstrapNodeUpdater(const QNetworkProxy& proxy, QObject* parent) : QObject{parent} , proxy{proxy} {} void BootstrapNodeUpdater::requestBootstrapNodes() { nam.setProxy(proxy); connect(&nam, &QNetworkAccessManager::finished, this, &BootstrapNodeUpdater::onRequestComplete); QNetworkRequest request{NodeListAddress}; request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); nam.get(request); } /** * @brief Loads the list of built in boostrap nodes * @return List of bootstrapnodes on success, empty list on error */ QList BootstrapNodeUpdater::loadDefaultBootstrapNodes() { QFile nodesFile{builtinNodesFile}; if (!nodesFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "Couldn't read bootstrap nodes"; return {}; } QString nodesJson = nodesFile.readAll(); nodesFile.close(); QJsonDocument d = QJsonDocument::fromJson(nodesJson.toUtf8()); if (d.isNull()) { qWarning() << "Failed to parse JSON document"; return {}; } return jsonToNodeList(d); } void BootstrapNodeUpdater::onRequestComplete(QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { nam.clearAccessCache(); emit availableBootstrapNodes({}); return; } // parse the reply JSON QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll()); if (jsonDocument.isNull()) { emit availableBootstrapNodes({}); return; } QList result = jsonToNodeList(jsonDocument); emit availableBootstrapNodes(result); } void BootstrapNodeUpdater::jsonNodeToDhtServer(const QJsonObject& node, QList& outList) { // first check if the node in question has all needed fields bool found = true; for (const auto& key : NodeFields::neededFields) { found &= node.contains(key); } if (!found) { return; } // only use nodes that provide at least UDP connection if (!node[NodeFields::status_udp].toBool(false)) { return; } const QString public_key = node[NodeFields::public_key].toString({}); const int port = node[NodeFields::port].toInt(-1); // nodes.tox.chat doesn't use empty strings for empty addresses QString ipv6_address = node[NodeFields::ipv6].toString({}); if (ipv6_address == emptyAddress) { ipv6_address = QString{}; } QString ipv4_address = node[NodeFields::ipv4].toString({}); if (ipv4_address == emptyAddress) { ipv4_address = QString{}; } const QString maintainer = node[NodeFields::maintainer].toString({}); if (port < 1 || port > std::numeric_limits::max()) { return; } const quint16 port_u16 = static_cast(port); if (!public_key.contains(ToxPkRegEx)) { return; } DhtServer server; server.userId = public_key; server.port = port_u16; server.name = maintainer; if (!ipv4_address.isEmpty()) { server.address = ipv4_address; outList.append(server); } // avoid adding the same server twice in case they use the same dns name for v6 and v4 if (!ipv6_address.isEmpty() && ipv4_address != ipv6_address) { server.address = ipv6_address; outList.append(server); } return; } QList BootstrapNodeUpdater::jsonToNodeList(const QJsonDocument& nodeList) { QList result; if (!nodeList.isObject()) { return result; } QJsonObject rootObj = nodeList.object(); if (!(rootObj.contains(jsonNodeArrayName) && rootObj[jsonNodeArrayName].isArray())) { return result; } QJsonArray nodes = rootObj[jsonNodeArrayName].toArray(); for (const auto& node : nodes) { if (node.isObject()) { jsonNodeToDhtServer(node.toObject(), result); } } return result; } qTox/src/net/bootstrapnodeupdater.h000066400000000000000000000015501415623743500200220ustar00rootroot00000000000000#ifndef BOOTSTRAPNODEUPDATER_H #define BOOTSTRAPNODEUPDATER_H #include #include #include #include #include "src/core/dhtserver.h" class QNetworkReply; class BootstrapNodeUpdater : public QObject { Q_OBJECT public: explicit BootstrapNodeUpdater(const QNetworkProxy& proxy, QObject* parent = nullptr); void requestBootstrapNodes(); static QList loadDefaultBootstrapNodes(); signals: void availableBootstrapNodes(QList nodes); private slots: void onRequestComplete(QNetworkReply* reply); private: static QList jsonToNodeList(const QJsonDocument& nodeList); static void jsonNodeToDhtServer(const QJsonObject& node, QList& outList); private: QNetworkProxy proxy; QNetworkAccessManager nam; }; #endif // BOOTSTRAPNODEUPDATER_H qTox/src/net/toxuri.cpp000066400000000000000000000106741415623743500154460ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/net/toxuri.h" #include "src/core/core.h" #include "src/nexus.h" #include "src/widget/gui.h" #include "src/widget/tool/friendrequestdialog.h" #include #include #include #include #include #include #include #include #include #include #include bool toxURIEventHandler(const QByteArray& eventData) { if (!eventData.startsWith("tox:")) return false; handleToxURI(eventData); return true; } /** * @brief Shows a dialog asking whether or not to add this tox address as a friend. * @note Will wait until the core is ready first. * @param toxURI Tox URI to try to add. * @return True, if tox URI is correct, false otherwise. */ bool handleToxURI(const QString& toxURI) { Nexus& nexus = Nexus::getInstance(); Core* core = nexus.getCore(); while (!core) { core = nexus.getCore(); qApp->processEvents(); QThread::msleep(10); } QString toxaddr = toxURI.mid(4); ToxId toxId(toxaddr); QString error = QString(); if (!toxId.isValid()) { error = QMessageBox::tr("%1 is not a valid Tox address.").arg(toxaddr); } else if (toxId == core->getSelfId()) { error = QMessageBox::tr("You can't add yourself as a friend!", "When trying to add your own Tox ID as friend"); } if (!error.isEmpty()) { GUI::showWarning(QMessageBox::tr("Couldn't add friend"), error); return false; } const QString defaultMessage = QObject::tr("%1 here! Tox me maybe?", "Default message in Tox URI friend requests. Write something appropriate!"); const QString username = Nexus::getCore()->getUsername(); ToxURIDialog* dialog = new ToxURIDialog(nullptr, toxaddr, defaultMessage.arg(username)); QObject::connect(dialog, &ToxURIDialog::finished, [=](int result) { if (result == QDialog::Accepted) { Core::getInstance()->requestFriendship(toxId, dialog->getRequestMessage()); } dialog->deleteLater(); }); dialog->open(); return true; } ToxURIDialog::ToxURIDialog(QWidget* parent, const QString& userId, const QString& message) : QDialog(parent) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(tr("Add a friend", "Title of the window to add a friend through Tox URI")); QLabel* friendsLabel = new QLabel(tr("Do you want to add %1 as a friend?").arg(userId), this); QLabel* userIdLabel = new QLabel(tr("User ID:"), this); QLineEdit* userIdEdit = new QLineEdit(userId, this); userIdEdit->setCursorPosition(0); userIdEdit->setReadOnly(true); QLabel* messageLabel = new QLabel(tr("Friend request message:"), this); messageEdit = new QPlainTextEdit(message, this); QDialogButtonBox* buttonBox = new QDialogButtonBox(Qt::Horizontal, this); buttonBox->addButton(tr("Send", "Send a friend request"), QDialogButtonBox::AcceptRole); buttonBox->addButton(tr("Cancel", "Don't send a friend request"), QDialogButtonBox::RejectRole); connect(buttonBox, &QDialogButtonBox::accepted, this, &FriendRequestDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &FriendRequestDialog::reject); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(friendsLabel); layout->addSpacing(12); layout->addWidget(userIdLabel); layout->addWidget(userIdEdit); layout->addWidget(messageLabel); layout->addWidget(messageEdit); layout->addWidget(buttonBox); resize(300, 200); } QString ToxURIDialog::getRequestMessage() { return messageEdit->toPlainText(); } qTox/src/net/toxuri.h000066400000000000000000000023101415623743500150770ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXURI_H #define TOXURI_H #include bool handleToxURI(const QString& toxURI); // Internals class QByteArray; class QPlainTextEdit; bool toxURIEventHandler(const QByteArray& eventData); class ToxURIDialog : public QDialog { Q_OBJECT public: explicit ToxURIDialog(QWidget* parent, const QString& userId, const QString& message); QString getRequestMessage(); private: QPlainTextEdit* messageEdit; }; #endif // TOXURI_H qTox/src/net/updatecheck.cpp000066400000000000000000000104651415623743500163720ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/net/updatecheck.h" #include "src/persistence/settings.h" #include #include #include #include #include #include #include #include #include namespace { const QString versionUrl{QStringLiteral("https://api.github.com/repos/qTox/qTox/releases/latest")}; struct Version { int major; int minor; int patch; }; Version tagToVersion(QString tagName) { // capture tag name to avoid showing update available on dev builds which include hash as part of describe QRegularExpression versionFormat{QStringLiteral("v([0-9]+)\\.([0-9]+)\\.([0-9]+)")}; auto matches = versionFormat.match(tagName); assert(matches.lastCapturedIndex() == 3); bool ok; auto major = matches.captured(1).toInt(&ok); assert(ok); auto minor = matches.captured(2).toInt(&ok); assert(ok); auto patch = matches.captured(3).toInt(&ok); assert(ok); return {major, minor, patch}; } bool isUpdateAvailable(Version current, Version available) { // A user may have a version greater than our latest release in the time between a tag being pushed and the release // being published. Don't notify about an update in that case. if (current.major < available.major) { return true; } if (current.major > available.major) { return false; } if (current.minor < available.minor) { return true; } if (current.minor > available.minor) { return false; } if (current.patch < available.patch) { return true; } if (current.patch > available.patch) { return false; } return false; } } // namespace UpdateCheck::UpdateCheck(const Settings& settings) : settings(settings) { updateTimer.start(1000 * 60 * 60 * 24 /* 1 day */); connect(&updateTimer, &QTimer::timeout, this, &UpdateCheck::checkForUpdate); connect(&manager, &QNetworkAccessManager::finished, this, &UpdateCheck::handleResponse); } void UpdateCheck::checkForUpdate() { if (!settings.getCheckUpdates()) { // still run the timer to check periodically incase setting changes return; } manager.setProxy(settings.getProxy()); QNetworkRequest request{versionUrl}; manager.get(request); } void UpdateCheck::handleResponse(QNetworkReply* reply) { assert(reply != nullptr); if (reply == nullptr) { qWarning() << "Update check returned null reply, ignoring"; return; } if (reply->error() != QNetworkReply::NoError) { qWarning() << "Failed to check for update:" << reply->error(); emit updateCheckFailed(); reply->deleteLater(); return; } QByteArray result = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(result); QJsonObject jObject = doc.object(); QVariantMap mainMap = jObject.toVariantMap(); QString latestVersion = mainMap["tag_name"].toString(); if (latestVersion.isEmpty()) { qWarning() << "No tag name found in response:"; emit updateCheckFailed(); reply->deleteLater(); return; } auto currentVer = tagToVersion(GIT_DESCRIBE); auto availableVer = tagToVersion(latestVersion); if (isUpdateAvailable(currentVer, availableVer)) { qInfo() << "Update available to version" << latestVersion; QUrl link{mainMap["html_url"].toString()}; emit updateAvailable(latestVersion, link); } else { qInfo() << "qTox is up to date"; emit upToDate(); } reply->deleteLater(); } qTox/src/net/updatecheck.h000066400000000000000000000024711415623743500160350ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #include #include class Settings; class QString; class QUrl; class QNetworkReply; class UpdateCheck : public QObject { Q_OBJECT public: UpdateCheck(const Settings& settings); void checkForUpdate(); signals: void updateAvailable(QString latestVersion, QUrl link); void upToDate(); void updateCheckFailed(); private slots: void handleResponse(QNetworkReply* reply); private: QNetworkAccessManager manager; QTimer updateTimer; const Settings& settings; }; qTox/src/nexus.cpp000066400000000000000000000314021415623743500144600ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "nexus.h" #include "persistence/settings.h" #include "src/core/core.h" #include "src/core/coreav.h" #include "src/model/groupinvite.h" #include "src/model/status.h" #include "src/persistence/profile.h" #include "src/widget/widget.h" #include "video/camerasource.h" #include "widget/gui.h" #include "widget/loginscreen.h" #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #include #include #include #endif /** * @class Nexus * * This class is in charge of connecting various systems together * and forwarding signals appropriately to the right objects, * it is in charge of starting the GUI and the Core. */ Q_DECLARE_OPAQUE_POINTER(ToxAV*) static Nexus* nexus{nullptr}; Nexus::Nexus(QObject* parent) : QObject(parent) , profile{nullptr} , widget{nullptr} {} Nexus::~Nexus() { delete widget; widget = nullptr; delete profile; profile = nullptr; emit saveGlobal(); #ifdef Q_OS_MAC delete globalMenuBar; #endif } /** * @brief Sets up invariants and calls showLogin * * Hides the login screen and shows the GUI for the given profile. * Will delete the current GUI, if it exists. */ void Nexus::start() { qDebug() << "Starting up"; // Setup the environment qRegisterMetaType("Status::Status"); qRegisterMetaType("vpx_image"); qRegisterMetaType("uint8_t"); qRegisterMetaType("uint16_t"); qRegisterMetaType("uint32_t"); qRegisterMetaType("const int16_t*"); qRegisterMetaType("int32_t"); qRegisterMetaType("int64_t"); qRegisterMetaType("size_t"); qRegisterMetaType("QPixmap"); qRegisterMetaType("Profile*"); qRegisterMetaType("ToxAV*"); qRegisterMetaType("ToxFile"); qRegisterMetaType("ToxFile::FileDirection"); qRegisterMetaType>("std::shared_ptr"); qRegisterMetaType("ToxPk"); qRegisterMetaType("ToxId"); qRegisterMetaType("GroupId"); qRegisterMetaType("ContactId"); qRegisterMetaType("GroupInvite"); qRegisterMetaType("ReceiptNum"); qRegisterMetaType("RowId"); qApp->setQuitOnLastWindowClosed(false); #ifdef Q_OS_MAC // TODO: still needed? globalMenuBar = new QMenuBar(0); dockMenu = new QMenu(globalMenuBar); viewMenu = globalMenuBar->addMenu(QString()); windowMenu = globalMenuBar->addMenu(QString()); globalMenuBar->addAction(windowMenu->menuAction()); fullscreenAction = viewMenu->addAction(QString()); fullscreenAction->setShortcut(QKeySequence::FullScreen); connect(fullscreenAction, &QAction::triggered, this, &Nexus::toggleFullscreen); minimizeAction = windowMenu->addAction(QString()); minimizeAction->setShortcut(Qt::CTRL + Qt::Key_M); connect(minimizeAction, &QAction::triggered, [this]() { minimizeAction->setEnabled(false); QApplication::focusWindow()->showMinimized(); }); windowMenu->addSeparator(); frontAction = windowMenu->addAction(QString()); connect(frontAction, &QAction::triggered, this, &Nexus::bringAllToFront); QAction* quitAction = new QAction(globalMenuBar); quitAction->setMenuRole(QAction::QuitRole); connect(quitAction, &QAction::triggered, qApp, &QApplication::quit); retranslateUi(); #endif showMainGUI(); } /** * @brief Hides the main GUI, delete the profile, and shows the login screen */ int Nexus::showLogin(const QString& profileName) { delete widget; widget = nullptr; delete profile; profile = nullptr; LoginScreen loginScreen{profileName}; connectLoginScreen(loginScreen); // TODO(kriby): Move core out of profile // This is awkward because the core is in the profile // The connection order ensures profile will be ready for bootstrap for now connect(this, &Nexus::currentProfileChanged, this, &Nexus::bootstrapWithProfile); int returnval = loginScreen.exec(); if (returnval == QDialog::Rejected) { // Kriby: This will terminate the main application loop, necessary until we refactor // away the split startup/return to login behavior. qApp->quit(); } disconnect(this, &Nexus::currentProfileChanged, this, &Nexus::bootstrapWithProfile); return returnval; } void Nexus::bootstrapWithProfile(Profile* p) { // kriby: This is a hack until a proper controller is written profile = p; if (profile) { audioControl = std::unique_ptr(Audio::makeAudio(*settings)); assert(audioControl != nullptr); profile->getCore()->getAv()->setAudio(*audioControl); start(); } } void Nexus::setSettings(Settings* settings) { if (this->settings) { QObject::disconnect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal); } this->settings = settings; if (this->settings) { QObject::connect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal); } } void Nexus::connectLoginScreen(const LoginScreen& loginScreen) { // TODO(kriby): Move connect sequences to a controller class object instead // Nexus -> LoginScreen QObject::connect(this, &Nexus::profileLoaded, &loginScreen, &LoginScreen::onProfileLoaded); QObject::connect(this, &Nexus::profileLoadFailed, &loginScreen, &LoginScreen::onProfileLoadFailed); // LoginScreen -> Nexus QObject::connect(&loginScreen, &LoginScreen::createNewProfile, this, &Nexus::onCreateNewProfile); QObject::connect(&loginScreen, &LoginScreen::loadProfile, this, &Nexus::onLoadProfile); // LoginScreen -> Settings QObject::connect(&loginScreen, &LoginScreen::autoLoginChanged, settings, &Settings::setAutoLogin); QObject::connect(&loginScreen, &LoginScreen::autoLoginChanged, settings, &Settings::saveGlobal); // Settings -> LoginScreen QObject::connect(settings, &Settings::autoLoginChanged, &loginScreen, &LoginScreen::onAutoLoginChanged); } void Nexus::showMainGUI() { // TODO(kriby): Rewrite as view-model connect sequence only, add to a controller class object assert(profile); // Create GUI widget = new Widget(*audioControl); // Start GUI widget->init(); GUI::getInstance(); // Zetok protection // There are small instants on startup during which no // profile is loaded but the GUI could still receive events, // e.g. between two modal windows. Disable the GUI to prevent that. GUI::setEnabled(false); // Connections connect(profile, &Profile::selfAvatarChanged, widget, &Widget::onSelfAvatarLoaded); connect(profile, &Profile::coreChanged, widget, &Widget::onCoreChanged); connect(profile, &Profile::failedToStart, widget, &Widget::onFailedToStartCore, Qt::BlockingQueuedConnection); connect(profile, &Profile::badProxy, widget, &Widget::onBadProxyCore, Qt::BlockingQueuedConnection); profile->startCore(); GUI::setEnabled(true); } /** * @brief Returns the singleton instance. */ Nexus& Nexus::getInstance() { if (!nexus) nexus = new Nexus; return *nexus; } void Nexus::destroyInstance() { delete nexus; nexus = nullptr; } /** * @brief Get core instance. * @return nullptr if not started, core instance otherwise. */ Core* Nexus::getCore() { Nexus& nexus = getInstance(); if (!nexus.profile) return nullptr; return nexus.profile->getCore(); } /** * @brief Get current user profile. * @return nullptr if not started, profile otherwise. */ Profile* Nexus::getProfile() { return getInstance().profile; } /** * @brief Creates a new profile and replaces the current one. * @param name New username * @param pass New password */ void Nexus::onCreateNewProfile(const QString& name, const QString& pass) { setProfile(Profile::createProfile(name, parser, pass)); parser = nullptr; // only apply cmdline proxy settings once } /** * Loads an existing profile and replaces the current one. */ void Nexus::onLoadProfile(const QString& name, const QString& pass) { setProfile(Profile::loadProfile(name, parser, pass)); parser = nullptr; // only apply cmdline proxy settings once } /** * Changes the loaded profile and notifies listeners. * @param p */ void Nexus::setProfile(Profile* p) { if (!p) { emit profileLoadFailed(); // Warnings are issued during respective createNew/load calls return; } else { emit profileLoaded(); } emit currentProfileChanged(p); } void Nexus::setParser(QCommandLineParser* parser) { this->parser = parser; } /** * @brief Get desktop GUI widget. * @return nullptr if not started, desktop widget otherwise. */ Widget* Nexus::getDesktopGUI() { return getInstance().widget; } #ifdef Q_OS_MAC void Nexus::retranslateUi() { viewMenu->menuAction()->setText(tr("View", "OS X Menu bar")); windowMenu->menuAction()->setText(tr("Window", "OS X Menu bar")); minimizeAction->setText(tr("Minimize", "OS X Menu bar")); frontAction->setText((tr("Bring All to Front", "OS X Menu bar"))); } void Nexus::onWindowStateChanged(Qt::WindowStates state) { minimizeAction->setEnabled(QApplication::activeWindow() != nullptr); if (QApplication::activeWindow() != nullptr && sender() == QApplication::activeWindow()) { if (state & Qt::WindowFullScreen) minimizeAction->setEnabled(false); if (state & Qt::WindowFullScreen) fullscreenAction->setText(tr("Exit Fullscreen")); else fullscreenAction->setText(tr("Enter Fullscreen")); updateWindows(); } updateWindowsStates(); } void Nexus::updateWindows() { updateWindowsArg(nullptr); } void Nexus::updateWindowsArg(QWindow* closedWindow) { QWindowList windowList = QApplication::topLevelWindows(); delete windowActions; windowActions = new QActionGroup(this); windowMenu->addSeparator(); QAction* dockLast; if (!dockMenu->actions().isEmpty()) dockLast = dockMenu->actions().first(); else dockLast = nullptr; QWindow* activeWindow; if (QApplication::activeWindow()) activeWindow = QApplication::activeWindow()->windowHandle(); else activeWindow = nullptr; for (int i = 0; i < windowList.size(); ++i) { if (closedWindow == windowList[i]) continue; QAction* action = windowActions->addAction(windowList[i]->title()); action->setCheckable(true); action->setChecked(windowList[i] == activeWindow); connect(action, &QAction::triggered, [=] { onOpenWindow(windowList[i]); }); windowMenu->addAction(action); dockMenu->insertAction(dockLast, action); } if (dockLast && !dockLast->isSeparator()) dockMenu->insertSeparator(dockLast); } void Nexus::updateWindowsClosed() { updateWindowsArg(static_cast(sender())->windowHandle()); } void Nexus::updateWindowsStates() { bool exists = false; QWindowList windowList = QApplication::topLevelWindows(); for (QWindow* window : windowList) { if (!(window->windowState() & Qt::WindowMinimized)) { exists = true; break; } } frontAction->setEnabled(exists); } void Nexus::onOpenWindow(QObject* object) { QWindow* window = static_cast(object); if (window->windowState() & QWindow::Minimized) window->showNormal(); window->raise(); window->requestActivate(); } void Nexus::toggleFullscreen() { QWidget* window = QApplication::activeWindow(); if (window->isFullScreen()) window->showNormal(); else window->showFullScreen(); } void Nexus::bringAllToFront() { QWindowList windowList = QApplication::topLevelWindows(); QWindow* focused = QApplication::focusWindow(); for (QWindow* window : windowList) window->raise(); focused->raise(); } #endif qTox/src/nexus.h000066400000000000000000000053401415623743500141270ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef NEXUS_H #define NEXUS_H #include #include "src/audio/iaudiocontrol.h" class Widget; class Profile; class Settings; class LoginScreen; class Core; class QCommandLineParser; #ifdef Q_OS_MAC class QMenuBar; class QMenu; class QAction; class QWindow; class QActionGroup; class QSignalMapper; #endif class Nexus : public QObject { Q_OBJECT public: void start(); void showMainGUI(); void setSettings(Settings* settings); void setParser(QCommandLineParser* parser); static Nexus& getInstance(); static void destroyInstance(); static Core* getCore(); static Profile* getProfile(); static Widget* getDesktopGUI(); #ifdef Q_OS_MAC public: QMenuBar* globalMenuBar; QMenu* viewMenu; QMenu* windowMenu; QAction* minimizeAction; QAction* fullscreenAction; QAction* frontAction; QMenu* dockMenu; public slots: void retranslateUi(); void onWindowStateChanged(Qt::WindowStates state); void updateWindows(); void updateWindowsClosed(); void updateWindowsStates(); void onOpenWindow(QObject* object); void toggleFullscreen(); void bringAllToFront(); private: void updateWindowsArg(QWindow* closedWindow); QActionGroup* windowActions = nullptr; #endif signals: void currentProfileChanged(Profile* Profile); void profileLoaded(); void profileLoadFailed(); void saveGlobal(); public slots: void onCreateNewProfile(const QString& name, const QString& pass); void onLoadProfile(const QString& name, const QString& pass); int showLogin(const QString& profileName = QString()); void bootstrapWithProfile(Profile* p); private: explicit Nexus(QObject* parent = nullptr); void connectLoginScreen(const LoginScreen& loginScreen); void setProfile(Profile* p); ~Nexus(); private: Profile* profile; Settings* settings; Widget* widget; std::unique_ptr audioControl; QCommandLineParser* parser = nullptr; }; #endif // NEXUS_H qTox/src/persistence/000077500000000000000000000000001415623743500151365ustar00rootroot00000000000000qTox/src/persistence/db/000077500000000000000000000000001415623743500155235ustar00rootroot00000000000000qTox/src/persistence/db/rawdatabase.cpp000066400000000000000000000762361415623743500205230ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "rawdatabase.h" #include #include #include #include #include #include #include /** * @class RawDatabase * @brief Implements a low level RAII interface to a SQLCipher (SQlite3) database. * * Thread-safe, does all database operations on a worker thread. * The queries must not contain transaction commands (BEGIN/COMMIT/...) or the behavior is * undefined. * * @var QMutex RawDatabase::transactionsMutex; * @brief Protects pendingTransactions */ /** * @class Query * @brief A query to be executed by the database. * * Can be composed of one or more SQL statements in the query, * optional BLOB parameters to be bound, and callbacks fired when the query is executed * Calling any database method from a query callback is undefined behavior. * * @var QByteArray RawDatabase::Query::query * @brief UTF-8 query string * * @var QVector RawDatabase::Query::blobs * @brief Bound data blobs * * @var std::function RawDatabase::Query::insertCallback * @brief Called after execution with the last insert rowid * * @var std::function&)> RawDatabase::Query::rowCallback * @brief Called during execution for each row * * @var QVector RawDatabase::Query::statements * @brief Statements to be compiled from the query */ /** * @struct Transaction * @brief SQL transactions to be processed. * * A transaction is made of queries, which can have bound BLOBs. * * @var std::atomic_bool* RawDatabase::Transaction::success = nullptr; * @brief If not a nullptr, the result of the transaction will be set * * @var std::atomic_bool* RawDatabase::Transaction::done = nullptr; * @brief If not a nullptr, will be set to true when the transaction has been executed */ /** * @brief Tries to open a database. * @param path Path to database. * @param password If empty, the database will be opened unencrypted. * Otherwise we will use toxencryptsave to derive a key and encrypt the database. */ RawDatabase::RawDatabase(const QString& path, const QString& password, const QByteArray& salt) : workerThread{new QThread} , path{path} , currentSalt{salt} // we need the salt later if a new password should be set , currentHexKey{deriveKey(password, salt)} { workerThread->setObjectName("qTox Database"); moveToThread(workerThread.get()); workerThread->start(); // first try with the new salt if (open(path, currentHexKey)) { return; } // avoid opening the same db twice close(); // create a backup before trying to upgrade to new salt bool upgrade = true; if (!QFile::copy(path, path + ".bak")) { qDebug() << "Couldn't create the backup of the database, won't upgrade"; upgrade = false; } // fall back to the old salt currentHexKey = deriveKey(password); if (open(path, currentHexKey)) { // upgrade only if backup successful if (upgrade) { // still using old salt, upgrade if (setPassword(password)) { qDebug() << "Successfully upgraded to dynamic salt"; } else { qWarning() << "Failed to set password with new salt"; } } } else { qDebug() << "Failed to open database with old salt"; } } RawDatabase::~RawDatabase() { close(); workerThread->exit(0); while (workerThread->isRunning()) workerThread->wait(50); } /** * @brief Tries to open the database with the given (possibly empty) key. * @param path Path to database. * @param hexKey Hex representation of the key in string. * @return True if success, false otherwise. */ bool RawDatabase::open(const QString& path, const QString& hexKey) { if (QThread::currentThread() != workerThread.get()) { bool ret; QMetaObject::invokeMethod(this, "open", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, path), Q_ARG(const QString&, hexKey)); return ret; } if (!QFile::exists(path) && QFile::exists(path + ".tmp")) { qWarning() << "Restoring database from temporary export file! Did we crash while changing " "the password or upgrading?"; QFile::rename(path + ".tmp", path); } if (sqlite3_open_v2(path.toUtf8().data(), &sqlite, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr) != SQLITE_OK) { qWarning() << "Failed to open database" << path << "with error:" << sqlite3_errmsg(sqlite); return false; } if (sqlite3_create_function(sqlite, "regexp", 2, SQLITE_UTF8, nullptr, &RawDatabase::regexpInsensitive, nullptr, nullptr)) { qWarning() << "Failed to create function regexp"; close(); return false; } if (sqlite3_create_function(sqlite, "regexpsensitive", 2, SQLITE_UTF8, nullptr, &RawDatabase::regexpSensitive, nullptr, nullptr)) { qWarning() << "Failed to create function regexpsensitive"; close(); return false; } if (!hexKey.isEmpty()) { if (!openEncryptedDatabaseAtLatestSupportedVersion(hexKey)) { close(); return false; } } return true; } bool RawDatabase::openEncryptedDatabaseAtLatestSupportedVersion(const QString& hexKey) { // old qTox database are saved with SQLCipher 3.x defaults. For a period after 1.16.3 but before 1.17.0, databases // could be partially upgraded to SQLCipher 4.0 defaults, since SQLCipher 3.x isn't capable of setitng all the same // params. If SQLCipher 4.x happened to be used, they would have been fully upgraded to 4.0 default params. // We need to support all three of these cases, so also upgrade to the latest possible params while we're here if (!setKey(hexKey)) { return false; } auto highestSupportedVersion = highestSupportedParams(); if (setCipherParameters(highestSupportedVersion)) { if (testUsable()) { qInfo() << "Opened database with SQLCipher" << toString(highestSupportedVersion) << "parameters"; return true; } else { return updateSavedCipherParameters(hexKey, highestSupportedVersion); } } else { qCritical() << "Failed to set latest supported SQLCipher params!"; return false; } } bool RawDatabase::testUsable() { // this will unfortunately log a warning if it fails, even though we may expect failure return execNow("SELECT count(*) FROM sqlite_master;"); } /** * @brief Changes stored db encryption from SQLCipher 3.x defaults to 4.x defaults */ bool RawDatabase::updateSavedCipherParameters(const QString& hexKey, SqlCipherParams newParams) { auto currentParams = readSavedCipherParams(hexKey, newParams); setKey(hexKey); // setKey again because a SELECT has already been run, causing crypto settings to take effect if (!setCipherParameters(currentParams)) { return false; } const auto user_version = getUserVersion(); if (user_version < 0) { return false; } if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS newParams KEY \"x'" + hexKey + "'\";")) { return false; } if (!setCipherParameters(newParams, "newParams")) { return false; } if (!execNow("SELECT sqlcipher_export('newParams');")) { return false; } if (!execNow(QString("PRAGMA newParams.user_version = %1;").arg(user_version))) { return false; } if (!execNow("DETACH DATABASE newParams;")) { return false; } if (!commitDbSwap(hexKey)) { return false; } qInfo() << "Upgraded database from SQLCipher" << toString(currentParams) << "params to" << toString(newParams) << "params complete"; return true; } bool RawDatabase::setCipherParameters(SqlCipherParams params, const QString& database) { QString prefix; if (!database.isNull()) { prefix = database + "."; } // from https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/ const QString default3_xParams{"PRAGMA database.cipher_page_size = 1024;" "PRAGMA database.kdf_iter = 64000;" "PRAGMA database.cipher_hmac_algorithm = HMAC_SHA1;" "PRAGMA database.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"}; // cipher_hmac_algorithm and cipher_kdf_algorithm weren't supported in sqlcipher 3.x, so our upgrade to 4 only // applied some of the new params if sqlcipher 3.x was used at the time const QString halfUpgradedTo4Params{"PRAGMA database.cipher_page_size = 4096;" "PRAGMA database.kdf_iter = 256000;" "PRAGMA database.cipher_hmac_algorithm = HMAC_SHA1;" "PRAGMA database.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"}; const QString default4_xParams{"PRAGMA database.cipher_page_size = 4096;" "PRAGMA database.kdf_iter = 256000;" "PRAGMA database.cipher_hmac_algorithm = HMAC_SHA512;" "PRAGMA database.cipher_kdf_algorithm = PBKDF2_HMAC_SHA512;" "PRAGMA database.cipher_memory_security = ON;"}; // got disabled by default in 4.5.0, so manually enable it QString defaultParams; switch(params) { case SqlCipherParams::p3_0: { defaultParams = default3_xParams; break; } case SqlCipherParams::halfUpgradedTo4: { defaultParams = halfUpgradedTo4Params; break; } case SqlCipherParams::p4_0: { defaultParams = default4_xParams; break; } } qDebug() << "Setting SQLCipher" << toString(params) << "parameters"; return execNow(defaultParams.replace("database.", prefix)); } RawDatabase::SqlCipherParams RawDatabase::highestSupportedParams() { // Note: This is just calling into the sqlcipher library, not touching the database. QString cipherVersion; if (!execNow(RawDatabase::Query("PRAGMA cipher_version", [&](const QVector& row) { cipherVersion = row[0].toString(); }))) { qCritical() << "Failed to read cipher_version"; return SqlCipherParams::p3_0; } auto majorVersion = cipherVersion.split('.')[0].toInt(); SqlCipherParams highestSupportedParams; switch (majorVersion) { case 3: highestSupportedParams = SqlCipherParams::halfUpgradedTo4; break; case 4: highestSupportedParams = SqlCipherParams::p4_0; break; default: qCritical() << "Unsupported SQLCipher version detected!"; return SqlCipherParams::p3_0; } qDebug() << "Highest supported SQLCipher params on this system are" << toString(highestSupportedParams); return highestSupportedParams; } RawDatabase::SqlCipherParams RawDatabase::readSavedCipherParams(const QString& hexKey, SqlCipherParams newParams) { for (int i = static_cast(SqlCipherParams::p3_0); i < static_cast(newParams); ++i) { if (!setKey(hexKey)) { break; } if (!setCipherParameters(static_cast(i))) { break; } if (testUsable()) { return static_cast(i); } } qCritical() << "Failed to check saved SQLCipher params"; return SqlCipherParams::p3_0; } bool RawDatabase::setKey(const QString& hexKey) { // setKey again to clear old bad cipher settings if (!execNow("PRAGMA key = \"x'" + hexKey + "'\"")) { qWarning() << "Failed to set encryption key"; return false; } return true; } int RawDatabase::getUserVersion() { int64_t user_version; if (!execNow(RawDatabase::Query("PRAGMA user_version", [&](const QVector& row) { user_version = row[0].toLongLong(); }))) { qCritical() << "Failed to read user_version during cipher upgrade"; return -1; } return user_version; } /** * @brief Close the database and free its associated resources. */ void RawDatabase::close() { if (QThread::currentThread() != workerThread.get()) return (void)QMetaObject::invokeMethod(this, "close", Qt::BlockingQueuedConnection); // We assume we're in the ctor or dtor, so we just need to finish processing our transactions process(); if (sqlite3_close(sqlite) == SQLITE_OK) sqlite = nullptr; else qWarning() << "Error closing database:" << sqlite3_errmsg(sqlite); } /** * @brief Checks, that the database is open. * @return True if the database was opened successfully. */ bool RawDatabase::isOpen() { // We don't need thread safety since only the ctor/dtor can write this pointer return sqlite != nullptr; } /** * @brief Executes a SQL transaction synchronously. * @param statement Statement to execute. * @return Whether the transaction was successful. */ bool RawDatabase::execNow(const QString& statement) { return execNow(Query{statement}); } /** * @brief Executes a SQL transaction synchronously. * @param statement Statement to execute. * @return Whether the transaction was successful. */ bool RawDatabase::execNow(const RawDatabase::Query& statement) { return execNow(QVector{statement}); } /** * @brief Executes a SQL transaction synchronously. * @param statements List of statements to execute. * @return Whether the transaction was successful. */ bool RawDatabase::execNow(const QVector& statements) { if (!sqlite) { qWarning() << "Trying to exec, but the database is not open"; return false; } std::atomic_bool done{false}; std::atomic_bool success{false}; Transaction trans; trans.queries = statements; trans.done = &done; trans.success = &success; { QMutexLocker locker{&transactionsMutex}; pendingTransactions.enqueue(trans); } // We can't use blocking queued here, otherwise we might process future transactions // before returning, but we only want to wait until this transaction is done. QMetaObject::invokeMethod(this, "process"); while (!done.load(std::memory_order_acquire)) QThread::msleep(10); return success.load(std::memory_order_acquire); } /** * @brief Executes a SQL transaction asynchronously. * @param statement Statement to execute. */ void RawDatabase::execLater(const QString& statement) { execLater(Query{statement}); } void RawDatabase::execLater(const RawDatabase::Query& statement) { execLater(QVector{statement}); } void RawDatabase::execLater(const QVector& statements) { if (!sqlite) { qWarning() << "Trying to exec, but the database is not open"; return; } Transaction trans; trans.queries = statements; { QMutexLocker locker{&transactionsMutex}; pendingTransactions.enqueue(trans); } QMetaObject::invokeMethod(this, "process", Qt::QueuedConnection); } /** * @brief Waits until all the pending transactions are executed. */ void RawDatabase::sync() { QMetaObject::invokeMethod(this, "process", Qt::BlockingQueuedConnection); } /** * @brief Changes the database password, encrypting or decrypting if necessary. * @param password If password is empty, the database will be decrypted. * @return True if success, false otherwise. * @note Will process all transactions before changing the password. */ bool RawDatabase::setPassword(const QString& password) { if (!sqlite) { qWarning() << "Trying to change the password, but the database is not open"; return false; } if (QThread::currentThread() != workerThread.get()) { bool ret; QMetaObject::invokeMethod(this, "setPassword", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, password)); return ret; } // If we need to decrypt or encrypt, we'll need to sync and close, // so we always process the pending queue before rekeying for consistency process(); if (QFile::exists(path + ".tmp")) { qWarning() << "Found old temporary export file while rekeying, deleting it"; QFile::remove(path + ".tmp"); } if (!password.isEmpty()) { QString newHexKey = deriveKey(password, currentSalt); if (!currentHexKey.isEmpty()) { if (!execNow("PRAGMA rekey = \"x'" + newHexKey + "'\"")) { qWarning() << "Failed to change encryption key"; close(); return false; } } else { if (!encryptDatabase(newHexKey)) { close(); return false; } currentHexKey = newHexKey; } } else { if (currentHexKey.isEmpty()) return true; if (!decryptDatabase()) { close(); return false; } } return true; } bool RawDatabase::encryptDatabase(const QString& newHexKey) { const auto user_version = getUserVersion(); if (user_version < 0) { return false; } if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS encrypted KEY \"x'" + newHexKey + "'\";")) { qWarning() << "Failed to export encrypted database"; return false; } if (!setCipherParameters(SqlCipherParams::p4_0, "encrypted")) { return false; } if (!execNow("SELECT sqlcipher_export('encrypted');")) { return false; } if (!execNow(QString("PRAGMA encrypted.user_version = %1;").arg(user_version))) { return false; } if (!execNow("DETACH DATABASE encrypted;")) { return false; } return commitDbSwap(newHexKey); } bool RawDatabase::decryptDatabase() { const auto user_version = getUserVersion(); if (user_version < 0) { return false; } if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS plaintext KEY '';" "SELECT sqlcipher_export('plaintext');")) { qWarning() << "Failed to export decrypted database"; return false; } if (!execNow(QString("PRAGMA plaintext.user_version = %1;").arg(user_version))) { return false; } if (!execNow("DETACH DATABASE plaintext;")) { return false; } return commitDbSwap({}); } bool RawDatabase::commitDbSwap(const QString& hexKey) { // This is racy as hell, but nobody will race with us since we hold the profile lock // If we crash or die here, the rename should be atomic, so we can recover no matter // what close(); QFile::remove(path); QFile::rename(path + ".tmp", path); currentHexKey = hexKey; if (!open(path, currentHexKey)) { qCritical() << "Failed to swap db"; return false; } return true; } /** * @brief Moves the database file on disk to match the new path. * @param newPath Path to move database file. * @return True if success, false otherwise. * * @note Will process all transactions before renaming */ bool RawDatabase::rename(const QString& newPath) { if (!sqlite) { qWarning() << "Trying to change the password, but the database is not open"; return false; } if (QThread::currentThread() != workerThread.get()) { bool ret; QMetaObject::invokeMethod(this, "rename", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, newPath)); return ret; } process(); if (path == newPath) return true; if (QFile::exists(newPath)) return false; close(); if (!QFile::rename(path, newPath)) return false; path = newPath; return open(path, currentHexKey); } /** * @brief Deletes the on disk database file after closing it. * @note Will process all transactions before deletings. * @return True if success, false otherwise. */ bool RawDatabase::remove() { if (!sqlite) { qWarning() << "Trying to remove the database, but it is not open"; return false; } if (QThread::currentThread() != workerThread.get()) { bool ret; QMetaObject::invokeMethod(this, "remove", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret)); return ret; } qDebug() << "Removing database " << path; close(); return QFile::remove(path); } /** * @brief Functor used to free tox_pass_key memory. * * This functor can be used as Deleter for smart pointers. * @note Doesn't take care of overwriting the key. */ struct PassKeyDeleter { void operator()(Tox_Pass_Key* pass_key) { tox_pass_key_free(pass_key); } }; /** * @brief Derives a 256bit key from the password and returns it hex-encoded * @param password Password to decrypt database * @return String representation of key * @deprecated deprecated on 2016-11-06, kept for compatibility, replaced by the salted version */ QString RawDatabase::deriveKey(const QString& password) { if (password.isEmpty()) return {}; const QByteArray passData = password.toUtf8(); static_assert(TOX_PASS_KEY_LENGTH >= 32, "toxcore must provide 256bit or longer keys"); static const uint8_t expandConstant[TOX_PASS_SALT_LENGTH + 1] = "L'ignorance est le pire des maux"; const std::unique_ptr key(tox_pass_key_derive_with_salt( reinterpret_cast(passData.data()), static_cast(passData.size()), expandConstant, nullptr)); return QByteArray(reinterpret_cast(key.get()) + 32, 32).toHex(); } /** * @brief Derives a 256bit key from the password and returns it hex-encoded * @param password Password to decrypt database * @param salt Salt to improve password strength, must be TOX_PASS_SALT_LENGTH bytes * @return String representation of key */ QString RawDatabase::deriveKey(const QString& password, const QByteArray& salt) { if (password.isEmpty()) { return {}; } if (salt.length() != TOX_PASS_SALT_LENGTH) { qWarning() << "Salt length doesn't match toxencryptsave expections"; return {}; } const QByteArray passData = password.toUtf8(); static_assert(TOX_PASS_KEY_LENGTH >= 32, "toxcore must provide 256bit or longer keys"); const std::unique_ptr key(tox_pass_key_derive_with_salt( reinterpret_cast(passData.data()), static_cast(passData.size()), reinterpret_cast(salt.constData()), nullptr)); return QByteArray(reinterpret_cast(key.get()) + 32, 32).toHex(); } /** * @brief Implements the actual processing of pending transactions. * Unqueues, compiles, binds and executes queries, then notifies of results * * @warning MUST only be called from the worker thread */ void RawDatabase::process() { assert(QThread::currentThread() == workerThread.get()); if (!sqlite) return; forever { // Fetch the next transaction Transaction trans; { QMutexLocker locker{&transactionsMutex}; if (pendingTransactions.isEmpty()) return; trans = pendingTransactions.dequeue(); } // In case we exit early, prepare to signal errors if (trans.success != nullptr) trans.success->store(false, std::memory_order_release); // Add transaction commands if necessary if (trans.queries.size() > 1) { trans.queries.prepend({"BEGIN;"}); trans.queries.append({"COMMIT;"}); } // Compile queries for (Query& query : trans.queries) { assert(query.statements.isEmpty()); // sqlite3_prepare_v2 only compiles one statement at a time in the query, // we need to loop over them all int curParam = 0; const char* compileTail = query.query.data(); do { // Compile the next statement sqlite3_stmt* stmt; int r; if ((r = sqlite3_prepare_v2(sqlite, compileTail, query.query.size() - static_cast(compileTail - query.query.data()), &stmt, &compileTail)) != SQLITE_OK) { qWarning() << "Failed to prepare statement" << anonymizeQuery(query.query) << "and returned" << r; qWarning("The full error is %d: %s", sqlite3_errcode(sqlite), sqlite3_errmsg(sqlite)); goto cleanupStatements; } query.statements += stmt; // Now we can bind our params to this statement int nParams = sqlite3_bind_parameter_count(stmt); if (query.blobs.size() < curParam + nParams) { qWarning() << "Not enough parameters to bind to query " << anonymizeQuery(query.query); goto cleanupStatements; } for (int i = 0; i < nParams; ++i) { const QByteArray& blob = query.blobs[curParam + i]; if (sqlite3_bind_blob(stmt, i + 1, blob.data(), blob.size(), SQLITE_STATIC) != SQLITE_OK) { qWarning() << "Failed to bind param" << curParam + i << "to query" << anonymizeQuery(query.query); goto cleanupStatements; } } curParam += nParams; } while (compileTail != query.query.data() + query.query.size()); // Execute each statement of each query of our transaction for (sqlite3_stmt* stmt : query.statements) { int column_count = sqlite3_column_count(stmt); int result; do { result = sqlite3_step(stmt); // Execute our row callback if (result == SQLITE_ROW && query.rowCallback) { QVector row; for (int i = 0; i < column_count; ++i) row += extractData(stmt, i); query.rowCallback(row); } } while (result == SQLITE_ROW); if (result == SQLITE_DONE) continue; QString anonQuery = anonymizeQuery(query.query); switch (result) { case SQLITE_ERROR: qWarning() << "Error executing query" << anonQuery; goto cleanupStatements; case SQLITE_MISUSE: qWarning() << "Misuse executing query" << anonQuery; goto cleanupStatements; case SQLITE_CONSTRAINT: qWarning() << "Constraint error executing query" << anonQuery; goto cleanupStatements; default: qWarning() << "Unknown error" << result << "executing query" << anonQuery; goto cleanupStatements; } } if (query.insertCallback) query.insertCallback(RowId{sqlite3_last_insert_rowid(sqlite)}); } if (trans.success != nullptr) trans.success->store(true, std::memory_order_release); // Free our statements cleanupStatements: for (Query& query : trans.queries) { for (sqlite3_stmt* stmt : query.statements) sqlite3_finalize(stmt); query.statements.clear(); } // Signal transaction results if (trans.done != nullptr) trans.done->store(true, std::memory_order_release); } } /** * @brief Hides public keys and timestamps in query. * @param query Source query, which should be anonymized. * @return Query without timestamps and public keys. */ QString RawDatabase::anonymizeQuery(const QByteArray& query) { QString queryString(query); queryString.replace(QRegularExpression("chat.public_key='[A-F0-9]{64}'"), "char.public_key=''"); queryString.replace(QRegularExpression("timestamp BETWEEN \\d{5,} AND \\d{5,}"), "timestamp BETWEEN AND "); return queryString; } /** * @brief Extracts a variant from one column of a result row depending on the column type. * @param stmt Statement to execute. * @param col Number of column to extract. * @return Extracted data. */ QVariant RawDatabase::extractData(sqlite3_stmt* stmt, int col) { int type = sqlite3_column_type(stmt, col); if (type == SQLITE_INTEGER) { return sqlite3_column_int64(stmt, col); } else if (type == SQLITE_TEXT) { const char* str = reinterpret_cast(sqlite3_column_text(stmt, col)); int len = sqlite3_column_bytes(stmt, col); return QString::fromUtf8(str, len); } else if (type == SQLITE_NULL) { return QVariant{}; } else { const char* data = reinterpret_cast(sqlite3_column_blob(stmt, col)); int len = sqlite3_column_bytes(stmt, col); return QByteArray::fromRawData(data, len); } } /** * @brief Use for create function in db for search data use regular experessions without case sensitive * @param ctx ctx the context in which an SQL function executes * @param argc number of arguments * @param argv arguments */ void RawDatabase::regexpInsensitive(sqlite3_context* ctx, int argc, sqlite3_value** argv) { regexp(ctx, argc, argv, QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption); } /** * @brief Use for create function in db for search data use regular experessions without case sensitive * @param ctx the context in which an SQL function executes * @param argc number of arguments * @param argv arguments */ void RawDatabase::regexpSensitive(sqlite3_context* ctx, int argc, sqlite3_value** argv) { regexp(ctx, argc, argv, QRegularExpression::UseUnicodePropertiesOption); } void RawDatabase::regexp(sqlite3_context* ctx, int argc, sqlite3_value** argv, const QRegularExpression::PatternOptions cs) { QRegularExpression regex; const QString str1(reinterpret_cast(sqlite3_value_text(argv[0]))); const QString str2(reinterpret_cast(sqlite3_value_text(argv[1]))); regex.setPattern(str1); regex.setPatternOptions(cs); const bool b = str2.contains(regex); if (b) { sqlite3_result_int(ctx, 1); } else { sqlite3_result_int(ctx, 0); } } qTox/src/persistence/db/rawdatabase.h000066400000000000000000000127251415623743500201610ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef RAWDATABASE_H #define RAWDATABASE_H #include "src/util/strongtype.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /// The two following defines are required to use SQLCipher /// They are used by the sqlite3.h header #define SQLITE_HAS_CODEC #define SQLITE_TEMP_STORE 2 #include using RowId = NamedType; Q_DECLARE_METATYPE(RowId); class RawDatabase : QObject { Q_OBJECT public: class Query { public: Query(QString query, QVector blobs = {}, const std::function& insertCallback = {}) : query{query.toUtf8()} , blobs{blobs} , insertCallback{insertCallback} { } Query(QString query, const std::function& insertCallback) : query{query.toUtf8()} , insertCallback{insertCallback} { } Query(QString query, const std::function&)>& rowCallback) : query{query.toUtf8()} , rowCallback{rowCallback} { } Query() = default; private: QByteArray query; QVector blobs; std::function insertCallback; std::function&)> rowCallback; QVector statements; friend class RawDatabase; }; public: enum class SqlCipherParams { // keep these sorted in upgrade order p3_0, // SQLCipher 3.0 default encryption params // SQLCipher 4.0 default params where SQLCipher 3.0 supports them, but 3.0 params where not possible. // We accidentally got to this state when attemption to update all databases to 4.0 defaults even when using // SQLCipher 3.x, but might as well keep using these for people with SQLCipher 3.x. halfUpgradedTo4, p4_0 // SQLCipher 4.0 default encryption params }; RawDatabase(const QString& path, const QString& password, const QByteArray& salt); ~RawDatabase(); bool isOpen(); bool execNow(const QString& statement); bool execNow(const Query& statement); bool execNow(const QVector& statements); void execLater(const QString& statement); void execLater(const Query& statement); void execLater(const QVector& statements); void sync(); static QString toString(SqlCipherParams params) { switch (params) { case SqlCipherParams::p3_0: return "3.0 default"; case SqlCipherParams::halfUpgradedTo4: return "3.x max compatible"; case SqlCipherParams::p4_0: return "4.0 default"; } assert(false); return {}; } public slots: bool setPassword(const QString& password); bool rename(const QString& newPath); bool remove(); protected slots: bool open(const QString& path, const QString& hexKey = {}); void close(); void process(); private: QString anonymizeQuery(const QByteArray& query); bool openEncryptedDatabaseAtLatestSupportedVersion(const QString& hexKey); bool updateSavedCipherParameters(const QString& hexKey, SqlCipherParams newParams); bool setCipherParameters(SqlCipherParams params, const QString& database = {}); SqlCipherParams highestSupportedParams(); SqlCipherParams readSavedCipherParams(const QString& hexKey, SqlCipherParams newParams); bool setKey(const QString& hexKey); int getUserVersion(); bool encryptDatabase(const QString& newHexKey); bool decryptDatabase(); bool commitDbSwap(const QString& hexKey); bool testUsable(); protected: static QString deriveKey(const QString& password, const QByteArray& salt); static QString deriveKey(const QString& password); static QVariant extractData(sqlite3_stmt* stmt, int col); static void regexpInsensitive(sqlite3_context* ctx, int argc, sqlite3_value** argv); static void regexpSensitive(sqlite3_context* ctx, int argc, sqlite3_value** argv); private: static void regexp(sqlite3_context* ctx, int argc, sqlite3_value** argv, const QRegularExpression::PatternOptions cs); struct Transaction { QVector queries; std::atomic_bool* success = nullptr; std::atomic_bool* done = nullptr; }; private: sqlite3* sqlite; std::unique_ptr workerThread; QQueue pendingTransactions; QMutex transactionsMutex; QString path; QByteArray currentSalt; QString currentHexKey; }; #endif // RAWDATABASE_H qTox/src/persistence/history.cpp000066400000000000000000001126351415623743500173530ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #include "history.h" #include "profile.h" #include "settings.h" #include "db/rawdatabase.h" namespace { static constexpr int SCHEMA_VERSION = 4; bool createCurrentSchema(RawDatabase& db) { QVector queries; queries += RawDatabase::Query(QStringLiteral( "CREATE TABLE peers (id INTEGER PRIMARY KEY, " "public_key TEXT NOT NULL UNIQUE);" "CREATE TABLE aliases (id INTEGER PRIMARY KEY, " "owner INTEGER, " "display_name BLOB NOT NULL, " "UNIQUE(owner, display_name));" "CREATE TABLE history " "(id INTEGER PRIMARY KEY, " "timestamp INTEGER NOT NULL, " "chat_id INTEGER NOT NULL, " "sender_alias INTEGER NOT NULL, " // even though technically a message can be null for file transfer, we've opted // to just insert an empty string when there's no content, this moderately simplifies // implementating to leakon as currently our database doesn't have support for optional // fields. We would either have to insert "?" or "null" based on if message exists and then // ensure that our blob vector always has the right number of fields. Better to just // leave this as NOT NULL for now. "message BLOB NOT NULL, " "file_id INTEGER);" "CREATE TABLE file_transfers " "(id INTEGER PRIMARY KEY, " "chat_id INTEGER NOT NULL, " "file_restart_id BLOB NOT NULL, " "file_name BLOB NOT NULL, " "file_path BLOB NOT NULL, " "file_hash BLOB NOT NULL, " "file_size INTEGER NOT NULL, " "direction INTEGER NOT NULL, " "file_state INTEGER NOT NULL);" "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY);" "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY);")); // sqlite doesn't support including the index as part of the CREATE TABLE statement, so add a second query queries += RawDatabase::Query( "CREATE INDEX chat_id_idx on history (chat_id);"); queries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = %1;").arg(SCHEMA_VERSION)); return db.execNow(queries); } bool isNewDb(std::shared_ptr& db, bool& success) { bool newDb; if (!db->execNow(RawDatabase::Query("SELECT COUNT(*) FROM sqlite_master;", [&](const QVector& row) { newDb = row[0].toLongLong() == 0; }))) { db.reset(); success = false; return false; } success = true; return newDb; } bool dbSchema0to1(RawDatabase& db) { QVector queries; queries += RawDatabase::Query(QStringLiteral( "CREATE TABLE file_transfers " "(id INTEGER PRIMARY KEY, " "chat_id INTEGER NOT NULL, " "file_restart_id BLOB NOT NULL, " "file_name BLOB NOT NULL, " "file_path BLOB NOT NULL, " "file_hash BLOB NOT NULL, " "file_size INTEGER NOT NULL, " "direction INTEGER NOT NULL, " "file_state INTEGER NOT NULL);")); queries += RawDatabase::Query(QStringLiteral("ALTER TABLE history ADD file_id INTEGER;")); queries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 1;")); return db.execNow(queries); } bool dbSchema1to2(RawDatabase& db) { // Any faux_offline_pending message, in a chat that has newer delivered // message is decided to be broken. It must be moved from // faux_offline_pending to broken_messages // the last non-pending message in each chat QString lastDeliveredQuery = QString( "SELECT chat_id, MAX(history.id) FROM " "history JOIN peers chat ON chat_id = chat.id " "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " "WHERE faux_offline_pending.id IS NULL " "GROUP BY chat_id;"); QVector upgradeQueries; upgradeQueries += RawDatabase::Query(QStringLiteral( "CREATE TABLE broken_messages " "(id INTEGER PRIMARY KEY);")); auto rowCallback = [&upgradeQueries](const QVector& row) { auto chatId = row[0].toLongLong(); auto lastDeliveredHistoryId = row[1].toLongLong(); upgradeQueries += QString("INSERT INTO broken_messages " "SELECT faux_offline_pending.id FROM " "history JOIN faux_offline_pending " "ON faux_offline_pending.id = history.id " "WHERE history.chat_id=%1 " "AND history.id < %2;").arg(chatId).arg(lastDeliveredHistoryId); }; // note this doesn't modify the db, just generate new queries, so is safe // to run outside of our upgrade transaction if (!db.execNow({lastDeliveredQuery, rowCallback})) { return false; } upgradeQueries += QString( "DELETE FROM faux_offline_pending " "WHERE id in (" "SELECT id FROM broken_messages);"); upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 2;")); return db.execNow(upgradeQueries); } bool dbSchema2to3(RawDatabase& db) { // Any faux_offline_pending message with the content "/me " are action // messages that qTox previously let a user enter, but that will cause an // action type message to be sent to toxcore, with 0 length, which will // always fail. They must be be moved from faux_offline_pending to broken_messages // to avoid qTox from erroring trying to send them on every connect const QString emptyActionMessageString = "/me "; QVector upgradeQueries; upgradeQueries += RawDatabase::Query{QString("INSERT INTO broken_messages " "SELECT faux_offline_pending.id FROM " "history JOIN faux_offline_pending " "ON faux_offline_pending.id = history.id " "WHERE history.message = ?;"), {emptyActionMessageString.toUtf8()}}; upgradeQueries += QString( "DELETE FROM faux_offline_pending " "WHERE id in (" "SELECT id FROM broken_messages);"); upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 3;")); return db.execNow(upgradeQueries); } bool dbSchema3to4(RawDatabase& db) { QVector upgradeQueries; upgradeQueries += RawDatabase::Query{QString( "CREATE INDEX chat_id_idx on history (chat_id);")}; upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 4;")); return db.execNow(upgradeQueries); } /** * @brief Upgrade the db schema * @return True if the schema upgrade succeded, false otherwise * @note On future alterations of the database all you have to do is bump the SCHEMA_VERSION * variable and add another case to the switch statement below. Make sure to fall through on each case. */ bool dbSchemaUpgrade(std::shared_ptr& db) { int64_t databaseSchemaVersion; if (!db->execNow(RawDatabase::Query("PRAGMA user_version", [&](const QVector& row) { databaseSchemaVersion = row[0].toLongLong(); }))) { qCritical() << "History failed to read user_version"; return false; } if (databaseSchemaVersion > SCHEMA_VERSION) { qWarning().nospace() << "Database version (" << databaseSchemaVersion << ") is newer than we currently support (" << SCHEMA_VERSION << "). Please upgrade qTox"; // We don't know what future versions have done, we have to disable db access until we re-upgrade return false; } else if (databaseSchemaVersion == SCHEMA_VERSION) { // No work to do return true; } switch (databaseSchemaVersion) { case 0: { // Note: 0 is a special version that is actually two versions. // possibility 1) it is a newly created database and it neesds the current schema to be created. // possibility 2) it is a old existing database, before version 1 and before we saved schema version, // and needs to be updated. bool success = false; const bool newDb = isNewDb(db, success); if (!success) { qCritical() << "Failed to create current db schema"; return false; } if (newDb) { if (!createCurrentSchema(*db)) { qCritical() << "Failed to create current db schema"; return false; } qDebug() << "Database created at schema version" << SCHEMA_VERSION; break; // new db is the only case where we don't incrementally upgrade through each version } else { if (!dbSchema0to1(*db)) { qCritical() << "Failed to upgrade db to schema version 1, aborting"; return false; } qDebug() << "Database upgraded incrementally to schema version 1"; } } // fallthrough case 1: if (!dbSchema1to2(*db)) { qCritical() << "Failed to upgrade db to schema version 2, aborting"; return false; } qDebug() << "Database upgraded incrementally to schema version 2"; //fallthrough case 2: if (!dbSchema2to3(*db)) { qCritical() << "Failed to upgrade db to schema version 3, aborting"; return false; } qDebug() << "Database upgraded incrementally to schema version 3"; case 3: if (!dbSchema3to4(*db)) { qCritical() << "Failed to upgrade db to schema version 4, aborting"; return false; } qDebug() << "Database upgraded incrementally to schema version 4"; // etc. default: qInfo() << "Database upgrade finished (databaseSchemaVersion" << databaseSchemaVersion << "->" << SCHEMA_VERSION << ")"; } return true; } MessageState getMessageState(bool isPending, bool isBroken) { assert(!(isPending && isBroken)); MessageState messageState; if (isPending) { messageState = MessageState::pending; } else if (isBroken) { messageState = MessageState::broken; } else { messageState = MessageState::complete; } return messageState; } } // namespace /** * @class History * @brief Interacts with the profile database to save the chat history. * * @var QHash History::peers * @brief Maps friend public keys to unique IDs by index. * Caches mappings to speed up message saving. */ static constexpr int NUM_MESSAGES_DEFAULT = 100; // arbitrary number of messages loaded when not loading by date FileDbInsertionData::FileDbInsertionData() { static int id = qRegisterMetaType(); (void)id; } /** * @brief Prepares the database to work with the history. * @param db This database will be prepared for use with the history. */ History::History(std::shared_ptr db_) : db(db_) { if (!isValid()) { qWarning() << "Database not open, init failed"; return; } const auto upgradeSucceeded = dbSchemaUpgrade(db); // dbSchemaUpgrade may have put us in an invalid state if (!upgradeSucceeded) { db.reset(); return; } connect(this, &History::fileInsertionReady, this, &History::onFileInsertionReady); connect(this, &History::fileInserted, this, &History::onFileInserted); // Cache our current peers db->execLater(RawDatabase::Query{"SELECT public_key, id FROM peers;", [this](const QVector& row) { peers[row[0].toString()] = row[1].toInt(); }}); } History::~History() { if (!isValid()) { return; } // We could have execLater requests pending with a lambda attached, // so clear the pending transactions first db->sync(); } /** * @brief Checks if the database was opened successfully * @return True if database if opened, false otherwise. */ bool History::isValid() { return db && db->isOpen(); } /** * @brief Checks if a friend has chat history * @param friendPk * @return True if has, false otherwise. */ bool History::historyExists(const ToxPk& friendPk) { if (historyAccessBlocked()) { return false; } return !getMessagesForFriend(friendPk, 0, 1).empty(); } /** * @brief Erases all the chat history from the database. */ void History::eraseHistory() { if (!isValid()) { return; } db->execNow("DELETE FROM faux_offline_pending;" "DELETE FROM history;" "DELETE FROM aliases;" "DELETE FROM peers;" "DELETE FROM file_transfers;" "DELETE FROM broken_messages;" "VACUUM;"); } /** * @brief Erases the chat history with one friend. * @param friendPk Friend public key to erase. */ void History::removeFriendHistory(const QString& friendPk) { if (!isValid()) { return; } if (!peers.contains(friendPk)) { return; } int64_t id = peers[friendPk]; QString queryText = QString("DELETE FROM faux_offline_pending " "WHERE faux_offline_pending.id IN ( " " SELECT faux_offline_pending.id FROM faux_offline_pending " " LEFT JOIN history ON faux_offline_pending.id = history.id " " WHERE chat_id=%1 " "); " "DELETE FROM broken_messages " "WHERE broken_messages.id IN ( " " SELECT broken_messages.id FROM broken_messages " " LEFT JOIN history ON broken_messages.id = history.id " " WHERE chat_id=%1 " "); " "DELETE FROM history WHERE chat_id=%1; " "DELETE FROM aliases WHERE owner=%1; " "DELETE FROM peers WHERE id=%1; " "DELETE FROM file_transfers WHERE chat_id=%1;" "VACUUM;") .arg(id); if (db->execNow(queryText)) { peers.remove(friendPk); } else { qWarning() << "Failed to remove friend's history"; } } /** * @brief Generate query to insert new message in database * @param friendPk Friend publick key to save. * @param message Message to save. * @param sender Sender to save. * @param time Time of message sending. * @param isDelivered True if message was already delivered. * @param dispName Name, which should be displayed. * @param insertIdCallback Function, called after query execution. */ QVector History::generateNewMessageQueries(const QString& friendPk, const QString& message, const QString& sender, const QDateTime& time, bool isDelivered, QString dispName, std::function insertIdCallback) { QVector queries; // Get the db id of the peer we're chatting with int64_t peerId; if (peers.contains(friendPk)) { peerId = (peers)[friendPk]; } else { if (peers.isEmpty()) { peerId = 0; } else { peerId = *std::max_element(peers.begin(), peers.end()) + 1; } (peers)[friendPk] = peerId; queries += RawDatabase::Query(("INSERT INTO peers (id, public_key) " "VALUES (%1, '" + friendPk + "');") .arg(peerId)); } // Get the db id of the sender of the message int64_t senderId; if (peers.contains(sender)) { senderId = (peers)[sender]; } else { if (peers.isEmpty()) { senderId = 0; } else { senderId = *std::max_element(peers.begin(), peers.end()) + 1; } (peers)[sender] = senderId; queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) " "VALUES (%1, '" + sender + "');") .arg(senderId)}; } queries += RawDatabase::Query( QString("INSERT OR IGNORE INTO aliases (owner, display_name) VALUES (%1, ?);").arg(senderId), {dispName.toUtf8()}); // If the alias already existed, the insert will ignore the conflict and last_insert_rowid() // will return garbage, // so we have to check changes() and manually fetch the row ID in this case queries += RawDatabase::Query(QString( "INSERT INTO history (timestamp, chat_id, message, sender_alias) " "VALUES (%1, %2, ?, (" " CASE WHEN changes() IS 0 THEN (" " SELECT id FROM aliases WHERE owner=%3 AND display_name=?)" " ELSE last_insert_rowid() END" "));") .arg(time.toMSecsSinceEpoch()) .arg(peerId) .arg(senderId), {message.toUtf8(), dispName.toUtf8()}, insertIdCallback); if (!isDelivered) { queries += RawDatabase::Query{"INSERT INTO faux_offline_pending (id) VALUES (" " last_insert_rowid()" ");"}; } return queries; } void History::onFileInsertionReady(FileDbInsertionData data) { QVector queries; std::weak_ptr weakThis = shared_from_this(); // peerId is guaranteed to be inserted since we just used it in addNewMessage auto peerId = peers[data.friendPk]; // Copy to pass into labmda for later auto fileId = data.fileId; queries += RawDatabase::Query(QStringLiteral( "INSERT INTO file_transfers (chat_id, file_restart_id, " "file_path, file_name, file_hash, file_size, direction, file_state) " "VALUES (%1, ?, ?, ?, ?, %2, %3, %4);") .arg(peerId) .arg(data.size) .arg(static_cast(data.direction)) .arg(ToxFile::CANCELED), {data.fileId.toUtf8(), data.filePath.toUtf8(), data.fileName.toUtf8(), QByteArray()}, [weakThis, fileId](RowId id) { auto pThis = weakThis.lock(); if (pThis) { emit pThis->fileInserted(id, fileId); } }); queries += RawDatabase::Query(QStringLiteral("UPDATE history " "SET file_id = (last_insert_rowid()) " "WHERE id = %1") .arg(data.historyId.get())); db->execLater(queries); } void History::onFileInserted(RowId dbId, QString fileId) { auto& fileInfo = fileInfos[fileId]; if (fileInfo.finished) { db->execLater( generateFileFinished(dbId, fileInfo.success, fileInfo.filePath, fileInfo.fileHash)); fileInfos.remove(fileId); } else { fileInfo.finished = false; fileInfo.fileId = dbId; } } RawDatabase::Query History::generateFileFinished(RowId id, bool success, const QString& filePath, const QByteArray& fileHash) { auto file_state = success ? ToxFile::FINISHED : ToxFile::CANCELED; if (filePath.length()) { return RawDatabase::Query(QStringLiteral("UPDATE file_transfers " "SET file_state = %1, file_path = ?, file_hash = ?" "WHERE id = %2") .arg(file_state) .arg(id.get()), {filePath.toUtf8(), fileHash}); } else { return RawDatabase::Query(QStringLiteral("UPDATE file_transfers " "SET finished = %1 " "WHERE id = %2") .arg(file_state) .arg(id.get())); } } void History::addNewFileMessage(const QString& friendPk, const QString& fileId, const QString& fileName, const QString& filePath, int64_t size, const QString& sender, const QDateTime& time, QString const& dispName) { if (historyAccessBlocked()) { return; } // This is an incredibly far from an optimal way of implementing this, // but given the frequency that people are going to be initiating a file // transfer we can probably live with it. // Since both inserting an alias for a user and inserting a file transfer // will generate new ids, there is no good way to inject both new ids into the // history query without refactoring our RawDatabase::Query and processor loops. // What we will do instead is chain callbacks to try to get reasonable behavior. // We can call the generateNewMessageQueries() fn to insert a message with an empty // message in it, and get the id with the callbck. Once we have the id we can ammend // the data to have our newly inserted file_id as well ToxFile::FileDirection direction; if (sender == friendPk) { direction = ToxFile::RECEIVING; } else { direction = ToxFile::SENDING; } std::weak_ptr weakThis = shared_from_this(); FileDbInsertionData insertionData; insertionData.friendPk = friendPk; insertionData.fileId = fileId; insertionData.fileName = fileName; insertionData.filePath = filePath; insertionData.size = size; insertionData.direction = direction; auto insertFileTransferFn = [weakThis, insertionData](RowId messageId) { auto insertionDataRw = std::move(insertionData); insertionDataRw.historyId = messageId; auto thisPtr = weakThis.lock(); if (thisPtr) emit thisPtr->fileInsertionReady(std::move(insertionDataRw)); }; addNewMessage(friendPk, "", sender, time, true, dispName, insertFileTransferFn); } /** * @brief Saves a chat message in the database. * @param friendPk Friend publick key to save. * @param message Message to save. * @param sender Sender to save. * @param time Time of message sending. * @param isDelivered True if message was already delivered. * @param dispName Name, which should be displayed. * @param insertIdCallback Function, called after query execution. */ void History::addNewMessage(const QString& friendPk, const QString& message, const QString& sender, const QDateTime& time, bool isDelivered, QString dispName, const std::function& insertIdCallback) { if (historyAccessBlocked()) { return; } db->execLater(generateNewMessageQueries(friendPk, message, sender, time, isDelivered, dispName, insertIdCallback)); } void History::setFileFinished(const QString& fileId, bool success, const QString& filePath, const QByteArray& fileHash) { if (historyAccessBlocked()) { return; } auto& fileInfo = fileInfos[fileId]; if (fileInfo.fileId.get() == -1) { fileInfo.finished = true; fileInfo.success = success; fileInfo.filePath = filePath; fileInfo.fileHash = fileHash; } else { db->execLater(generateFileFinished(fileInfo.fileId, success, filePath, fileHash)); } fileInfos.remove(fileId); } size_t History::getNumMessagesForFriend(const ToxPk& friendPk) { if (historyAccessBlocked()) { return 0; } return getNumMessagesForFriendBeforeDate(friendPk, QDateTime()); } size_t History::getNumMessagesForFriendBeforeDate(const ToxPk& friendPk, const QDateTime& date) { if (historyAccessBlocked()) { return 0; } QString queryText = QString("SELECT COUNT(history.id) " "FROM history " "JOIN peers chat ON chat_id = chat.id " "WHERE chat.public_key='%1'") .arg(friendPk.toString()); if (date.isNull()) { queryText += ";"; } else { queryText += QString(" AND timestamp < %1;").arg(date.toMSecsSinceEpoch()); } size_t numMessages = 0; auto rowCallback = [&numMessages](const QVector& row) { numMessages = row[0].toLongLong(); }; db->execNow({queryText, rowCallback}); return numMessages; } QList History::getMessagesForFriend(const ToxPk& friendPk, size_t firstIdx, size_t lastIdx) { if (historyAccessBlocked()) { return {}; } QList messages; // Don't forget to update the rowCallback if you change the selected columns! QString queryText = QString("SELECT history.id, faux_offline_pending.id, timestamp, " "chat.public_key, aliases.display_name, sender.public_key, " "message, file_transfers.file_restart_id, " "file_transfers.file_path, file_transfers.file_name, " "file_transfers.file_size, file_transfers.direction, " "file_transfers.file_state, broken_messages.id FROM history " "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " "JOIN peers chat ON history.chat_id = chat.id " "JOIN aliases ON sender_alias = aliases.id " "JOIN peers sender ON aliases.owner = sender.id " "LEFT JOIN file_transfers ON history.file_id = file_transfers.id " "LEFT JOIN broken_messages ON history.id = broken_messages.id " "WHERE chat.public_key='%1' " "LIMIT %2 OFFSET %3;") .arg(friendPk.toString()) .arg(lastIdx - firstIdx) .arg(firstIdx); auto rowCallback = [&messages](const QVector& row) { // dispName and message could have null bytes, QString::fromUtf8 // truncates on null bytes so we strip them auto id = RowId{row[0].toLongLong()}; auto isPending = !row[1].isNull(); auto timestamp = QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()); auto friend_key = row[3].toString(); auto display_name = QString::fromUtf8(row[4].toByteArray().replace('\0', "")); auto sender_key = row[5].toString(); auto isBroken = !row[13].isNull(); MessageState messageState = getMessageState(isPending, isBroken); if (row[7].isNull()) { messages += {id, messageState, timestamp, friend_key, display_name, sender_key, row[6].toString()}; } else { ToxFile file; file.fileKind = TOX_FILE_KIND_DATA; file.resumeFileId = row[7].toString().toUtf8(); file.filePath = row[8].toString(); file.fileName = row[9].toString(); file.filesize = row[10].toLongLong(); file.direction = static_cast(row[11].toLongLong()); file.status = static_cast(row[12].toInt()); messages += {id, messageState, timestamp, friend_key, display_name, sender_key, file}; } }; db->execNow({queryText, rowCallback}); return messages; } QList History::getUndeliveredMessagesForFriend(const ToxPk& friendPk) { if (historyAccessBlocked()) { return {}; } auto queryText = QString("SELECT history.id, faux_offline_pending.id, timestamp, chat.public_key, " "aliases.display_name, sender.public_key, message, broken_messages.id " "FROM history " "JOIN faux_offline_pending ON history.id = faux_offline_pending.id " "JOIN peers chat on history.chat_id = chat.id " "JOIN aliases on sender_alias = aliases.id " "JOIN peers sender on aliases.owner = sender.id " "LEFT JOIN broken_messages ON history.id = broken_messages.id " "WHERE chat.public_key='%1';") .arg(friendPk.toString()); QList ret; auto rowCallback = [&ret](const QVector& row) { // dispName and message could have null bytes, QString::fromUtf8 // truncates on null bytes so we strip them auto id = RowId{row[0].toLongLong()}; auto isPending = !row[1].isNull(); auto timestamp = QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()); auto friend_key = row[3].toString(); auto display_name = QString::fromUtf8(row[4].toByteArray().replace('\0', "")); auto sender_key = row[5].toString(); auto isBroken = !row[7].isNull(); MessageState messageState = getMessageState(isPending, isBroken); ret += {id, messageState, timestamp, friend_key, display_name, sender_key, row[6].toString()}; }; db->execNow({queryText, rowCallback}); return ret; } /** * @brief Search phrase in chat messages * @param friendPk Friend public key * @param from a date message where need to start a search * @param phrase what need to find * @param parameter for search * @return date of the message where the phrase was found */ QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTime& from, QString phrase, const ParameterSearch& parameter) { if (historyAccessBlocked()) { return QDateTime(); } QDateTime result; auto rowCallback = [&result](const QVector& row) { result = QDateTime::fromMSecsSinceEpoch(row[0].toLongLong()); }; phrase.replace("'", "''"); QString message; switch (parameter.filter) { case FilterSearch::Register: message = QStringLiteral("message LIKE '%%1%'").arg(phrase); break; case FilterSearch::WordsOnly: message = QStringLiteral("message REGEXP '%1'") .arg(SearchExtraFunctions::generateFilterWordsOnly(phrase).toLower()); break; case FilterSearch::RegisterAndWordsOnly: message = QStringLiteral("REGEXPSENSITIVE(message, '%1')") .arg(SearchExtraFunctions::generateFilterWordsOnly(phrase)); break; case FilterSearch::Regular: message = QStringLiteral("message REGEXP '%1'").arg(phrase); break; case FilterSearch::RegisterAndRegular: message = QStringLiteral("REGEXPSENSITIVE(message '%1')").arg(phrase); break; default: message = QStringLiteral("LOWER(message) LIKE '%%1%'").arg(phrase.toLower()); break; } QDateTime date = from; if (!date.isValid()) { date = QDateTime::currentDateTime(); } if (parameter.period == PeriodSearch::AfterDate || parameter.period == PeriodSearch::BeforeDate) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) date = parameter.date.startOfDay(); #else date = QDateTime(parameter.date); #endif } QString period; switch (parameter.period) { case PeriodSearch::WithTheFirst: period = QStringLiteral("ORDER BY timestamp ASC LIMIT 1;"); break; case PeriodSearch::AfterDate: period = QStringLiteral("AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;") .arg(date.toMSecsSinceEpoch()); break; case PeriodSearch::BeforeDate: period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;") .arg(date.toMSecsSinceEpoch()); break; default: period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;") .arg(date.toMSecsSinceEpoch()); break; } QString queryText = QStringLiteral("SELECT timestamp " "FROM history " "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " "JOIN peers chat ON chat_id = chat.id " "WHERE chat.public_key='%1' " "AND %2 " "%3") .arg(friendPk) .arg(message) .arg(period); db->execNow({queryText, rowCallback}); return result; } /** * @brief Gets date boundaries in conversation with friendPk. History doesn't model conversation indexes, * but we can count messages between us and friendPk to effectively give us an index. This function * returns how many messages have happened between us <-> friendPk each time the date changes * @param[in] friendPk ToxPk of conversation to retrieve * @param[in] from Start date to look from * @param[in] maxNum Maximum number of date boundaries to retrieve * @note This API may seem a little strange, why not use QDate from and QDate to? The intent is to * have an API that can be used to get the first item after a date (for search) and to get a list * of date changes (for loadHistory). We could write two separate queries but the query is fairly * intricate compared to our other ones so reducing duplication of it is preferable. */ QList History::getNumMessagesForFriendBeforeDateBoundaries(const ToxPk& friendPk, const QDate& from, size_t maxNum) { if (historyAccessBlocked()) { return {}; } auto friendPkString = friendPk.toString(); // No guarantee that this is the most efficient way to do this... // We want to count messages that happened for a friend before a // certain date. We do this by re-joining our table a second time // but this time with the only filter being that our id is less than // the ID of the corresponding row in the table that is grouped by day auto countMessagesForFriend = QString("SELECT COUNT(*) - 1 " // Count - 1 corresponds to 0 indexed message id for friend "FROM history countHistory " // Import unfiltered table as countHistory "JOIN peers chat ON chat_id = chat.id " // link chat_id to chat.id "WHERE chat.public_key = '%1'" // filter this conversation "AND countHistory.id <= history.id") // and filter that our unfiltered table history id only has elements up to history.id .arg(friendPkString); auto limitString = (maxNum) ? QString("LIMIT %1").arg(maxNum) : QString(""); auto queryString = QString("SELECT (%1), (timestamp / 1000 / 60 / 60 / 24) AS day " "FROM history " "JOIN peers chat ON chat_id = chat.id " "WHERE chat.public_key = '%2' " "AND timestamp >= %3 " "GROUP by day " "%4;") .arg(countMessagesForFriend) .arg(friendPkString) #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) .arg(QDateTime(from.startOfDay()).toMSecsSinceEpoch()) #else .arg(QDateTime(from).toMSecsSinceEpoch()) #endif .arg(limitString); QList dateIdxs; auto rowCallback = [&dateIdxs](const QVector& row) { DateIdx dateIdx; dateIdx.numMessagesIn = row[0].toLongLong(); dateIdx.date = QDateTime::fromMSecsSinceEpoch(row[1].toLongLong() * 24 * 60 * 60 * 1000).date(); dateIdxs.append(dateIdx); }; db->execNow({queryString, rowCallback}); return dateIdxs; } /** * @brief Marks a message as delivered. * Removing message from the faux-offline pending messages list. * * @param id Message ID. */ void History::markAsDelivered(RowId messageId) { if (historyAccessBlocked()) { return; } db->execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(messageId.get())); } /** * @brief Determines if history access should be blocked * @return True if history should not be accessed */ bool History::historyAccessBlocked() { if (!Settings::getInstance().getEnableLogging()) { assert(false); qCritical() << "Blocked history access while history is disabled"; return true; } if (!isValid()) { return true; } return false; } qTox/src/persistence/history.h000066400000000000000000000147171415623743500170220ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef HISTORY_H #define HISTORY_H #include #include #include #include #include #include #include #include "src/core/toxfile.h" #include "src/core/toxpk.h" #include "src/persistence/db/rawdatabase.h" #include "src/widget/searchtypes.h" class Profile; class HistoryKeeper; enum class HistMessageContentType { message, file }; class HistMessageContent { public: HistMessageContent(QString message) : data(std::make_shared(std::move(message))) , type(HistMessageContentType::message) {} HistMessageContent(ToxFile file) : data(std::make_shared(std::move(file))) , type(HistMessageContentType::file) {} HistMessageContentType getType() const { return type; } QString& asMessage() { assert(type == HistMessageContentType::message); return *static_cast(data.get()); } ToxFile& asFile() { assert(type == HistMessageContentType::file); return *static_cast(data.get()); } const QString& asMessage() const { assert(type == HistMessageContentType::message); return *static_cast(data.get()); } const ToxFile& asFile() const { assert(type == HistMessageContentType::file); return *static_cast(data.get()); } private: // Not really shared but shared_ptr has support for shared_ptr std::shared_ptr data; HistMessageContentType type; }; struct FileDbInsertionData { FileDbInsertionData(); RowId historyId; QString friendPk; QString fileId; QString fileName; QString filePath; int64_t size; int direction; }; Q_DECLARE_METATYPE(FileDbInsertionData); enum class MessageState { complete, pending, broken }; class History : public QObject, public std::enable_shared_from_this { Q_OBJECT public: struct HistMessage { HistMessage(RowId id, MessageState state, QDateTime timestamp, QString chat, QString dispName, QString sender, QString message) : chat{chat} , sender{sender} , dispName{dispName} , timestamp{timestamp} , id{id} , state{state} , content(std::move(message)) {} HistMessage(RowId id, MessageState state, QDateTime timestamp, QString chat, QString dispName, QString sender, ToxFile file) : chat{chat} , sender{sender} , dispName{dispName} , timestamp{timestamp} , id{id} , state{state} , content(std::move(file)) {} QString chat; QString sender; QString dispName; QDateTime timestamp; RowId id; MessageState state; HistMessageContent content; }; struct DateIdx { QDate date; size_t numMessagesIn; }; public: explicit History(std::shared_ptr db); ~History(); bool isValid(); bool historyExists(const ToxPk& friendPk); void eraseHistory(); void removeFriendHistory(const QString& friendPk); void addNewMessage(const QString& friendPk, const QString& message, const QString& sender, const QDateTime& time, bool isDelivered, QString dispName, const std::function& insertIdCallback = {}); void addNewFileMessage(const QString& friendPk, const QString& fileId, const QString& fileName, const QString& filePath, int64_t size, const QString& sender, const QDateTime& time, QString const& dispName); void setFileFinished(const QString& fileId, bool success, const QString& filePath, const QByteArray& fileHash); size_t getNumMessagesForFriend(const ToxPk& friendPk); size_t getNumMessagesForFriendBeforeDate(const ToxPk& friendPk, const QDateTime& date); QList getMessagesForFriend(const ToxPk& friendPk, size_t firstIdx, size_t lastIdx); QList getUndeliveredMessagesForFriend(const ToxPk& friendPk); QDateTime getDateWhereFindPhrase(const QString& friendPk, const QDateTime& from, QString phrase, const ParameterSearch& parameter); QList getNumMessagesForFriendBeforeDateBoundaries(const ToxPk& friendPk, const QDate& from, size_t maxNum); void markAsDelivered(RowId messageId); protected: QVector generateNewMessageQueries(const QString& friendPk, const QString& message, const QString& sender, const QDateTime& time, bool isDelivered, QString dispName, std::function insertIdCallback = {}); signals: void fileInsertionReady(FileDbInsertionData data); void fileInserted(RowId dbId, QString fileId); private slots: void onFileInsertionReady(FileDbInsertionData data); void onFileInserted(RowId dbId, QString fileId); private: bool historyAccessBlocked(); static RawDatabase::Query generateFileFinished(RowId fileId, bool success, const QString& filePath, const QByteArray& fileHash); std::shared_ptr db; QHash peers; struct FileInfo { bool finished = false; bool success = false; QString filePath; QByteArray fileHash; RowId fileId{-1}; }; // This needs to be a shared pointer to avoid callback lifetime issues QHash fileInfos; }; #endif // HISTORY_H qTox/src/persistence/ifriendsettings.h000066400000000000000000000052471415623743500205200ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef I_FRIEND_SETTINGS_H #define I_FRIEND_SETTINGS_H #include "src/model/interface.h" #include #include class ToxPk; class IFriendSettings { public: enum class AutoAcceptCall { None = 0x00, Audio = 0x01, Video = 0x02, AV = Audio | Video }; Q_DECLARE_FLAGS(AutoAcceptCallFlags, AutoAcceptCall) virtual ~IFriendSettings() = default; virtual QString getContactNote(const ToxPk& pk) const = 0; virtual void setContactNote(const ToxPk& pk, const QString& note) = 0; virtual QString getAutoAcceptDir(const ToxPk& pk) const = 0; virtual void setAutoAcceptDir(const ToxPk& pk, const QString& dir) = 0; virtual AutoAcceptCallFlags getAutoAcceptCall(const ToxPk& pk) const = 0; virtual void setAutoAcceptCall(const ToxPk& pk, AutoAcceptCallFlags accept) = 0; virtual bool getAutoGroupInvite(const ToxPk& pk) const = 0; virtual void setAutoGroupInvite(const ToxPk& pk, bool accept) = 0; virtual QString getFriendAlias(const ToxPk& pk) const = 0; virtual void setFriendAlias(const ToxPk& pk, const QString& alias) = 0; virtual int getFriendCircleID(const ToxPk& pk) const = 0; virtual void setFriendCircleID(const ToxPk& pk, int circleID) = 0; virtual QDateTime getFriendActivity(const ToxPk& pk) const = 0; virtual void setFriendActivity(const ToxPk& pk, const QDateTime& date) = 0; virtual void saveFriendSettings(const ToxPk& pk) = 0; virtual void removeFriendSettings(const ToxPk& pk) = 0; signals: DECLARE_SIGNAL(autoAcceptCallChanged, const ToxPk& pk, AutoAcceptCallFlags accept); DECLARE_SIGNAL(autoGroupInviteChanged, const ToxPk& pk, bool accept); DECLARE_SIGNAL(autoAcceptDirChanged, const ToxPk& pk, const QString& dir); DECLARE_SIGNAL(contactNoteChanged, const ToxPk& pk, const QString& note); }; Q_DECLARE_OPERATORS_FOR_FLAGS(IFriendSettings::AutoAcceptCallFlags) #endif // I_FRIEND_SETTINGS_H qTox/src/persistence/igroupsettings.h000066400000000000000000000022231415623743500203740ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IGROUP_SETTINGS_H #define IGROUP_SETTINGS_H #include class IGroupSettings { public: virtual ~IGroupSettings() = default; virtual QStringList getBlackList() const = 0; virtual void setBlackList(const QStringList& blist) = 0; virtual bool getGroupAlwaysNotify() const = 0; virtual void setGroupAlwaysNotify(bool newValue) = 0; }; #endif /*IGROUP_SETTINGS_H*/ qTox/src/persistence/offlinemsgengine.cpp000066400000000000000000000125451415623743500211700ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "offlinemsgengine.h" #include "src/core/core.h" #include "src/model/friend.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/model/status.h" #include #include #include #include OfflineMsgEngine::OfflineMsgEngine(Friend* frnd, ICoreFriendMessageSender* messageSender) : f(frnd) , messageSender(messageSender) {} /** * @brief Notification that the message is now delivered. * * @param[in] receipt Toxcore message ID which the receipt is for. */ void OfflineMsgEngine::onReceiptReceived(ReceiptNum receipt) { QMutexLocker ml(&mutex); if (receivedReceipts.contains(receipt)) { qWarning() << "Receievd duplicate receipt" << receipt.get() << "from friend" << f->getId(); return; } receivedReceipts.append(receipt); checkForCompleteMessages(receipt); } /** * @brief Add a message which has been saved to history, but not sent yet to the peer. * * OfflineMsgEngine will send this message once the friend becomes online again, then track its * receipt, updating history and chatlog once received. * * @param[in] messageID database RowId of the message, used to eventually mark messages as received in history * @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp */ void OfflineMsgEngine::addUnsentMessage(Message const& message, CompletionFn completionCallback) { QMutexLocker ml(&mutex); unsentMessages.append(OfflineMessage{message, std::chrono::steady_clock::now(), completionCallback}); } /** * @brief Add a message which has been saved to history, and which has been sent to the peer. * * OfflineMsgEngine will track this message's receipt. If the friend goes offline then comes back before the receipt * is received, OfflineMsgEngine will also resend the message, updating history and chatlog once received. * * @param[in] receipt the toxcore message ID, corresponding to expected receipt ID * @param[in] messageID database RowId of the message, used to eventually mark messages as received in history * @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp */ void OfflineMsgEngine::addSentMessage(ReceiptNum receipt, Message const& message, CompletionFn completionCallback) { QMutexLocker ml(&mutex); assert(!sentMessages.contains(receipt)); sentMessages.insert(receipt, {message, std::chrono::steady_clock::now(), completionCallback}); checkForCompleteMessages(receipt); } /** * @brief Deliver all messages, used when a friend comes online. */ void OfflineMsgEngine::deliverOfflineMsgs() { QMutexLocker ml(&mutex); if (!Status::isOnline(f->getStatus())) { return; } if (sentMessages.empty() && unsentMessages.empty()) { return; } QVector messages = sentMessages.values().toVector() + unsentMessages; // order messages by authorship time to resend in same order as they were written std::sort(messages.begin(), messages.end(), [](const OfflineMessage& lhs, const OfflineMessage& rhs) { return lhs.authorshipTime < rhs.authorshipTime; }); removeAllMessages(); for (const auto& message : messages) { QString messageText = message.message.content; ReceiptNum receipt; bool messageSent{false}; if (message.message.isAction) { messageSent = messageSender->sendAction(f->getId(), messageText, receipt); } else { messageSent = messageSender->sendMessage(f->getId(), messageText, receipt); } if (messageSent) { addSentMessage(receipt, message.message, message.completionFn); } else { qCritical() << "deliverOfflineMsgs failed to send message"; addUnsentMessage(message.message, message.completionFn); } } } /** * @brief Removes all messages which are being tracked. */ void OfflineMsgEngine::removeAllMessages() { QMutexLocker ml(&mutex); receivedReceipts.clear(); sentMessages.clear(); unsentMessages.clear(); } void OfflineMsgEngine::completeMessage(QMap::iterator msgIt) { msgIt->completionFn(); receivedReceipts.removeOne(msgIt.key()); sentMessages.erase(msgIt); } void OfflineMsgEngine::checkForCompleteMessages(ReceiptNum receipt) { auto msgIt = sentMessages.find(receipt); const bool receiptReceived = receivedReceipts.contains(receipt); if (!receiptReceived || msgIt == sentMessages.end()) { return; } completeMessage(msgIt); } qTox/src/persistence/offlinemsgengine.h000066400000000000000000000043571415623743500206370ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef OFFLINEMSGENGINE_H #define OFFLINEMSGENGINE_H #include "src/chatlog/chatmessage.h" #include "src/core/core.h" #include "src/model/message.h" #include "src/persistence/db/rawdatabase.h" #include "src/util/compatiblerecursivemutex.h" #include #include #include #include #include #include class Friend; class ICoreFriendMessageSender; class OfflineMsgEngine : public QObject { Q_OBJECT public: explicit OfflineMsgEngine(Friend* f, ICoreFriendMessageSender* messageSender); using CompletionFn = std::function; void addUnsentMessage(Message const& message, CompletionFn completionCallback); void addSentMessage(ReceiptNum receipt, Message const& message, CompletionFn completionCallback); void deliverOfflineMsgs(); public slots: void removeAllMessages(); void onReceiptReceived(ReceiptNum receipt); private: struct OfflineMessage { Message message; std::chrono::time_point authorshipTime; CompletionFn completionFn; }; private slots: void completeMessage(QMap::iterator msgIt); private: void checkForCompleteMessages(ReceiptNum receipt); CompatibleRecursiveMutex mutex; const Friend* f; ICoreFriendMessageSender* messageSender; QVector receivedReceipts; QMap sentMessages; QVector unsentMessages; }; #endif // OFFLINEMSGENGINE_H qTox/src/persistence/paths.cpp000066400000000000000000000174031415623743500167660ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "paths.h" #include #include #include #include #include #include #include #include namespace { const QLatin1String globalSettingsFile{"qtox.ini"}; const QLatin1String profileFolder{"profiles"}; const QLatin1String themeFolder{"themes"}; const QLatin1String avatarsFolder{"avatars"}; const QLatin1String transfersFolder{"transfers"}; const QLatin1String screenshotsFolder{"screenshots"}; // NOTE(sudden6): currently unused, but reflects the TCS at 2018-11 #ifdef Q_OS_WIN const QLatin1String TCSToxFileFolder{"%APPDATA%/tox/"}; #elif defined(Q_OS_OSX) const QLatin1String TCSToxFileFolder{"~/Library/Application Support/Tox"}; #else const QLatin1String TCSToxFileFolder{"~/.config/tox/"}; #endif } // namespace /** * @class Profile * @brief Handles all qTox internal paths * * The qTox internal file layout starts at ``. This directory is platform * specific and depends on if qTox runs in portable mode. * * Example file layout for non-portable mode: * @code * /themes/ * /profiles/ * /profiles/avatars/ * /... * @endcode * * Example file layout for portable mode: * @code * /qTox.bin * /themes/ * /profiles/ * /profiles/avatars/ * /qtox.ini * @endcode * * All qTox or Tox specific directories should be looked up through this module. */ /** * @brief Paths::makePaths Factory method for the Paths object * @param mode * @return Pointer to Paths object on success, nullptr else */ Paths* Paths::makePaths(Portable mode) { bool portable = false; const QString basePortable = qApp->applicationDirPath(); const QString baseNonPortable = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); const QString portableSettingsPath = basePortable % QDir::separator() % globalSettingsFile; switch (mode) { case Portable::Portable: qDebug() << "Forcing portable"; portable = true; break; case Portable::NonPortable: qDebug() << "Forcing non-portable"; portable = false; break; case Portable::Auto: // auto detect if (QFile{portableSettingsPath}.exists()) { qDebug() << "Automatic portable"; portable = true; } else { qDebug() << "Automatic non-portable"; portable = false; } break; } QString basePath = portable ? basePortable : baseNonPortable; if (basePath.isEmpty()) { qCritical() << "Couldn't find writeable path"; return nullptr; } return new Paths(basePath, portable); } Paths::Paths(const QString& basePath, bool portable) : basePath{basePath} , portable{portable} {} /** * @brief Check if qTox is running in portable mode. * @return True if running in portable mode, false else. */ bool Paths::isPortable() const { return portable; } /** * @brief Returns the path to the global settings file "qtox.ini" * @return The path to the folder. */ QString Paths::getGlobalSettingsPath() const { QString path; if (portable) { path = basePath; } else { path = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); if (path.isEmpty()) { qDebug() << "Can't find writable location for settings file"; return {}; } } // we assume a writeable path for portable mode return path % QDir::separator() % globalSettingsFile; } /** * @brief Get the folder where profile specific information is stored, e.g. .ini * @return The path to the folder. */ QString Paths::getProfilesDir() const { return basePath % QDir::separator() % profileFolder % QDir::separator(); } /** * @brief Get the folder where the .tox file is stored * @note Expect a change here, since TCS will probably be updated. * @return The path to the folder on success, empty string else. */ QString Paths::getToxSaveDir() const { if (isPortable()) { return basePath % QDir::separator() % profileFolder % QDir::separator(); } // GenericDataLocation would be a better solution, but we keep this code for backward // compatibility // workaround for https://bugreports.qt-project.org/browse/QTBUG-38845 #ifdef Q_OS_WIN // TODO(sudden6): this doesn't really follow the Tox Client Standard and probably // breaks when %APPDATA% is changed return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "AppData" + QDir::separator() + "Roaming" + QDir::separator() + "tox") + QDir::separator(); #elif defined(Q_OS_OSX) return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "Library" + QDir::separator() + "Application Support" + QDir::separator() + "Tox") + QDir::separator(); #else // TODO(sudden6): This does not respect the XDG_* environment variables and also // stores user data in a config location return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QDir::separator() + "tox") + QDir::separator(); #endif } /** * @brief Get the folder where avatar files are stored * @note Expect a change here, since TCS will probably be updated. * @return The path to the folder on success, empty string else. */ QString Paths::getAvatarsDir() const { // follow the layout in // https://tox.gitbooks.io/tox-client-standard/content/data_storage/export_format.html QString path = getToxSaveDir(); if (path.isEmpty()) { qDebug() << "Can't find location for avatars directory"; return {}; } return path % avatarsFolder % QDir::separator(); } /** * @brief Get the folder where screenshots are stored * @return The path to the folder. */ QString Paths::getScreenshotsDir() const { return basePath % QDir::separator() % screenshotsFolder % QDir::separator(); } /** * @brief Get the folder where file transfer data is stored * @return The path to the folder. */ QString Paths::getTransfersDir() const { return basePath % QDir::separator() % transfersFolder % QDir::separator(); } /** * @brief Get a prioritized list with directories that contain themes. * @return A list of directories sorted from most important to least important. * @note Users of this function should use the theme from the folder that appears first in the list. */ QStringList Paths::getThemeDirs() const { QStringList themeFolders{}; if (!isPortable()) { themeFolders += QStandardPaths::locate(QStandardPaths::AppDataLocation, themeFolder, QStandardPaths::LocateDirectory); } // look for themes beside the qTox binary with lowest priority const QString curPath = qApp->applicationDirPath(); themeFolders += curPath % QDir::separator() % themeFolder % QDir::separator(); return themeFolders; } qTox/src/persistence/paths.h000066400000000000000000000030431415623743500164260ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef PATHS_H #define PATHS_H #include #include class Paths { public: enum class Portable { Auto, /** Auto detect if portable or non-portable */ Portable, /** Force portable mode */ NonPortable /** Force non-portable mode */ }; static Paths* makePaths(Portable mode = Portable::Auto); bool isPortable() const; QString getGlobalSettingsPath() const; QString getProfilesDir() const; QString getToxSaveDir() const; QString getAvatarsDir() const; QString getTransfersDir() const; QStringList getThemeDirs() const; QString getScreenshotsDir() const; private: Paths(const QString &basePath, bool portable); private: QString basePath{}; bool portable = false; }; #endif // PATHS_H qTox/src/persistence/profile.cpp000066400000000000000000000616321415623743500173120ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #include #include #include #include #include #include #include #include "profile.h" #include "profilelocker.h" #include "settings.h" #include "src/core/core.h" #include "src/core/coreav.h" #include "src/core/corefile.h" #include "src/net/avatarbroadcaster.h" #include "src/nexus.h" #include "src/widget/gui.h" #include "src/widget/tool/identicon.h" #include "src/widget/widget.h" /** * @class Profile * @brief Manages user profiles. * * @var bool Profile::newProfile * @brief True if this is a newly created profile, with no .tox save file yet. * * @var bool Profile::isRemoved * @brief True if the profile has been removed by remove(). */ QStringList Profile::profiles; void Profile::initCore(const QByteArray& toxsave, ICoreSettings& s, bool isNewProfile) { if (toxsave.isEmpty() && !isNewProfile) { qCritical() << "Existing toxsave is empty"; emit failedToStart(); } if (!toxsave.isEmpty() && isNewProfile) { qCritical() << "New profile has toxsave data"; emit failedToStart(); } Core::ToxCoreErrors err; core = Core::makeToxCore(toxsave, &s, &err); if (!core) { switch (err) { case Core::ToxCoreErrors::BAD_PROXY: emit badProxy(); break; case Core::ToxCoreErrors::ERROR_ALLOC: case Core::ToxCoreErrors::FAILED_TO_START: case Core::ToxCoreErrors::INVALID_SAVE: default: emit failedToStart(); } qDebug() << "failed to start ToxCore"; return; } if (isNewProfile) { core->setStatusMessage(tr("Toxing on qTox")); core->setUsername(name); } // save tox file when Core requests it connect(core.get(), &Core::saveRequest, this, &Profile::onSaveToxSave); // react to avatar changes connect(core.get(), &Core::friendAvatarRemoved, this, &Profile::removeAvatar); connect(core.get(), &Core::friendAvatarChanged, this, &Profile::setFriendAvatar); connect(core.get(), &Core::fileAvatarOfferReceived, this, &Profile::onAvatarOfferReceived, Qt::ConnectionType::QueuedConnection); } Profile::Profile(QString name, const QString& password, bool isNewProfile, const QByteArray& toxsave, std::unique_ptr passkey) : name{name} , passkey{std::move(passkey)} , isRemoved{false} , encrypted{this->passkey != nullptr} { Settings& s = Settings::getInstance(); // Core settings are saved per profile, need to load them before starting Core s.loadPersonal(name, this->passkey.get()); // TODO(kriby): Move/refactor core initialization to remove settings dependency // note to self: use slots/signals for this? initCore(toxsave, s, isNewProfile); loadDatabase(password); } /** * @brief Locks and loads an existing profile and creates the associate Core* instance. * @param name Profile name. * @param password Profile password. * @return Returns a nullptr on error. Profile pointer otherwise. * * @example If the profile is already in use return nullptr. */ Profile* Profile::loadProfile(QString name, const QCommandLineParser* parser, const QString& password) { if (ProfileLocker::hasLock()) { qCritical() << "Tried to load profile " << name << ", but another profile is already locked!"; return nullptr; } if (!ProfileLocker::lock(name)) { qWarning() << "Failed to lock profile " << name; return nullptr; } std::unique_ptr tmpKey = nullptr; QByteArray data = QByteArray(); Profile* p = nullptr; qint64 fileSize = 0; Settings& s = Settings::getInstance(); QString path = s.getSettingsDirPath() + name + ".tox"; QFile saveFile(path); qDebug() << "Loading tox save " << path; if (!saveFile.exists()) { qWarning() << "The tox save file " << path << " was not found"; goto fail; } if (!saveFile.open(QIODevice::ReadOnly)) { qCritical() << "The tox save file " << path << " couldn't' be opened"; goto fail; } fileSize = saveFile.size(); if (fileSize <= 0) { qWarning() << "The tox save file" << path << " is empty!"; goto fail; } data = saveFile.readAll(); if (ToxEncrypt::isEncrypted(data)) { if (password.isEmpty()) { qCritical() << "The tox save file is encrypted, but we don't have a password!"; goto fail; } tmpKey = ToxEncrypt::makeToxEncrypt(password, data); if (!tmpKey) { qCritical() << "Failed to derive key of the tox save file"; goto fail; } data = tmpKey->decrypt(data); if (data.isEmpty()) { qCritical() << "Failed to decrypt the tox save file"; goto fail; } } else { if (!password.isEmpty()) { qWarning() << "We have a password, but the tox save file is not encrypted"; } } saveFile.close(); p = new Profile(name, password, false, data, std::move(tmpKey)); s.updateProfileData(p, parser); return p; // cleanup in case of error fail: saveFile.close(); ProfileLocker::unlock(); return nullptr; } /** * @brief Creates a new profile and the associated Core* instance. * @param name Username. * @param password If password is not empty, the profile will be encrypted. * @return Returns a nullptr on error. Profile pointer otherwise. * * @note If the profile is already in use return nullptr. */ Profile* Profile::createProfile(QString name, const QCommandLineParser* parser, QString password) { std::unique_ptr tmpKey; if (!password.isEmpty()) { tmpKey = ToxEncrypt::makeToxEncrypt(password); if (!tmpKey) { qCritical() << "Failed to derive key for the tox save"; return nullptr; } } if (ProfileLocker::hasLock()) { qCritical() << "Tried to create profile " << name << ", but another profile is already locked!"; return nullptr; } if (exists(name)) { qCritical() << "Tried to create profile " << name << ", but it already exists!"; return nullptr; } if (!ProfileLocker::lock(name)) { qWarning() << "Failed to lock profile " << name; return nullptr; } Settings::getInstance().createPersonal(name); Profile* p = new Profile(name, password, true, QByteArray(), std::move(tmpKey)); return p; } Profile::~Profile() { if (isRemoved) { return; } onSaveToxSave(); Settings::getInstance().savePersonal(this); Settings::getInstance().sync(); ProfileLocker::assertLock(); assert(ProfileLocker::getCurLockName() == name); ProfileLocker::unlock(); } /** * @brief Lists all the files in the config dir with a given extension * @param extension Raw extension, e.g. "jpeg" not ".jpeg". * @return Vector of filenames. */ QStringList Profile::getFilesByExt(QString extension) { QDir dir(Settings::getInstance().getSettingsDirPath()); QStringList out; dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); dir.setNameFilters(QStringList("*." + extension)); QFileInfoList list = dir.entryInfoList(); out.reserve(list.size()); for (QFileInfo file : list) { out += file.completeBaseName(); } return out; } /** * @brief Scan for profile, automatically importing them if needed. * @warning NOT thread-safe. */ const QStringList Profile::getAllProfileNames() { profiles.clear(); QStringList toxfiles = getFilesByExt("tox"), inifiles = getFilesByExt("ini"); for (const QString& toxfile : toxfiles) { if (!inifiles.contains(toxfile)) { Settings::getInstance().createPersonal(toxfile); } profiles.append(toxfile); } return profiles; } Core* Profile::getCore() { // TODO(sudden6): this is evil return core.get(); } QString Profile::getName() const { return name; } /** * @brief Starts the Core thread */ void Profile::startCore() { // kriby: code duplication belongs in initCore, but cannot yet due to Core/Profile coupling connect(core.get(), &Core::requestSent, this, &Profile::onRequestSent); emit coreChanged(*core); core->start(); const ToxId& selfId = core->getSelfId(); const ToxPk& selfPk = selfId.getPublicKey(); const QByteArray data = loadAvatarData(selfPk); if (data.isEmpty()) { qDebug() << "Self avatar not found, will broadcast empty avatar to friends"; } // TODO(sudden6): moved here, because it crashes in the constructor // reason: Core::getInstance() returns nullptr, because it's not yet initialized // solution: kill Core::getInstance setAvatar(data); } /** * @brief Saves the profile's .tox save, encrypted if needed. * @warning Invalid on deleted profiles. */ void Profile::onSaveToxSave() { QByteArray data = core->getToxSaveData(); assert(data.size()); saveToxSave(data); } // TODO(sudden6): handle this better maybe? void Profile::onAvatarOfferReceived(uint32_t friendId, uint32_t fileId, const QByteArray& avatarHash) { // accept if we don't have it already const bool accept = getAvatarHash(core->getFriendPublicKey(friendId)) != avatarHash; core->getCoreFile()->handleAvatarOffer(friendId, fileId, accept); } /** * @brief Write the .tox save, encrypted if needed. * @param data Byte array of profile save. * @return true if successfully saved, false otherwise * @warning Invalid on deleted profiles. */ bool Profile::saveToxSave(QByteArray data) { assert(!isRemoved); ProfileLocker::assertLock(); assert(ProfileLocker::getCurLockName() == name); QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox"; qDebug() << "Saving tox save to " << path; QSaveFile saveFile(path); if (!saveFile.open(QIODevice::WriteOnly)) { qCritical() << "Tox save file " << path << " couldn't be opened"; return false; } if (encrypted) { data = passkey->encrypt(data); if (data.isEmpty()) { qCritical() << "Failed to encrypt, can't save!"; saveFile.cancelWriting(); return false; } } saveFile.write(data); // check if everything got written if (saveFile.flush()) { saveFile.commit(); } else { saveFile.cancelWriting(); qCritical() << "Failed to write, can't save!"; return false; } return true; } /** * @brief Gets the path of the avatar file cached by this profile and corresponding to this owner * ID. * @param owner Path to avatar of friend with this PK will returned. * @param forceUnencrypted If true, return the path to the plaintext file even if this is an * encrypted profile. * @return Path to the avatar. */ QString Profile::avatarPath(const ToxPk& owner, bool forceUnencrypted) { const QString ownerStr = owner.toString(); if (!encrypted || forceUnencrypted) { return Settings::getInstance().getSettingsDirPath() + "avatars/" + ownerStr + ".png"; } QByteArray idData = ownerStr.toUtf8(); QByteArray pubkeyData = core->getSelfId().getPublicKey().getByteArray(); constexpr int hashSize = TOX_PUBLIC_KEY_SIZE; static_assert(hashSize >= crypto_generichash_BYTES_MIN && hashSize <= crypto_generichash_BYTES_MAX, "Hash size not supported by libsodium"); static_assert(hashSize >= crypto_generichash_KEYBYTES_MIN && hashSize <= crypto_generichash_KEYBYTES_MAX, "Key size not supported by libsodium"); QByteArray hash(hashSize, 0); crypto_generichash((uint8_t*)hash.data(), hashSize, (uint8_t*)idData.data(), idData.size(), (uint8_t*)pubkeyData.data(), pubkeyData.size()); return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() + ".png"; } /** * @brief Get our avatar from cache. * @return Avatar as QPixmap. */ QPixmap Profile::loadAvatar() { return loadAvatar(core->getSelfId().getPublicKey()); } /** * @brief Get a contact's avatar from cache. * @param owner Friend PK to load avatar. * @return Avatar as QPixmap. */ QPixmap Profile::loadAvatar(const ToxPk& owner) { QPixmap pic; if (Settings::getInstance().getShowIdenticons()) { const QByteArray avatarData = loadAvatarData(owner); if (avatarData.isEmpty()) { pic = QPixmap::fromImage(Identicon(owner.getByteArray()).toImage(16)); } else { pic.loadFromData(avatarData); } } else { pic.loadFromData(loadAvatarData(owner)); } return pic; } /** * @brief Get a contact's avatar from cache. * @param owner Friend PK to load avatar. * @return Avatar as QByteArray. */ QByteArray Profile::loadAvatarData(const ToxPk& owner) { QString path = avatarPath(owner); bool avatarEncrypted = encrypted; // If the encrypted avatar isn't found, try loading the unencrypted one for the same ID if (avatarEncrypted && !QFile::exists(path)) { avatarEncrypted = false; path = avatarPath(owner, true); } QFile file(path); if (!file.open(QIODevice::ReadOnly)) { return {}; } QByteArray pic = file.readAll(); if (avatarEncrypted && !pic.isEmpty()) { pic = passkey->decrypt(pic); if (pic.isEmpty()) { qWarning() << "Failed to decrypt avatar at" << path; } } return pic; } void Profile::loadDatabase(QString password) { assert(core); if (isRemoved) { qDebug() << "Can't load database of removed profile"; return; } QByteArray salt = core->getSelfId().getPublicKey().getByteArray(); if (salt.size() != TOX_PASS_SALT_LENGTH) { qWarning() << "Couldn't compute salt from public key" << name; GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled.")); } // At this point it's too early to load the personal settings (Nexus will do it), so we always // load // the history, and if it fails we can't change the setting now, but we keep a nullptr database = std::make_shared(getDbPath(name), password, salt); if (database && database->isOpen()) { history.reset(new History(database)); } else { qWarning() << "Failed to open database for profile" << name; GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled.")); } } /** * @brief Sets our own avatar * @param pic Picture to use as avatar, if empty an Identicon will be used depending on settings */ void Profile::setAvatar(QByteArray pic) { QPixmap pixmap; QByteArray avatarData; const ToxPk& selfPk = core->getSelfPublicKey(); if (!pic.isEmpty()) { pixmap.loadFromData(pic); avatarData = pic; } else { if (Settings::getInstance().getShowIdenticons()) { const QImage identicon = Identicon(selfPk.getByteArray()).toImage(32); pixmap = QPixmap::fromImage(identicon); } else { pixmap.load(":/img/contact_dark.svg"); } } saveAvatar(selfPk, avatarData); emit selfAvatarChanged(pixmap); AvatarBroadcaster::setAvatar(avatarData); AvatarBroadcaster::enableAutoBroadcast(); } /** * @brief Sets a friends avatar * @param pic Picture to use as avatar, if empty an Identicon will be used depending on settings * @param owner pk of friend */ void Profile::setFriendAvatar(const ToxPk& owner, QByteArray pic) { QPixmap pixmap; QByteArray avatarData; if (!pic.isEmpty()) { pixmap.loadFromData(pic); avatarData = pic; emit friendAvatarSet(owner, pixmap); } else if (Settings::getInstance().getShowIdenticons()) { const QImage identicon = Identicon(owner.getByteArray()).toImage(32); pixmap = QPixmap::fromImage(identicon); emit friendAvatarSet(owner, pixmap); } else { pixmap.load(":/img/contact_dark.svg"); emit friendAvatarRemoved(owner); } friendAvatarChanged(owner, pixmap); saveAvatar(owner, avatarData); } /** * @brief Adds history message about friendship request attempt if history is enabled * @param friendPk Pk of a friend which request is destined to * @param message Friendship request message */ void Profile::onRequestSent(const ToxPk& friendPk, const QString& message) { if (!isHistoryEnabled()) { return; } const QString pkStr = friendPk.toString(); const QString inviteStr = Core::tr("/me offers friendship, \"%1\"").arg(message); const QString selfStr = core->getSelfPublicKey().toString(); const QDateTime datetime = QDateTime::currentDateTime(); const QString name = core->getUsername(); history->addNewMessage(pkStr, inviteStr, selfStr, datetime, true, name); } /** * @brief Save an avatar to cache. * @param pic Picture to save. * @param owner PK of avatar owner. */ void Profile::saveAvatar(const ToxPk& owner, const QByteArray& avatar) { const bool needEncrypt = encrypted && !avatar.isEmpty(); const QByteArray& pic = needEncrypt ? passkey->encrypt(avatar) : avatar; QString path = avatarPath(owner); QDir(Settings::getInstance().getSettingsDirPath()).mkdir("avatars"); if (pic.isEmpty()) { QFile::remove(path); } else { QSaveFile file(path); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "Tox avatar " << path << " couldn't be saved"; return; } file.write(pic); file.commit(); } } /** * @brief Get the tox hash of a cached avatar. * @param owner Friend PK to get hash. * @return Avatar tox hash. */ QByteArray Profile::getAvatarHash(const ToxPk& owner) { QByteArray pic = loadAvatarData(owner); QByteArray avatarHash(TOX_HASH_LENGTH, 0); tox_hash((uint8_t*)avatarHash.data(), (uint8_t*)pic.data(), pic.size()); return avatarHash; } /** * @brief Removes our own avatar. */ void Profile::removeSelfAvatar() { removeAvatar(core->getSelfId().getPublicKey()); } /** * @brief Removes friend avatar. */ void Profile::removeFriendAvatar(const ToxPk& owner) { removeAvatar(owner); } /** * @brief Checks that the history is enabled in the settings, and loaded successfully for this * profile. * @return True if enabled, false otherwise. */ bool Profile::isHistoryEnabled() { return Settings::getInstance().getEnableLogging() && history; } /** * @brief Get chat history. * @return May return a nullptr if the history failed to load. */ History* Profile::getHistory() { return history.get(); } /** * @brief Removes a cached avatar. * @param owner Friend PK whose avater to delete. */ void Profile::removeAvatar(const ToxPk& owner) { QFile::remove(avatarPath(owner)); if (owner == core->getSelfId().getPublicKey()) { setAvatar({}); } else { setFriendAvatar(owner, {}); } } bool Profile::exists(QString name) { QString path = Settings::getInstance().getSettingsDirPath() + name; return QFile::exists(path + ".tox"); } /** * @brief Checks, if profile has a password. * @return True if we have a password set (doesn't check the actual file on disk). */ bool Profile::isEncrypted() const { return encrypted; } /** * @brief Checks if profile is encrypted. * @note Checks the actual file on disk. * @param name Profile name. * @return True if profile is encrypted, false otherwise. */ bool Profile::isEncrypted(QString name) { uint8_t data[TOX_PASS_ENCRYPTION_EXTRA_LENGTH] = {0}; QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox"; QFile saveFile(path); if (!saveFile.open(QIODevice::ReadOnly)) { qWarning() << "Couldn't open tox save " << path; return false; } saveFile.read((char*)data, TOX_PASS_ENCRYPTION_EXTRA_LENGTH); saveFile.close(); return tox_is_data_encrypted(data); } /** * @brief Removes the profile permanently. * Updates the profiles vector. * @return Vector of filenames that could not be removed. * @warning It is invalid to call loadToxSave or saveToxSave on a deleted profile. */ QStringList Profile::remove() { if (isRemoved) { qWarning() << "Profile " << name << " is already removed!"; return {}; } isRemoved = true; qDebug() << "Removing profile" << name; for (int i = 0; i < profiles.size(); ++i) { if (profiles[i] == name) { profiles.removeAt(i); i--; } } QString path = Settings::getInstance().getSettingsDirPath() + name; ProfileLocker::unlock(); QFile profileMain{path + ".tox"}; QFile profileConfig{path + ".ini"}; QStringList ret; if (!profileMain.remove() && profileMain.exists()) { ret.push_back(profileMain.fileName()); qWarning() << "Could not remove file " << profileMain.fileName(); } if (!profileConfig.remove() && profileConfig.exists()) { ret.push_back(profileConfig.fileName()); qWarning() << "Could not remove file " << profileConfig.fileName(); } QString dbPath = getDbPath(name); if (database && database->isOpen() && !database->remove() && QFile::exists(dbPath)) { ret.push_back(dbPath); qWarning() << "Could not remove file " << dbPath; } history.reset(); database.reset(); return ret; } /** * @brief Tries to rename the profile. * @param newName New name for the profile. * @return False on error, true otherwise. */ bool Profile::rename(QString newName) { QString path = Settings::getInstance().getSettingsDirPath() + name, newPath = Settings::getInstance().getSettingsDirPath() + newName; if (!ProfileLocker::lock(newName)) { return false; } QFile::rename(path + ".tox", newPath + ".tox"); QFile::rename(path + ".ini", newPath + ".ini"); if (database) { database->rename(newName); } bool resetAutorun = Settings::getInstance().getAutorun(); Settings::getInstance().setAutorun(false); Settings::getInstance().setCurrentProfile(newName); if (resetAutorun) { Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line } name = newName; return true; } const ToxEncrypt* Profile::getPasskey() const { return passkey.get(); } /** * @brief Changes the encryption password and re-saves everything with it * @param newPassword Password for encryption, if empty profile will be decrypted. * @param oldPassword Supply previous password if already encrypted or empty QString if not yet * encrypted. * @return Empty QString on success or error message on failure. */ QString Profile::setPassword(const QString& newPassword) { if (newPassword.isEmpty()) { // remove password encrypted = false; } else { std::unique_ptr newpasskey = ToxEncrypt::makeToxEncrypt(newPassword); if (!newpasskey) { qCritical() << "Failed to derive key from password, the profile won't use the new password"; return tr( "Failed to derive key from password, the profile won't use the new password."); } // apply change passkey = std::move(newpasskey); encrypted = true; } // apply new encryption onSaveToxSave(); bool dbSuccess = false; // TODO: ensure the database and the tox save file use the same password if (database) { dbSuccess = database->setPassword(newPassword); } QString error{}; if (!dbSuccess) { error = tr("Couldn't change password on the database, it might be corrupted or use the old " "password."); } QByteArray avatar = loadAvatarData(core->getSelfId().getPublicKey()); saveAvatar(core->getSelfId().getPublicKey(), avatar); QVector friendList = core->getFriendList(); QVectorIterator i(friendList); while (i.hasNext()) { const ToxPk friendPublicKey = core->getFriendPublicKey(i.next()); saveAvatar(friendPublicKey, loadAvatarData(friendPublicKey)); } return error; } /** * @brief Retrieves the path to the database file for a given profile. * @param profileName Profile name. * @return Path to database. */ QString Profile::getDbPath(const QString& profileName) { return Settings::getInstance().getSettingsDirPath() + profileName + ".db"; } qTox/src/persistence/profile.h000066400000000000000000000100341415623743500167450ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef PROFILE_H #define PROFILE_H #include "src/core/core.h" #include "src/core/toxencrypt.h" #include "src/core/toxid.h" #include "src/persistence/history.h" #include #include #include #include #include #include class Settings; class QCommandLineParser; class Profile : public QObject { Q_OBJECT public: static Profile* loadProfile(QString name, const QCommandLineParser* parser, const QString& password = QString()); static Profile* createProfile(QString name, const QCommandLineParser* parser, QString password); ~Profile(); Core* getCore(); QString getName() const; void startCore(); bool isEncrypted() const; QString setPassword(const QString& newPassword); const ToxEncrypt* getPasskey() const; QPixmap loadAvatar(); QPixmap loadAvatar(const ToxPk& owner); QByteArray loadAvatarData(const ToxPk& owner); void setAvatar(QByteArray pic); void setFriendAvatar(const ToxPk& owner, QByteArray pic); QByteArray getAvatarHash(const ToxPk& owner); void removeSelfAvatar(); void removeFriendAvatar(const ToxPk& owner); bool isHistoryEnabled(); History* getHistory(); QStringList remove(); bool rename(QString newName); static const QStringList getAllProfileNames(); static bool exists(QString name); static bool isEncrypted(QString name); static QString getDbPath(const QString& profileName); signals: void selfAvatarChanged(const QPixmap& pixmap); // emit on any change, including default avatar. Used by those that don't care about active on default avatar. void friendAvatarChanged(const ToxPk& friendPk, const QPixmap& pixmap); // emit on a set of avatar, including identicon, used by those two care about active for default, so can't use friendAvatarChanged void friendAvatarSet(const ToxPk& friendPk, const QPixmap& pixmap); // emit on set to default, used by those that modify on active void friendAvatarRemoved(const ToxPk& friendPk); // TODO(sudden6): this doesn't seem to be the right place for Core errors void failedToStart(); void badProxy(); void coreChanged(Core& core); public slots: void onRequestSent(const ToxPk& friendPk, const QString& message); private slots: void loadDatabase(QString password); void saveAvatar(const ToxPk& owner, const QByteArray& avatar); void removeAvatar(const ToxPk& owner); void onSaveToxSave(); // TODO(sudden6): use ToxPk instead of friendId void onAvatarOfferReceived(uint32_t friendId, uint32_t fileId, const QByteArray& avatarHash); private: Profile(QString name, const QString& password, bool newProfile, const QByteArray& toxsave, std::unique_ptr passKey); static QStringList getFilesByExt(QString extension); QString avatarPath(const ToxPk& owner, bool forceUnencrypted = false); bool saveToxSave(QByteArray data); void initCore(const QByteArray& toxsave, ICoreSettings& s, bool isNewProfile); private: std::unique_ptr core = nullptr; QString name; std::unique_ptr passkey = nullptr; std::shared_ptr database; std::shared_ptr history; bool isRemoved; bool encrypted = false; static QStringList profiles; }; #endif // PROFILE_H qTox/src/persistence/profilelocker.cpp000066400000000000000000000101111415623743500204740ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "profilelocker.h" #include "src/persistence/settings.h" #include #include /** * @class ProfileLocker * @brief Locks a Tox profile so that multiple instances can not use the same profile. * Only one lock can be acquired at the same time, which means * that there is little need for manually unlocking. * The current lock will expire if you exit or acquire a new one. */ using namespace std; unique_ptr ProfileLocker::lockfile; QString ProfileLocker::curLockName; QString ProfileLocker::lockPathFromName(const QString& name) { return Settings::getInstance().getSettingsDirPath() + '/' + name + ".lock"; } /** * @brief Checks if a profile is currently locked by *another* instance. * If we own the lock, we consider it lockable. * There is no guarantee that the result will still be valid by the * time it is returned, this is provided on a best effort basis. * @param profile Profile name to check. * @return True, if profile locked, false otherwise. */ bool ProfileLocker::isLockable(QString profile) { // If we already have the lock, it's definitely lockable if (lockfile && curLockName == profile) return true; QLockFile newLock(lockPathFromName(profile)); return newLock.tryLock(); } /** * @brief Tries to acquire the lock on a profile, will not block. * @param profile Profile to lock. * @return Returns true if we already own the lock. */ bool ProfileLocker::lock(QString profile) { if (lockfile && curLockName == profile) return true; QLockFile* newLock = new QLockFile(lockPathFromName(profile)); newLock->setStaleLockTime(0); if (!newLock->tryLock()) { delete newLock; return false; } unlock(); lockfile.reset(newLock); curLockName = profile; return true; } /** * @brief Releases the lock on the current profile. */ void ProfileLocker::unlock() { if (!lockfile) return; lockfile->unlock(); lockfile.reset(); curLockName.clear(); } /** * @brief Check that we actually own the lock. * In case the file was deleted on disk, restore it. * If we can't get a lock, exit qTox immediately. * If we never had a lock in the first place, exit immediately. */ void ProfileLocker::assertLock() { if (!lockfile) { qCritical() << "assertLock: We don't seem to own any lock!"; deathByBrokenLock(); } if (!QFile(lockPathFromName(curLockName)).exists()) { QString tmp = curLockName; unlock(); if (lock(tmp)) { qCritical() << "assertLock: Lock file was lost, but could be restored"; } else { qCritical() << "assertLock: Lock file was lost, and could *NOT* be restored"; deathByBrokenLock(); } } } /** * @brief Print an error then exit immediately. */ void ProfileLocker::deathByBrokenLock() { qCritical() << "Lock is *BROKEN*, exiting immediately"; abort(); } /** * @brief Chacks, that profile locked. * @return Returns true if we're currently holding a lock. */ bool ProfileLocker::hasLock() { return lockfile.operator bool(); } /** * @brief Get current locked profile name. * @return Return the name of the currently loaded profile, a null string if there is none. */ QString ProfileLocker::getCurLockName() { if (lockfile) return curLockName; else return QString(); } qTox/src/persistence/profilelocker.h000066400000000000000000000025161415623743500201530ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef PROFILELOCKER_H #define PROFILELOCKER_H #include #include class ProfileLocker { private: ProfileLocker() = delete; public: static bool isLockable(QString profile); static bool lock(QString profile); static void unlock(); static bool hasLock(); static QString getCurLockName(); static void assertLock(); private: static QString lockPathFromName(const QString& name); static void deathByBrokenLock(); private: static std::unique_ptr lockfile; static QString curLockName; }; #endif // PROFILELOCKER_H qTox/src/persistence/serialize.cpp000066400000000000000000000062531415623743500176370ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/persistence/serialize.h" /** * @file serialize.cpp * Most of functions in this file are unsafe unless otherwise specified. * @warning Do not use them on untrusted data (e.g. check a signature first). */ QString dataToString(QByteArray data) { char num3; int strlen = 0; int num2 = 0; int i = 0; do { num3 = data[i++]; strlen |= (num3 & 0x7f) << num2; num2 += 7; } while ((num3 & 0x80) != 0); if (strlen <= 0) return QString(); // Remove the strlen data.remove(0, i); data.truncate(strlen); return QString(data); } uint64_t dataToUint64(const QByteArray& data) { return static_cast(data[0]) | (static_cast(data[1]) << 8) | (static_cast(data[2]) << 16) | (static_cast(data[3]) << 24) | (static_cast(data[4]) << 32) | (static_cast(data[5]) << 40) | (static_cast(data[6]) << 48) | (static_cast(data[7]) << 56); } int dataToVInt(const QByteArray& data) { char num3; int num = 0; int num2 = 0; int i = 0; do { num3 = data[i++]; num |= static_cast(num3 & 0x7f) << num2; num2 += 7; } while ((num3 & 0x80) != 0); return num; } size_t dataToVUint(const QByteArray& data) { char num3; size_t num = 0; int num2 = 0; int i = 0; do { num3 = data[i++]; num |= static_cast(num3 & 0x7f) << num2; num2 += 7; } while ((num3 & 0x80) != 0); return num; } unsigned getVUint32Size(QByteArray data) { unsigned lensize = 0; char num3; do { num3 = data[lensize]; ++lensize; } while ((num3 & 0x80) != 0); return lensize; } QByteArray vintToData(int num) { QByteArray data(sizeof(int), 0); // Write the size in a Uint of variable lenght (8-32 bits) int i = 0; while (num >= 0x80) { data[i] = static_cast(num | 0x80); ++i; num = num >> 7; } data[i] = static_cast(num); data.resize(i + 1); return data; } QByteArray vuintToData(size_t num) { QByteArray data(sizeof(size_t), 0); // Write the size in a Uint of variable lenght (8-32 bits) int i = 0; while (num >= 0x80) { data[i] = static_cast(num | 0x80); ++i; num = num >> 7; } data[i] = static_cast(num); data.resize(i + 1); return data; } qTox/src/persistence/serialize.h000066400000000000000000000022271415623743500173010ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SERIALIZE_H #define SERIALIZE_H #include #include #include QString dataToString(QByteArray data); uint64_t dataToUint64(const QByteArray& data); int dataToVInt(const QByteArray& data); size_t dataToVUint(const QByteArray& data); unsigned getVUint32Size(QByteArray data); QByteArray vintToData(int num); QByteArray vuintToData(size_t num); #endif // SERIALIZE_H qTox/src/persistence/settings.cpp000066400000000000000000002014461415623743500175110ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "settings.h" #include "src/core/core.h" #include "src/core/corefile.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/persistence/profilelocker.h" #include "src/persistence/settingsserializer.h" #include "src/persistence/smileypack.h" #include "src/widget/gui.h" #include "src/widget/style.h" #ifdef QTOX_PLATFORM_EXT #include "src/platform/autorun.h" #endif #include "src/ipc.h" #include "src/util/compatiblerecursivemutex.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /** * @var QHash Settings::widgetSettings * @brief Assume all widgets have unique names * @warning Don't use it to save every single thing you want to save, use it * for some general purpose widgets, such as MainWindows or Splitters, * which have widget->saveX() and widget->loadX() methods. */ const QString Settings::globalSettingsFile = "qtox.ini"; Settings* Settings::settings{nullptr}; CompatibleRecursiveMutex Settings::bigLock; QThread* Settings::settingsThread{nullptr}; Settings::Settings() : loaded(false) , useCustomDhtList{false} , makeToxPortable{false} , currentProfileId(0) { settingsThread = new QThread(); settingsThread->setObjectName("qTox Settings"); settingsThread->start(QThread::LowPriority); moveToThread(settingsThread); loadGlobal(); } Settings::~Settings() { sync(); settingsThread->exit(0); settingsThread->wait(); delete settingsThread; } /** * @brief Returns the singleton instance. */ Settings& Settings::getInstance() { if (!settings) settings = new Settings(); return *settings; } void Settings::destroyInstance() { delete settings; settings = nullptr; } void Settings::loadGlobal() { QMutexLocker locker{&bigLock}; if (loaded) return; createSettingsDir(); makeToxPortable = Settings::isToxPortable(); QDir dir(getSettingsDirPath()); QString filePath = dir.filePath(globalSettingsFile); // If no settings file exist -- use the default one if (!QFile(filePath).exists()) { qDebug() << "No settings file found, using defaults"; filePath = ":/conf/" + globalSettingsFile; } qDebug() << "Loading settings from " + filePath; QSettings s(filePath, QSettings::IniFormat); s.setIniCodec("UTF-8"); s.beginGroup("Login"); { autoLogin = s.value("autoLogin", false).toBool(); } s.endGroup(); s.beginGroup("General"); { translation = s.value("translation", "en").toString(); showSystemTray = s.value("showSystemTray", true).toBool(); autostartInTray = s.value("autostartInTray", false).toBool(); closeToTray = s.value("closeToTray", false).toBool(); if (currentProfile.isEmpty()) { currentProfile = s.value("currentProfile", "").toString(); currentProfileId = makeProfileId(currentProfile); } autoAwayTime = s.value("autoAwayTime", 10).toInt(); checkUpdates = s.value("checkUpdates", true).toBool(); // note: notifySound and busySound UI elements are now under UI settings // page, but kept under General in settings file to be backwards compatible notifySound = s.value("notifySound", true).toBool(); notifyHide = s.value("notifyHide", false).toBool(); busySound = s.value("busySound", false).toBool(); autoSaveEnabled = s.value("autoSaveEnabled", false).toBool(); globalAutoAcceptDir = s.value("globalAutoAcceptDir", QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory)) .toString(); autoAcceptMaxSize = static_cast(s.value("autoAcceptMaxSize", 20 << 20 /*20 MB*/).toLongLong()); stylePreference = static_cast(s.value("stylePreference", 1).toInt()); } s.endGroup(); s.beginGroup("Advanced"); { makeToxPortable = s.value("makeToxPortable", false).toBool(); enableIPv6 = s.value("enableIPv6", true).toBool(); forceTCP = s.value("forceTCP", false).toBool(); enableLanDiscovery = s.value("enableLanDiscovery", true).toBool(); } s.endGroup(); s.beginGroup("Widgets"); { QList objectNames = s.childKeys(); for (const QString& name : objectNames) widgetSettings[name] = s.value(name).toByteArray(); } s.endGroup(); s.beginGroup("GUI"); { showWindow = s.value("showWindow", true).toBool(); notify = s.value("notify", true).toBool(); desktopNotify = s.value("desktopNotify", true).toBool(); groupAlwaysNotify = s.value("groupAlwaysNotify", true).toBool(); groupchatPosition = s.value("groupchatPosition", true).toBool(); separateWindow = s.value("separateWindow", false).toBool(); dontGroupWindows = s.value("dontGroupWindows", false).toBool(); showIdenticons = s.value("showIdenticons", true).toBool(); const QString DEFAULT_SMILEYS = ":/smileys/emojione/emoticons.xml"; smileyPack = s.value("smileyPack", DEFAULT_SMILEYS).toString(); if (!QFile::exists(smileyPack)) { smileyPack = DEFAULT_SMILEYS; } emojiFontPointSize = s.value("emojiFontPointSize", 24).toInt(); firstColumnHandlePos = s.value("firstColumnHandlePos", 50).toInt(); secondColumnHandlePosFromRight = s.value("secondColumnHandlePosFromRight", 50).toInt(); timestampFormat = s.value("timestampFormat", "hh:mm:ss").toString(); dateFormat = s.value("dateFormat", "yyyy-MM-dd").toString(); minimizeOnClose = s.value("minimizeOnClose", false).toBool(); minimizeToTray = s.value("minimizeToTray", false).toBool(); lightTrayIcon = s.value("lightTrayIcon", false).toBool(); useEmoticons = s.value("useEmoticons", true).toBool(); statusChangeNotificationEnabled = s.value("statusChangeNotificationEnabled", false).toBool(); spellCheckingEnabled = s.value("spellCheckingEnabled", true).toBool(); themeColor = s.value("themeColor", 0).toInt(); style = s.value("style", "").toString(); if (style == "") // Default to Fusion if available, otherwise no style { if (QStyleFactory::keys().contains("Fusion")) style = "Fusion"; else style = "None"; } nameColors = s.value("nameColors", false).toBool(); } s.endGroup(); s.beginGroup("Chat"); { chatMessageFont = s.value("chatMessageFont", Style::getFont(Style::Big)).value(); } s.endGroup(); s.beginGroup("State"); { windowGeometry = s.value("windowGeometry", QByteArray()).toByteArray(); windowState = s.value("windowState", QByteArray()).toByteArray(); splitterState = s.value("splitterState", QByteArray()).toByteArray(); dialogGeometry = s.value("dialogGeometry", QByteArray()).toByteArray(); dialogSplitterState = s.value("dialogSplitterState", QByteArray()).toByteArray(); dialogSettingsGeometry = s.value("dialogSettingsGeometry", QByteArray()).toByteArray(); } s.endGroup(); s.beginGroup("Audio"); { inDev = s.value("inDev", "").toString(); audioInDevEnabled = s.value("audioInDevEnabled", true).toBool(); outDev = s.value("outDev", "").toString(); audioOutDevEnabled = s.value("audioOutDevEnabled", true).toBool(); audioInGainDecibel = s.value("inGain", 0).toReal(); audioThreshold = s.value("audioThreshold", 0).toReal(); outVolume = s.value("outVolume", 100).toInt(); enableTestSound = s.value("enableTestSound", true).toBool(); audioBitrate = s.value("audioBitrate", 64).toInt(); } s.endGroup(); s.beginGroup("Video"); { videoDev = s.value("videoDev", "").toString(); camVideoRes = s.value("camVideoRes", QRect()).toRect(); screenRegion = s.value("screenRegion", QRect()).toRect(); screenGrabbed = s.value("screenGrabbed", false).toBool(); camVideoFPS = static_cast(s.value("camVideoFPS", 0).toUInt()); } s.endGroup(); loaded = true; } bool Settings::isToxPortable() { QString localSettingsPath = qApp->applicationDirPath() + QDir::separator() + globalSettingsFile; if (!QFile(localSettingsPath).exists()) { return false; } QSettings ps(localSettingsPath, QSettings::IniFormat); ps.setIniCodec("UTF-8"); ps.beginGroup("Advanced"); bool result = ps.value("makeToxPortable", false).toBool(); ps.endGroup(); return result; } void Settings::updateProfileData(Profile* profile, const QCommandLineParser* parser) { QMutexLocker locker{&bigLock}; if (profile == nullptr) { qWarning() << QString("Could not load new settings (profile change to nullptr)"); return; } setCurrentProfile(profile->getName()); saveGlobal(); loadPersonal(profile->getName(), profile->getPasskey()); if (parser) { applyCommandLineOptions(*parser); } } /** * Verifies that commandline proxy settings are at least reasonable. Does not verify provided IP * or hostname addresses are valid. Code duplication with Settings::applyCommandLineOptions, which * also verifies arguments, should be removed in a future refactor. * @param parser QCommandLineParser instance */ bool Settings::verifyProxySettings(const QCommandLineParser& parser) { QString IPv6SettingString = parser.value("I").toLower(); QString LANSettingString = parser.value("L").toLower(); QString UDPSettingString = parser.value("U").toLower(); QString proxySettingString = parser.value("proxy").toLower(); QStringList proxySettingStrings = proxySettingString.split(":"); const QString SOCKS5 = QStringLiteral("socks5"); const QString HTTP = QStringLiteral("http"); const QString NONE = QStringLiteral("none"); const QString ON = QStringLiteral("on"); const QString OFF = QStringLiteral("off"); // Check for incompatible settings bool activeProxyType = false; if (parser.isSet("P")) { activeProxyType = proxySettingStrings[0] == SOCKS5 || proxySettingStrings[0] == HTTP; } if (parser.isSet("I")) { if (!(IPv6SettingString == ON || IPv6SettingString == OFF)) { qCritical() << "Unable to parse IPv6 setting."; return false; } } if (parser.isSet("U")) { if (!(UDPSettingString == ON || UDPSettingString == OFF)) { qCritical() << "Unable to parse UDP setting."; return false; } } if (parser.isSet("L")) { if (!(LANSettingString == ON || LANSettingString == OFF)) { qCritical() << "Unable to parse LAN setting."; return false; } } if (activeProxyType && UDPSettingString == ON) { qCritical() << "Cannot set UDP on with proxy."; return false; } if (activeProxyType && LANSettingString == ON) { qCritical() << "Cannot set LAN discovery on with proxy."; return false; } if (LANSettingString == ON && UDPSettingString == OFF) { qCritical() << "Incompatible UDP/LAN settings."; return false; } if (parser.isSet("P")) { if (proxySettingStrings[0] == NONE) { // slightly lazy check here, accepting 'NONE[:.*]' is fine since no other // arguments will be investigated when proxy settings are applied. return true; } // Since the first argument isn't 'none', verify format of remaining arguments if (proxySettingStrings.size() != 3) { qCritical() << "Invalid number of proxy arguments."; return false; } if (!(proxySettingStrings[0] == SOCKS5 || proxySettingStrings[0] == HTTP)) { qCritical() << "Unable to parse proxy type."; return false; } // TODO(Kriby): Sanity check IPv4/IPv6 addresses/hostnames? int portNumber = proxySettingStrings[2].toInt(); if (!(portNumber > 0 && portNumber < 65536)) { qCritical() << "Invalid port number range."; } } return true; } /** * Applies command line options on top of loaded settings. Fails without changes if attempting to * apply contradicting settings. * @param parser QCommandLineParser instance * @return Success indicator (success = true) */ bool Settings::applyCommandLineOptions(const QCommandLineParser& parser) { if (!verifyProxySettings(parser)) { return false; }; QString IPv6Setting = parser.value("I").toUpper(); QString LANSetting = parser.value("L").toUpper(); QString UDPSetting = parser.value("U").toUpper(); QString proxySettingString = parser.value("proxy").toUpper(); QStringList proxySettings = proxySettingString.split(":"); const QString SOCKS5 = QStringLiteral("SOCKS5"); const QString HTTP = QStringLiteral("HTTP"); const QString NONE = QStringLiteral("NONE"); const QString ON = QStringLiteral("ON"); const QString OFF = QStringLiteral("OFF"); if (parser.isSet("I")) { enableIPv6 = IPv6Setting == ON; qDebug() << QString("Setting IPv6 %1.").arg(IPv6Setting); } if (parser.isSet("P")) { qDebug() << QString("Setting proxy type to %1.").arg(proxySettings[0]); quint16 portNumber = 0; QString address = ""; if (proxySettings[0] == NONE) { proxyType = ICoreSettings::ProxyType::ptNone; } else { if (proxySettings[0] == SOCKS5) { proxyType = ICoreSettings::ProxyType::ptSOCKS5; } else if (proxySettings[0] == HTTP) { proxyType = ICoreSettings::ProxyType::ptHTTP; } else { qCritical() << "Failed to set valid proxy type"; assert(false); // verifyProxySettings should've made this impossible } forceTCP = true; enableLanDiscovery = false; address = proxySettings[1]; portNumber = static_cast(proxySettings[2].toInt()); } proxyAddr = address; qDebug() << QString("Setting proxy address to %1.").arg(address); proxyPort = portNumber; qDebug() << QString("Setting port number to %1.").arg(portNumber); } if (parser.isSet("U")) { bool shouldForceTCP = UDPSetting == OFF; if (!shouldForceTCP && proxyType != ICoreSettings::ProxyType::ptNone) { qDebug() << "Cannot use UDP with proxy; disable proxy explicitly with '-P none'."; } else { forceTCP = shouldForceTCP; qDebug() << QString("Setting UDP %1.").arg(UDPSetting); } // LANSetting == ON is caught by verifyProxySettings, the OFF check removes needless debug if (shouldForceTCP && !(LANSetting == OFF) && enableLanDiscovery) { qDebug() << "Cannot perform LAN discovery without UDP; disabling LAN discovery."; enableLanDiscovery = false; } } if (parser.isSet("L")) { bool shouldEnableLAN = LANSetting == ON; if (shouldEnableLAN && proxyType != ICoreSettings::ProxyType::ptNone) { qDebug() << "Cannot use LAN discovery with proxy; disable proxy explicitly with '-P none'."; } else if (shouldEnableLAN && forceTCP) { qDebug() << "Cannot use LAN discovery without UDP; enable UDP explicitly with '-U on'."; } else { enableLanDiscovery = shouldEnableLAN; qDebug() << QString("Setting LAN Discovery %1.").arg(LANSetting); } } return true; } void Settings::loadPersonal(QString profileName, const ToxEncrypt* passKey) { QMutexLocker locker{&bigLock}; QDir dir(getSettingsDirPath()); QString filePath = dir.filePath(globalSettingsFile); // load from a profile specific friend data list if possible QString tmp = dir.filePath(profileName + ".ini"); if (QFile(tmp).exists()) // otherwise, filePath remains the global file filePath = tmp; qDebug() << "Loading personal settings from" << filePath; SettingsSerializer ps(filePath, passKey); ps.load(); friendLst.clear(); ps.beginGroup("Privacy"); { typingNotification = ps.value("typingNotification", true).toBool(); enableLogging = ps.value("enableLogging", true).toBool(); blackList = ps.value("blackList").toString().split('\n'); } ps.endGroup(); ps.beginGroup("Friends"); { int size = ps.beginReadArray("Friend"); friendLst.reserve(size); for (int i = 0; i < size; i++) { ps.setArrayIndex(i); friendProp fp{ps.value("addr").toString()}; fp.alias = ps.value("alias").toString(); fp.note = ps.value("note").toString(); fp.autoAcceptDir = ps.value("autoAcceptDir").toString(); if (fp.autoAcceptDir == "") fp.autoAcceptDir = ps.value("autoAccept").toString(); fp.autoAcceptCall = Settings::AutoAcceptCallFlags(QFlag(ps.value("autoAcceptCall", 0).toInt())); fp.autoGroupInvite = ps.value("autoGroupInvite").toBool(); fp.circleID = ps.value("circle", -1).toInt(); if (getEnableLogging()) fp.activity = ps.value("activity", QDateTime()).toDateTime(); friendLst.insert(ToxId(fp.addr).getPublicKey().getByteArray(), fp); } ps.endArray(); } ps.endGroup(); ps.beginGroup("Requests"); { int size = ps.beginReadArray("Request"); friendRequests.clear(); friendRequests.reserve(size); for (int i = 0; i < size; i++) { ps.setArrayIndex(i); Request request; request.address = ps.value("addr").toString(); request.message = ps.value("message").toString(); request.read = ps.value("read").toBool(); friendRequests.push_back(request); } ps.endArray(); } ps.endGroup(); ps.beginGroup("GUI"); { compactLayout = ps.value("compactLayout", true).toBool(); sortingMode = static_cast( ps.value("friendSortingMethod", static_cast(FriendListSortingMode::Name)).toInt()); } ps.endGroup(); ps.beginGroup("Proxy"); { proxyType = static_cast(ps.value("proxyType", 0 /* ProxyType::None */).toInt()); proxyType = fixInvalidProxyType(proxyType); proxyAddr = ps.value("proxyAddr", proxyAddr).toString(); proxyPort = static_cast(ps.value("proxyPort", proxyPort).toUInt()); } ps.endGroup(); ps.beginGroup("Circles"); { int size = ps.beginReadArray("Circle"); circleLst.clear(); circleLst.reserve(size); for (int i = 0; i < size; i++) { ps.setArrayIndex(i); circleProp cp; cp.name = ps.value("name").toString(); cp.expanded = ps.value("expanded", true).toBool(); circleLst.push_back(cp); } ps.endArray(); } ps.endGroup(); } void Settings::resetToDefault() { // To stop saving loaded = false; // Remove file with profile settings QDir dir(getSettingsDirPath()); Profile* profile = Nexus::getProfile(); QString localPath = dir.filePath(profile->getName() + ".ini"); QFile local(localPath); if (local.exists()) local.remove(); } /** * @brief Asynchronous, saves the global settings. */ void Settings::saveGlobal() { if (QThread::currentThread() != settingsThread) return (void)QMetaObject::invokeMethod(&getInstance(), "saveGlobal"); QMutexLocker locker{&bigLock}; if (!loaded) return; QString path = getSettingsDirPath() + globalSettingsFile; qDebug() << "Saving global settings at " + path; QSettings s(path, QSettings::IniFormat); s.setIniCodec("UTF-8"); s.clear(); s.beginGroup("Login"); { s.setValue("autoLogin", autoLogin); } s.endGroup(); s.beginGroup("General"); { s.setValue("translation", translation); s.setValue("showSystemTray", showSystemTray); s.setValue("autostartInTray", autostartInTray); s.setValue("closeToTray", closeToTray); s.setValue("currentProfile", currentProfile); s.setValue("autoAwayTime", autoAwayTime); s.setValue("checkUpdates", checkUpdates); s.setValue("notifySound", notifySound); s.setValue("notifyHide", notifyHide); s.setValue("busySound", busySound); s.setValue("autoSaveEnabled", autoSaveEnabled); s.setValue("autoAcceptMaxSize", static_cast(autoAcceptMaxSize)); s.setValue("globalAutoAcceptDir", globalAutoAcceptDir); s.setValue("stylePreference", static_cast(stylePreference)); } s.endGroup(); s.beginGroup("Advanced"); { s.setValue("makeToxPortable", makeToxPortable); s.setValue("enableIPv6", enableIPv6); s.setValue("forceTCP", forceTCP); s.setValue("enableLanDiscovery", enableLanDiscovery); s.setValue("dbSyncType", static_cast(dbSyncType)); } s.endGroup(); s.beginGroup("Widgets"); { const QList widgetNames = widgetSettings.keys(); for (const QString& name : widgetNames) s.setValue(name, widgetSettings.value(name)); } s.endGroup(); s.beginGroup("GUI"); { s.setValue("showWindow", showWindow); s.setValue("notify", notify); s.setValue("desktopNotify", desktopNotify); s.setValue("groupAlwaysNotify", groupAlwaysNotify); s.setValue("separateWindow", separateWindow); s.setValue("dontGroupWindows", dontGroupWindows); s.setValue("groupchatPosition", groupchatPosition); s.setValue("showIdenticons", showIdenticons); s.setValue("smileyPack", smileyPack); s.setValue("emojiFontPointSize", emojiFontPointSize); s.setValue("firstColumnHandlePos", firstColumnHandlePos); s.setValue("secondColumnHandlePosFromRight", secondColumnHandlePosFromRight); s.setValue("timestampFormat", timestampFormat); s.setValue("dateFormat", dateFormat); s.setValue("minimizeOnClose", minimizeOnClose); s.setValue("minimizeToTray", minimizeToTray); s.setValue("lightTrayIcon", lightTrayIcon); s.setValue("useEmoticons", useEmoticons); s.setValue("themeColor", themeColor); s.setValue("style", style); s.setValue("nameColors", nameColors); s.setValue("statusChangeNotificationEnabled", statusChangeNotificationEnabled); s.setValue("spellCheckingEnabled", spellCheckingEnabled); } s.endGroup(); s.beginGroup("Chat"); { s.setValue("chatMessageFont", chatMessageFont); } s.endGroup(); s.beginGroup("State"); { s.setValue("windowGeometry", windowGeometry); s.setValue("windowState", windowState); s.setValue("splitterState", splitterState); s.setValue("dialogGeometry", dialogGeometry); s.setValue("dialogSplitterState", dialogSplitterState); s.setValue("dialogSettingsGeometry", dialogSettingsGeometry); } s.endGroup(); s.beginGroup("Audio"); { s.setValue("inDev", inDev); s.setValue("audioInDevEnabled", audioInDevEnabled); s.setValue("outDev", outDev); s.setValue("audioOutDevEnabled", audioOutDevEnabled); s.setValue("inGain", audioInGainDecibel); s.setValue("audioThreshold", audioThreshold); s.setValue("outVolume", outVolume); s.setValue("enableTestSound", enableTestSound); s.setValue("audioBitrate", audioBitrate); } s.endGroup(); s.beginGroup("Video"); { s.setValue("videoDev", videoDev); s.setValue("camVideoRes", camVideoRes); s.setValue("camVideoFPS", camVideoFPS); s.setValue("screenRegion", screenRegion); s.setValue("screenGrabbed", screenGrabbed); } s.endGroup(); } /** * @brief Asynchronous, saves the current profile. */ void Settings::savePersonal() { savePersonal(Nexus::getProfile()); } /** * @brief Asynchronous, saves the profile. * @param profile Profile to save. */ void Settings::savePersonal(Profile* profile) { if (!profile) { qDebug() << "Could not save personal settings because there is no active profile"; return; } if (QThread::currentThread() != settingsThread) return (void)QMetaObject::invokeMethod(&getInstance(), "savePersonal", Q_ARG(Profile*, profile)); savePersonal(profile->getName(), profile->getPasskey()); } void Settings::savePersonal(QString profileName, const ToxEncrypt* passkey) { QMutexLocker locker{&bigLock}; if (!loaded) return; QString path = getSettingsDirPath() + profileName + ".ini"; qDebug() << "Saving personal settings at " << path; SettingsSerializer ps(path, passkey); ps.beginGroup("Friends"); { ps.beginWriteArray("Friend", friendLst.size()); int index = 0; for (auto& frnd : friendLst) { ps.setArrayIndex(index); ps.setValue("addr", frnd.addr); ps.setValue("alias", frnd.alias); ps.setValue("note", frnd.note); ps.setValue("autoAcceptDir", frnd.autoAcceptDir); ps.setValue("autoAcceptCall", static_cast(frnd.autoAcceptCall)); ps.setValue("autoGroupInvite", frnd.autoGroupInvite); ps.setValue("circle", frnd.circleID); if (getEnableLogging()) ps.setValue("activity", frnd.activity); ++index; } ps.endArray(); } ps.endGroup(); ps.beginGroup("Requests"); { ps.beginWriteArray("Request", friendRequests.size()); int index = 0; for (auto& request : friendRequests) { ps.setArrayIndex(index); ps.setValue("addr", request.address); ps.setValue("message", request.message); ps.setValue("read", request.read); ++index; } ps.endArray(); } ps.endGroup(); ps.beginGroup("GUI"); { ps.setValue("compactLayout", compactLayout); ps.setValue("friendSortingMethod", static_cast(sortingMode)); } ps.endGroup(); ps.beginGroup("Proxy"); { ps.setValue("proxyType", static_cast(proxyType)); ps.setValue("proxyAddr", proxyAddr); ps.setValue("proxyPort", proxyPort); } ps.endGroup(); ps.beginGroup("Circles"); { ps.beginWriteArray("Circle", circleLst.size()); int index = 0; for (auto& circle : circleLst) { ps.setArrayIndex(index); ps.setValue("name", circle.name); ps.setValue("expanded", circle.expanded); ++index; } ps.endArray(); } ps.endGroup(); ps.beginGroup("Privacy"); { ps.setValue("typingNotification", typingNotification); ps.setValue("enableLogging", enableLogging); ps.setValue("blackList", blackList.join('\n')); } ps.endGroup(); ps.save(); } uint32_t Settings::makeProfileId(const QString& profile) { QByteArray data = QCryptographicHash::hash(profile.toUtf8(), QCryptographicHash::Md5); const uint32_t* dwords = reinterpret_cast(data.constData()); return dwords[0] ^ dwords[1] ^ dwords[2] ^ dwords[3]; } /** * @brief Get path to directory, where the settings files are stored. * @return Path to settings directory, ends with a directory separator. */ QString Settings::getSettingsDirPath() const { QMutexLocker locker{&bigLock}; if (makeToxPortable) return qApp->applicationDirPath() + QDir::separator(); // workaround for https://bugreports.qt-project.org/browse/QTBUG-38845 #ifdef Q_OS_WIN return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "AppData" + QDir::separator() + "Roaming" + QDir::separator() + "tox") + QDir::separator(); #elif defined(Q_OS_OSX) return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "Library" + QDir::separator() + "Application Support" + QDir::separator() + "Tox") + QDir::separator(); #else return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QDir::separator() + "tox") + QDir::separator(); #endif } /** * @brief Get path to directory, where the application data are stored. * @return Path to application data, ends with a directory separator. */ QString Settings::getAppDataDirPath() const { QMutexLocker locker{&bigLock}; if (makeToxPortable) return qApp->applicationDirPath() + QDir::separator(); // workaround for https://bugreports.qt-project.org/browse/QTBUG-38845 #ifdef Q_OS_WIN return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "AppData" + QDir::separator() + "Roaming" + QDir::separator() + "tox") + QDir::separator(); #elif defined(Q_OS_OSX) return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "Library" + QDir::separator() + "Application Support" + QDir::separator() + "Tox") + QDir::separator(); #else /* * TODO: Change QStandardPaths::DataLocation to AppDataLocation when upgrate Qt to 5.4+ * For now we need support Qt 5.3, so we use deprecated DataLocation * BTW, it's not a big deal since for linux AppDataLocation and DataLocation are equal */ return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)) + QDir::separator(); #endif } /** * @brief Get path to directory, where the application cache are stored. * @return Path to application cache, ends with a directory separator. */ QString Settings::getAppCacheDirPath() const { QMutexLocker locker{&bigLock}; if (makeToxPortable) return qApp->applicationDirPath() + QDir::separator(); // workaround for https://bugreports.qt-project.org/browse/QTBUG-38845 #ifdef Q_OS_WIN return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "AppData" + QDir::separator() + "Roaming" + QDir::separator() + "tox") + QDir::separator(); #elif defined(Q_OS_OSX) return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "Library" + QDir::separator() + "Application Support" + QDir::separator() + "Tox") + QDir::separator(); #else return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + QDir::separator(); #endif } bool Settings::getEnableTestSound() const { QMutexLocker locker{&bigLock}; return enableTestSound; } void Settings::setEnableTestSound(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != enableTestSound) { enableTestSound = newValue; emit enableTestSoundChanged(enableTestSound); } } bool Settings::getEnableIPv6() const { QMutexLocker locker{&bigLock}; return enableIPv6; } void Settings::setEnableIPv6(bool enabled) { QMutexLocker locker{&bigLock}; if (enabled != enableIPv6) { enableIPv6 = enabled; emit enableIPv6Changed(enableIPv6); } } bool Settings::getMakeToxPortable() const { QMutexLocker locker{&bigLock}; return makeToxPortable; } void Settings::setMakeToxPortable(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != makeToxPortable) { QFile(getSettingsDirPath() + globalSettingsFile).remove(); makeToxPortable = newValue; saveGlobal(); emit makeToxPortableChanged(makeToxPortable); } } bool Settings::getAutorun() const { QMutexLocker locker{&bigLock}; #ifdef QTOX_PLATFORM_EXT return Platform::getAutorun(); #else return false; #endif } void Settings::setAutorun(bool newValue) { #ifdef QTOX_PLATFORM_EXT QMutexLocker locker{&bigLock}; bool autorun = Platform::getAutorun(); if (newValue != autorun) { Platform::setAutorun(newValue); emit autorunChanged(autorun); } #else Q_UNUSED(newValue); #endif } bool Settings::getAutostartInTray() const { QMutexLocker locker{&bigLock}; return autostartInTray; } QString Settings::getStyle() const { QMutexLocker locker{&bigLock}; return style; } void Settings::setStyle(const QString& newStyle) { QMutexLocker locker{&bigLock}; if (newStyle != style) { style = newStyle; emit styleChanged(style); } } bool Settings::getShowSystemTray() const { QMutexLocker locker{&bigLock}; return showSystemTray; } void Settings::setShowSystemTray(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != showSystemTray) { showSystemTray = newValue; emit showSystemTrayChanged(newValue); } } void Settings::setUseEmoticons(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != useEmoticons) { useEmoticons = newValue; emit useEmoticonsChanged(useEmoticons); } } bool Settings::getUseEmoticons() const { QMutexLocker locker{&bigLock}; return useEmoticons; } void Settings::setAutoSaveEnabled(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != autoSaveEnabled) { autoSaveEnabled = newValue; emit autoSaveEnabledChanged(autoSaveEnabled); } } bool Settings::getAutoSaveEnabled() const { QMutexLocker locker{&bigLock}; return autoSaveEnabled; } void Settings::setAutostartInTray(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != autostartInTray) { autostartInTray = newValue; emit autostartInTrayChanged(autostartInTray); } } bool Settings::getCloseToTray() const { QMutexLocker locker{&bigLock}; return closeToTray; } void Settings::setCloseToTray(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != closeToTray) { closeToTray = newValue; emit closeToTrayChanged(newValue); } } bool Settings::getMinimizeToTray() const { QMutexLocker locker{&bigLock}; return minimizeToTray; } void Settings::setMinimizeToTray(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != minimizeToTray) { minimizeToTray = newValue; emit minimizeToTrayChanged(minimizeToTray); } } bool Settings::getLightTrayIcon() const { QMutexLocker locker{&bigLock}; return lightTrayIcon; } void Settings::setLightTrayIcon(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != lightTrayIcon) { lightTrayIcon = newValue; emit lightTrayIconChanged(lightTrayIcon); } } bool Settings::getStatusChangeNotificationEnabled() const { QMutexLocker locker{&bigLock}; return statusChangeNotificationEnabled; } void Settings::setStatusChangeNotificationEnabled(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != statusChangeNotificationEnabled) { statusChangeNotificationEnabled = newValue; emit statusChangeNotificationEnabledChanged(statusChangeNotificationEnabled); } } bool Settings::getSpellCheckingEnabled() const { const QMutexLocker locker{&bigLock}; return spellCheckingEnabled; } void Settings::setSpellCheckingEnabled(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != spellCheckingEnabled) { spellCheckingEnabled = newValue; emit statusChangeNotificationEnabledChanged(statusChangeNotificationEnabled); } } bool Settings::getNotifySound() const { QMutexLocker locker{&bigLock}; return notifySound; } void Settings::setNotifySound(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != notifySound) { notifySound = newValue; emit notifySoundChanged(notifySound); } } bool Settings::getNotifyHide() const { QMutexLocker locker{&bigLock}; return notifyHide; } void Settings::setNotifyHide(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != notifyHide) { notifyHide = newValue; emit notifyHideChanged(notifyHide); } } bool Settings::getBusySound() const { QMutexLocker locker{&bigLock}; return busySound; } void Settings::setBusySound(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != busySound) { busySound = newValue; emit busySoundChanged(busySound); } } bool Settings::getGroupAlwaysNotify() const { QMutexLocker locker{&bigLock}; return groupAlwaysNotify; } void Settings::setGroupAlwaysNotify(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != groupAlwaysNotify) { groupAlwaysNotify = newValue; emit groupAlwaysNotifyChanged(groupAlwaysNotify); } } QString Settings::getTranslation() const { QMutexLocker locker{&bigLock}; return translation; } void Settings::setTranslation(const QString& newValue) { QMutexLocker locker{&bigLock}; if (newValue != translation) { translation = newValue; emit translationChanged(translation); } } bool Settings::getForceTCP() const { QMutexLocker locker{&bigLock}; return forceTCP; } void Settings::setForceTCP(bool enabled) { QMutexLocker locker{&bigLock}; if (enabled != forceTCP) { forceTCP = enabled; emit forceTCPChanged(forceTCP); } } bool Settings::getEnableLanDiscovery() const { QMutexLocker locker{&bigLock}; return enableLanDiscovery; } void Settings::setEnableLanDiscovery(bool enabled) { QMutexLocker locker{&bigLock}; if (enabled != enableLanDiscovery) { enableLanDiscovery = enabled; emit enableLanDiscoveryChanged(enableLanDiscovery); } } QNetworkProxy Settings::getProxy() const { QMutexLocker locker{&bigLock}; QNetworkProxy proxy; switch (Settings::getProxyType()) { case ProxyType::ptNone: proxy.setType(QNetworkProxy::NoProxy); break; case ProxyType::ptSOCKS5: proxy.setType(QNetworkProxy::Socks5Proxy); break; case ProxyType::ptHTTP: proxy.setType(QNetworkProxy::HttpProxy); break; default: proxy.setType(QNetworkProxy::NoProxy); qWarning() << "Invalid Proxy type, setting to NoProxy"; break; } proxy.setHostName(Settings::getProxyAddr()); proxy.setPort(Settings::getProxyPort()); return proxy; } Settings::ProxyType Settings::getProxyType() const { QMutexLocker locker{&bigLock}; return proxyType; } void Settings::setProxyType(ProxyType newValue) { QMutexLocker locker{&bigLock}; if (newValue != proxyType) { proxyType = newValue; emit proxyTypeChanged(proxyType); } } QString Settings::getProxyAddr() const { QMutexLocker locker{&bigLock}; return proxyAddr; } void Settings::setProxyAddr(const QString& address) { QMutexLocker locker{&bigLock}; if (address != proxyAddr) { proxyAddr = address; emit proxyAddressChanged(proxyAddr); } } quint16 Settings::getProxyPort() const { QMutexLocker locker{&bigLock}; return proxyPort; } void Settings::setProxyPort(quint16 port) { QMutexLocker locker{&bigLock}; if (port != proxyPort) { proxyPort = port; emit proxyPortChanged(proxyPort); } } QString Settings::getCurrentProfile() const { QMutexLocker locker{&bigLock}; return currentProfile; } uint32_t Settings::getCurrentProfileId() const { QMutexLocker locker{&bigLock}; return currentProfileId; } void Settings::setCurrentProfile(const QString& profile) { QMutexLocker locker{&bigLock}; if (profile != currentProfile) { currentProfile = profile; currentProfileId = makeProfileId(currentProfile); emit currentProfileIdChanged(currentProfileId); } } bool Settings::getEnableLogging() const { QMutexLocker locker{&bigLock}; return enableLogging; } void Settings::setEnableLogging(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != enableLogging) { enableLogging = newValue; emit enableLoggingChanged(enableLogging); } } int Settings::getAutoAwayTime() const { QMutexLocker locker{&bigLock}; return autoAwayTime; } /** * @brief Sets how long the user may stay idle, before online status is set to "away". * @param[in] newValue the user idle duration in minutes * @note Values < 0 default to 10 minutes. */ void Settings::setAutoAwayTime(int newValue) { QMutexLocker locker{&bigLock}; if (newValue < 0) newValue = 10; if (newValue != autoAwayTime) { autoAwayTime = newValue; emit autoAwayTimeChanged(autoAwayTime); } } QString Settings::getAutoAcceptDir(const ToxPk& id) const { QMutexLocker locker{&bigLock}; auto it = friendLst.find(id.getByteArray()); if (it != friendLst.end()) return it->autoAcceptDir; return QString(); } void Settings::setAutoAcceptDir(const ToxPk& id, const QString& dir) { QMutexLocker locker{&bigLock}; auto& frnd = getOrInsertFriendPropRef(id); if (frnd.autoAcceptDir != dir) { frnd.autoAcceptDir = dir; emit autoAcceptDirChanged(id, dir); } } Settings::AutoAcceptCallFlags Settings::getAutoAcceptCall(const ToxPk& id) const { QMutexLocker locker{&bigLock}; auto it = friendLst.find(id.getByteArray()); if (it != friendLst.end()) return it->autoAcceptCall; return Settings::AutoAcceptCallFlags(); } void Settings::setAutoAcceptCall(const ToxPk& id, AutoAcceptCallFlags accept) { QMutexLocker locker{&bigLock}; auto& frnd = getOrInsertFriendPropRef(id); if (frnd.autoAcceptCall != accept) { frnd.autoAcceptCall = accept; emit autoAcceptCallChanged(id, accept); } } bool Settings::getAutoGroupInvite(const ToxPk& id) const { QMutexLocker locker{&bigLock}; auto it = friendLst.find(id.getByteArray()); if (it != friendLst.end()) { return it->autoGroupInvite; } return false; } void Settings::setAutoGroupInvite(const ToxPk& id, bool accept) { QMutexLocker locker{&bigLock}; auto& frnd = getOrInsertFriendPropRef(id); if (frnd.autoGroupInvite != accept) { frnd.autoGroupInvite = accept; emit autoGroupInviteChanged(id, accept); } } QString Settings::getContactNote(const ToxPk& id) const { QMutexLocker locker{&bigLock}; auto it = friendLst.find(id.getByteArray()); if (it != friendLst.end()) return it->note; return QString(); } void Settings::setContactNote(const ToxPk& id, const QString& note) { QMutexLocker locker{&bigLock}; auto& frnd = getOrInsertFriendPropRef(id); if (frnd.note != note) { frnd.note = note; emit contactNoteChanged(id, note); } } QString Settings::getGlobalAutoAcceptDir() const { QMutexLocker locker{&bigLock}; return globalAutoAcceptDir; } void Settings::setGlobalAutoAcceptDir(const QString& newValue) { QMutexLocker locker{&bigLock}; if (newValue != globalAutoAcceptDir) { globalAutoAcceptDir = newValue; emit globalAutoAcceptDirChanged(globalAutoAcceptDir); } } size_t Settings::getMaxAutoAcceptSize() const { QMutexLocker locker{&bigLock}; return autoAcceptMaxSize; } void Settings::setMaxAutoAcceptSize(size_t size) { QMutexLocker locker{&bigLock}; if (size != autoAcceptMaxSize) { autoAcceptMaxSize = size; emit autoAcceptMaxSizeChanged(autoAcceptMaxSize); } } const QFont& Settings::getChatMessageFont() const { QMutexLocker locker(&bigLock); return chatMessageFont; } void Settings::setChatMessageFont(const QFont& font) { QMutexLocker locker(&bigLock); if (font != chatMessageFont) { chatMessageFont = font; emit chatMessageFontChanged(chatMessageFont); } } void Settings::setWidgetData(const QString& uniqueName, const QByteArray& data) { QMutexLocker locker{&bigLock}; if (!widgetSettings.contains(uniqueName) || widgetSettings[uniqueName] != data) { widgetSettings[uniqueName] = data; emit widgetDataChanged(uniqueName); } } QByteArray Settings::getWidgetData(const QString& uniqueName) const { QMutexLocker locker{&bigLock}; return widgetSettings.value(uniqueName); } QString Settings::getSmileyPack() const { QMutexLocker locker{&bigLock}; return smileyPack; } void Settings::setSmileyPack(const QString& value) { QMutexLocker locker{&bigLock}; if (value != smileyPack) { smileyPack = value; emit smileyPackChanged(smileyPack); } } int Settings::getEmojiFontPointSize() const { QMutexLocker locker{&bigLock}; return emojiFontPointSize; } void Settings::setEmojiFontPointSize(int value) { QMutexLocker locker{&bigLock}; if (value != emojiFontPointSize) { emojiFontPointSize = value; emit emojiFontPointSizeChanged(emojiFontPointSize); } } const QString& Settings::getTimestampFormat() const { QMutexLocker locker{&bigLock}; return timestampFormat; } void Settings::setTimestampFormat(const QString& format) { QMutexLocker locker{&bigLock}; if (format != timestampFormat) { timestampFormat = format; emit timestampFormatChanged(timestampFormat); } } const QString& Settings::getDateFormat() const { QMutexLocker locker{&bigLock}; return dateFormat; } void Settings::setDateFormat(const QString& format) { QMutexLocker locker{&bigLock}; if (format != dateFormat) { dateFormat = format; emit dateFormatChanged(dateFormat); } } Settings::StyleType Settings::getStylePreference() const { QMutexLocker locker{&bigLock}; return stylePreference; } void Settings::setStylePreference(StyleType newValue) { QMutexLocker locker{&bigLock}; if (newValue != stylePreference) { stylePreference = newValue; emit stylePreferenceChanged(stylePreference); } } QByteArray Settings::getWindowGeometry() const { QMutexLocker locker{&bigLock}; return windowGeometry; } void Settings::setWindowGeometry(const QByteArray& value) { QMutexLocker locker{&bigLock}; if (value != windowGeometry) { windowGeometry = value; emit windowGeometryChanged(windowGeometry); } } QByteArray Settings::getWindowState() const { QMutexLocker locker{&bigLock}; return windowState; } void Settings::setWindowState(const QByteArray& value) { QMutexLocker locker{&bigLock}; if (value != windowState) { windowState = value; emit windowStateChanged(windowState); } } bool Settings::getCheckUpdates() const { QMutexLocker locker{&bigLock}; return checkUpdates; } void Settings::setCheckUpdates(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != checkUpdates) { checkUpdates = newValue; emit checkUpdatesChanged(checkUpdates); } } bool Settings::getNotify() const { QMutexLocker locker{&bigLock}; return notify; } void Settings::setNotify(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != notify) { notify = newValue; emit notifyChanged(notify); } } bool Settings::getShowWindow() const { QMutexLocker locker{&bigLock}; return showWindow; } void Settings::setShowWindow(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != showWindow) { showWindow = newValue; emit showWindowChanged(showWindow); } } bool Settings::getDesktopNotify() const { QMutexLocker locker{&bigLock}; return desktopNotify; } void Settings::setDesktopNotify(bool enabled) { QMutexLocker locker{&bigLock}; if (enabled != desktopNotify) { desktopNotify = enabled; emit desktopNotifyChanged(desktopNotify); } } QByteArray Settings::getSplitterState() const { QMutexLocker locker{&bigLock}; return splitterState; } void Settings::setSplitterState(const QByteArray& value) { QMutexLocker locker{&bigLock}; if (value != splitterState) { splitterState = value; emit splitterStateChanged(splitterState); } } QByteArray Settings::getDialogGeometry() const { QMutexLocker locker{&bigLock}; return dialogGeometry; } void Settings::setDialogGeometry(const QByteArray& value) { QMutexLocker locker{&bigLock}; if (value != dialogGeometry) { dialogGeometry = value; emit dialogGeometryChanged(dialogGeometry); } } QByteArray Settings::getDialogSplitterState() const { QMutexLocker locker{&bigLock}; return dialogSplitterState; } void Settings::setDialogSplitterState(const QByteArray& value) { QMutexLocker locker{&bigLock}; if (value != dialogSplitterState) { dialogSplitterState = value; emit dialogSplitterStateChanged(dialogSplitterState); } } QByteArray Settings::getDialogSettingsGeometry() const { QMutexLocker locker{&bigLock}; return dialogSettingsGeometry; } void Settings::setDialogSettingsGeometry(const QByteArray& value) { QMutexLocker locker{&bigLock}; if (value != dialogSettingsGeometry) { dialogSettingsGeometry = value; emit dialogSettingsGeometryChanged(dialogSettingsGeometry); } } bool Settings::getMinimizeOnClose() const { QMutexLocker locker{&bigLock}; return minimizeOnClose; } void Settings::setMinimizeOnClose(bool newValue) { QMutexLocker locker{&bigLock}; if (newValue != minimizeOnClose) { minimizeOnClose = newValue; emit minimizeOnCloseChanged(minimizeOnClose); } } bool Settings::getTypingNotification() const { QMutexLocker locker{&bigLock}; return typingNotification; } void Settings::setTypingNotification(bool enabled) { QMutexLocker locker{&bigLock}; if (enabled != typingNotification) { typingNotification = enabled; emit typingNotificationChanged(typingNotification); } } QStringList Settings::getBlackList() const { QMutexLocker locker{&bigLock}; return blackList; } void Settings::setBlackList(const QStringList& blist) { QMutexLocker locker{&bigLock}; if (blist != blackList) { blackList = blist; emit blackListChanged(blackList); } } QString Settings::getInDev() const { QMutexLocker locker{&bigLock}; return inDev; } void Settings::setInDev(const QString& deviceSpecifier) { QMutexLocker locker{&bigLock}; if (deviceSpecifier != inDev) { inDev = deviceSpecifier; emit inDevChanged(inDev); } } bool Settings::getAudioInDevEnabled() const { QMutexLocker locker(&bigLock); return audioInDevEnabled; } void Settings::setAudioInDevEnabled(bool enabled) { QMutexLocker locker(&bigLock); if (enabled != audioInDevEnabled) { audioInDevEnabled = enabled; emit audioInDevEnabledChanged(enabled); } } qreal Settings::getAudioInGainDecibel() const { QMutexLocker locker{&bigLock}; return audioInGainDecibel; } void Settings::setAudioInGainDecibel(qreal dB) { QMutexLocker locker{&bigLock}; if (dB < audioInGainDecibel || dB > audioInGainDecibel) { audioInGainDecibel = dB; emit audioInGainDecibelChanged(audioInGainDecibel); } } qreal Settings::getAudioThreshold() const { QMutexLocker locker{&bigLock}; return audioThreshold; } void Settings::setAudioThreshold(qreal percent) { QMutexLocker locker{&bigLock}; if (percent < audioThreshold || percent > audioThreshold) { audioThreshold = percent; emit audioThresholdChanged(audioThreshold); } } QString Settings::getVideoDev() const { QMutexLocker locker{&bigLock}; return videoDev; } void Settings::setVideoDev(const QString& deviceSpecifier) { QMutexLocker locker{&bigLock}; if (deviceSpecifier != videoDev) { videoDev = deviceSpecifier; emit videoDevChanged(videoDev); } } QString Settings::getOutDev() const { QMutexLocker locker{&bigLock}; return outDev; } void Settings::setOutDev(const QString& deviceSpecifier) { QMutexLocker locker{&bigLock}; if (deviceSpecifier != outDev) { outDev = deviceSpecifier; emit outDevChanged(outDev); } } bool Settings::getAudioOutDevEnabled() const { QMutexLocker locker(&bigLock); return audioOutDevEnabled; } void Settings::setAudioOutDevEnabled(bool enabled) { QMutexLocker locker(&bigLock); if (enabled != audioOutDevEnabled) { audioOutDevEnabled = enabled; emit audioOutDevEnabledChanged(audioOutDevEnabled); } } int Settings::getOutVolume() const { QMutexLocker locker{&bigLock}; return outVolume; } void Settings::setOutVolume(int volume) { QMutexLocker locker{&bigLock}; if (volume != outVolume) { outVolume = volume; emit outVolumeChanged(outVolume); } } int Settings::getAudioBitrate() const { const QMutexLocker locker{&bigLock}; return audioBitrate; } void Settings::setAudioBitrate(int bitrate) { const QMutexLocker locker{&bigLock}; if (bitrate != audioBitrate) { audioBitrate = bitrate; emit audioBitrateChanged(audioBitrate); } } QRect Settings::getScreenRegion() const { QMutexLocker locker(&bigLock); return screenRegion; } void Settings::setScreenRegion(const QRect& value) { QMutexLocker locker{&bigLock}; if (value != screenRegion) { screenRegion = value; emit screenRegionChanged(screenRegion); } } bool Settings::getScreenGrabbed() const { QMutexLocker locker(&bigLock); return screenGrabbed; } void Settings::setScreenGrabbed(bool value) { QMutexLocker locker{&bigLock}; if (value != screenGrabbed) { screenGrabbed = value; emit screenGrabbedChanged(screenGrabbed); } } QRect Settings::getCamVideoRes() const { QMutexLocker locker{&bigLock}; return camVideoRes; } void Settings::setCamVideoRes(QRect newValue) { QMutexLocker locker{&bigLock}; if (newValue != camVideoRes) { camVideoRes = newValue; emit camVideoResChanged(camVideoRes); } } float Settings::getCamVideoFPS() const { QMutexLocker locker{&bigLock}; return camVideoFPS; } void Settings::setCamVideoFPS(float newValue) { QMutexLocker locker{&bigLock}; if (newValue != camVideoFPS) { camVideoFPS = newValue; emit camVideoFPSChanged(camVideoFPS); } } QString Settings::getFriendAddress(const QString& publicKey) const { QMutexLocker locker{&bigLock}; // TODO: using ToxId here is a hack QByteArray key = ToxId(publicKey).getPublicKey().getByteArray(); auto it = friendLst.find(key); if (it != friendLst.end()) return it->addr; return QString(); } void Settings::updateFriendAddress(const QString& newAddr) { QMutexLocker locker{&bigLock}; // TODO: using ToxId here is a hack auto key = ToxId(newAddr).getPublicKey(); auto& frnd = getOrInsertFriendPropRef(key); frnd.addr = newAddr; } QString Settings::getFriendAlias(const ToxPk& id) const { QMutexLocker locker{&bigLock}; auto it = friendLst.find(id.getByteArray()); if (it != friendLst.end()) return it->alias; return QString(); } void Settings::setFriendAlias(const ToxPk& id, const QString& alias) { QMutexLocker locker{&bigLock}; auto& frnd = getOrInsertFriendPropRef(id); frnd.alias = alias; } int Settings::getFriendCircleID(const ToxPk& id) const { QMutexLocker locker{&bigLock}; auto it = friendLst.find(id.getByteArray()); if (it != friendLst.end()) return it->circleID; return -1; } void Settings::setFriendCircleID(const ToxPk& id, int circleID) { QMutexLocker locker{&bigLock}; auto& frnd = getOrInsertFriendPropRef(id); frnd.circleID = circleID; } QDateTime Settings::getFriendActivity(const ToxPk& id) const { QMutexLocker locker{&bigLock}; auto it = friendLst.find(id.getByteArray()); if (it != friendLst.end()) return it->activity; return QDateTime(); } void Settings::setFriendActivity(const ToxPk& id, const QDateTime& activity) { QMutexLocker locker{&bigLock}; auto& frnd = getOrInsertFriendPropRef(id); frnd.activity = activity; } void Settings::saveFriendSettings(const ToxPk& id) { Q_UNUSED(id); savePersonal(); } void Settings::removeFriendSettings(const ToxPk& id) { QMutexLocker locker{&bigLock}; friendLst.remove(id.getByteArray()); } bool Settings::getCompactLayout() const { QMutexLocker locker{&bigLock}; return compactLayout; } void Settings::setCompactLayout(bool value) { QMutexLocker locker{&bigLock}; if (value != compactLayout) { compactLayout = value; emit compactLayoutChanged(value); } } Settings::FriendListSortingMode Settings::getFriendSortingMode() const { QMutexLocker locker{&bigLock}; return sortingMode; } void Settings::setFriendSortingMode(FriendListSortingMode mode) { QMutexLocker locker{&bigLock}; if (mode != sortingMode) { sortingMode = mode; emit sortingModeChanged(sortingMode); } } bool Settings::getSeparateWindow() const { QMutexLocker locker{&bigLock}; return separateWindow; } void Settings::setSeparateWindow(bool value) { QMutexLocker locker{&bigLock}; if (value != separateWindow) { separateWindow = value; emit separateWindowChanged(value); } } bool Settings::getDontGroupWindows() const { QMutexLocker locker{&bigLock}; return dontGroupWindows; } void Settings::setDontGroupWindows(bool value) { QMutexLocker locker{&bigLock}; if (value != dontGroupWindows) { dontGroupWindows = value; emit dontGroupWindowsChanged(dontGroupWindows); } } bool Settings::getGroupchatPosition() const { QMutexLocker locker{&bigLock}; return groupchatPosition; } void Settings::setGroupchatPosition(bool value) { QMutexLocker locker{&bigLock}; if (value != groupchatPosition) { groupchatPosition = value; emit groupchatPositionChanged(value); } } bool Settings::getShowIdenticons() const { const QMutexLocker locker{&bigLock}; return showIdenticons; } void Settings::setShowIdenticons(bool value) { const QMutexLocker locker{&bigLock}; if (value != showIdenticons) { showIdenticons = value; emit showIdenticonsChanged(value); } } int Settings::getCircleCount() const { QMutexLocker locker{&bigLock}; return circleLst.size(); } QString Settings::getCircleName(int id) const { QMutexLocker locker{&bigLock}; return circleLst[id].name; } void Settings::setCircleName(int id, const QString& name) { QMutexLocker locker{&bigLock}; circleLst[id].name = name; savePersonal(); } int Settings::addCircle(const QString& name) { QMutexLocker locker{&bigLock}; circleProp cp; cp.expanded = false; if (name.isEmpty()) cp.name = tr("Circle #%1").arg(circleLst.count() + 1); else cp.name = name; circleLst.append(cp); savePersonal(); return circleLst.count() - 1; } bool Settings::getCircleExpanded(int id) const { QMutexLocker locker{&bigLock}; return circleLst[id].expanded; } void Settings::setCircleExpanded(int id, bool expanded) { QMutexLocker locker{&bigLock}; circleLst[id].expanded = expanded; } bool Settings::addFriendRequest(const QString& friendAddress, const QString& message) { QMutexLocker locker{&bigLock}; for (auto queued : friendRequests) { if (queued.address == friendAddress) { queued.message = message; queued.read = false; return false; } } Request request; request.address = friendAddress; request.message = message; request.read = false; friendRequests.push_back(request); return true; } unsigned int Settings::getUnreadFriendRequests() const { QMutexLocker locker{&bigLock}; unsigned int unreadFriendRequests = 0; for (auto request : friendRequests) if (!request.read) ++unreadFriendRequests; return unreadFriendRequests; } Settings::Request Settings::getFriendRequest(int index) const { QMutexLocker locker{&bigLock}; return friendRequests.at(index); } int Settings::getFriendRequestSize() const { QMutexLocker locker{&bigLock}; return friendRequests.size(); } void Settings::clearUnreadFriendRequests() { QMutexLocker locker{&bigLock}; for (auto& request : friendRequests) request.read = true; } void Settings::removeFriendRequest(int index) { QMutexLocker locker{&bigLock}; friendRequests.removeAt(index); } void Settings::readFriendRequest(int index) { QMutexLocker locker{&bigLock}; friendRequests[index].read = true; } int Settings::removeCircle(int id) { // Replace index with last one and remove last one instead. // This gives you contiguous ids all the time. circleLst[id] = circleLst.last(); circleLst.pop_back(); savePersonal(); return circleLst.count(); } int Settings::getThemeColor() const { QMutexLocker locker{&bigLock}; return themeColor; } void Settings::setThemeColor(int value) { QMutexLocker locker{&bigLock}; if (value != themeColor) { themeColor = value; emit themeColorChanged(themeColor); } } bool Settings::getAutoLogin() const { QMutexLocker locker{&bigLock}; return autoLogin; } void Settings::setAutoLogin(bool state) { QMutexLocker locker{&bigLock}; if (state != autoLogin) { autoLogin = state; emit autoLoginChanged(autoLogin); } } void Settings::setEnableGroupChatsColor(bool state) { QMutexLocker locker{&bigLock}; if (state != nameColors) { nameColors = state; emit nameColorsChanged(nameColors); } } bool Settings::getEnableGroupChatsColor() const { return nameColors; } /** * @brief Write a default personal .ini settings file for a profile. * @param basename Filename without extension to save settings. * * @note If basename is "profile", settings will be saved in profile.ini */ void Settings::createPersonal(QString basename) { QMutexLocker locker{&bigLock}; QString path = getSettingsDirPath() + QDir::separator() + basename + ".ini"; qDebug() << "Creating new profile settings in " << path; QSettings ps(path, QSettings::IniFormat); ps.setIniCodec("UTF-8"); ps.beginGroup("Friends"); ps.beginWriteArray("Friend", 0); ps.endArray(); ps.endGroup(); ps.beginGroup("Privacy"); ps.endGroup(); } /** * @brief Creates a path to the settings dir, if it doesn't already exist */ void Settings::createSettingsDir() { QMutexLocker locker{&bigLock}; QString dir = Settings::getSettingsDirPath(); QDir directory(dir); if (!directory.exists() && !directory.mkpath(directory.absolutePath())) qCritical() << "Error while creating directory " << dir; } /** * @brief Waits for all asynchronous operations to complete */ void Settings::sync() { if (QThread::currentThread() != settingsThread) { QMetaObject::invokeMethod(&getInstance(), "sync", Qt::BlockingQueuedConnection); return; } QMutexLocker locker{&bigLock}; qApp->processEvents(); } Settings::friendProp& Settings::getOrInsertFriendPropRef(const ToxPk& id) { // No mutex lock, this is a private fn that should only be called by other // public functions that already locked the mutex auto it = friendLst.find(id.getByteArray()); if (it == friendLst.end()) { it = friendLst.insert(id.getByteArray(), friendProp{id.toString()}); } return *it; } ICoreSettings::ProxyType Settings::fixInvalidProxyType(ICoreSettings::ProxyType proxyType) { // Repair uninitialized enum that was saved to settings due to bug (https://github.com/qTox/qTox/issues/5311) switch (proxyType) { case ICoreSettings::ProxyType::ptNone: case ICoreSettings::ProxyType::ptSOCKS5: case ICoreSettings::ProxyType::ptHTTP: return proxyType; default: qWarning() << "Repairing invalid ProxyType, UDP will be enabled"; return ICoreSettings::ProxyType::ptNone; } } qTox/src/persistence/settings.h000066400000000000000000000601071415623743500171530ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SETTINGS_HPP #define SETTINGS_HPP #include "src/audio/iaudiosettings.h" #include "src/core/icoresettings.h" #include "src/core/toxencrypt.h" #include "src/core/toxfile.h" #include "src/persistence/ifriendsettings.h" #include "src/persistence/igroupsettings.h" #include "src/video/ivideosettings.h" #include "src/util/compatiblerecursivemutex.h" #include #include #include #include #include #include #include #include class Profile; class QCommandLineParser; namespace Db { enum class syncType; } class Settings : public QObject, public ICoreSettings, public IFriendSettings, public IGroupSettings, public IAudioSettings, public IVideoSettings { Q_OBJECT Q_ENUMS(StyleType) // general Q_PROPERTY(bool compactLayout READ getCompactLayout WRITE setCompactLayout NOTIFY compactLayoutChanged FINAL) Q_PROPERTY(bool autorun READ getAutorun WRITE setAutorun NOTIFY autorunChanged FINAL) // GUI Q_PROPERTY(bool separateWindow READ getSeparateWindow WRITE setSeparateWindow NOTIFY separateWindowChanged FINAL) Q_PROPERTY(QString smileyPack READ getSmileyPack WRITE setSmileyPack NOTIFY smileyPackChanged FINAL) Q_PROPERTY(int emojiFontPointSize READ getEmojiFontPointSize WRITE setEmojiFontPointSize NOTIFY emojiFontPointSizeChanged FINAL) Q_PROPERTY(bool minimizeOnClose READ getMinimizeOnClose WRITE setMinimizeOnClose NOTIFY minimizeOnCloseChanged FINAL) Q_PROPERTY(QByteArray windowGeometry READ getWindowGeometry WRITE setWindowGeometry NOTIFY windowGeometryChanged FINAL) Q_PROPERTY(QByteArray windowState READ getWindowState WRITE setWindowState NOTIFY windowStateChanged FINAL) Q_PROPERTY(QByteArray splitterState READ getSplitterState WRITE setSplitterState NOTIFY splitterStateChanged FINAL) Q_PROPERTY(QByteArray dialogGeometry READ getDialogGeometry WRITE setDialogGeometry NOTIFY dialogGeometryChanged FINAL) Q_PROPERTY(QByteArray dialogSplitterState READ getDialogSplitterState WRITE setDialogSplitterState NOTIFY dialogSplitterStateChanged FINAL) Q_PROPERTY(QByteArray dialogSettingsGeometry READ getDialogSettingsGeometry WRITE setDialogSettingsGeometry NOTIFY dialogSettingsGeometryChanged FINAL) Q_PROPERTY(QString style READ getStyle WRITE setStyle NOTIFY styleChanged FINAL) Q_PROPERTY(bool showSystemTray READ getShowSystemTray WRITE setShowSystemTray NOTIFY showSystemTrayChanged FINAL) Q_PROPERTY(bool showIdenticons READ getShowIdenticons WRITE setShowIdenticons NOTIFY showIdenticonsChanged FINAL) // ChatView Q_PROPERTY(bool groupchatPosition READ getGroupchatPosition WRITE setGroupchatPosition NOTIFY groupchatPositionChanged FINAL) Q_PROPERTY(QFont chatMessageFont READ getChatMessageFont WRITE setChatMessageFont NOTIFY chatMessageFontChanged FINAL) Q_PROPERTY(StyleType stylePreference READ getStylePreference WRITE setStylePreference NOTIFY stylePreferenceChanged FINAL) Q_PROPERTY(QString timestampFormat READ getTimestampFormat WRITE setTimestampFormat NOTIFY timestampFormatChanged FINAL) Q_PROPERTY(QString dateFormat READ getDateFormat WRITE setDateFormat NOTIFY dateFormatChanged FINAL) Q_PROPERTY(bool statusChangeNotificationEnabled READ getStatusChangeNotificationEnabled WRITE setStatusChangeNotificationEnabled NOTIFY statusChangeNotificationEnabledChanged FINAL) Q_PROPERTY(bool spellCheckingEnabled READ getSpellCheckingEnabled WRITE setSpellCheckingEnabled NOTIFY spellCheckingEnabledChanged FINAL) // Privacy Q_PROPERTY(bool typingNotification READ getTypingNotification WRITE setTypingNotification NOTIFY typingNotificationChanged FINAL) Q_PROPERTY(QStringList blackList READ getBlackList WRITE setBlackList NOTIFY blackListChanged FINAL) // Audio Q_PROPERTY(QString inDev READ getInDev WRITE setInDev NOTIFY inDevChanged FINAL) Q_PROPERTY(bool audioInDevEnabled READ getAudioInDevEnabled WRITE setAudioInDevEnabled NOTIFY audioInDevEnabledChanged FINAL) Q_PROPERTY(qreal audioInGainDecibel READ getAudioInGainDecibel WRITE setAudioInGainDecibel NOTIFY audioInGainDecibelChanged FINAL) Q_PROPERTY(qreal audioThreshold READ getAudioThreshold WRITE setAudioThreshold NOTIFY audioThresholdChanged FINAL) Q_PROPERTY(QString outDev READ getOutDev WRITE setOutDev NOTIFY outDevChanged FINAL) Q_PROPERTY(bool audioOutDevEnabled READ getAudioOutDevEnabled WRITE setAudioOutDevEnabled NOTIFY audioOutDevEnabledChanged FINAL) Q_PROPERTY(int outVolume READ getOutVolume WRITE setOutVolume NOTIFY outVolumeChanged FINAL) Q_PROPERTY(int audioBitrate READ getAudioBitrate WRITE setAudioBitrate NOTIFY audioBitrateChanged FINAL) // Video Q_PROPERTY(QString videoDev READ getVideoDev WRITE setVideoDev NOTIFY videoDevChanged FINAL) Q_PROPERTY(QRect camVideoRes READ getCamVideoRes WRITE setCamVideoRes NOTIFY camVideoResChanged FINAL) Q_PROPERTY(QRect screenRegion READ getScreenRegion WRITE setScreenRegion NOTIFY screenRegionChanged FINAL) Q_PROPERTY(bool screenGrabbed READ getScreenGrabbed WRITE setScreenGrabbed NOTIFY screenGrabbedChanged FINAL) Q_PROPERTY(float camVideoFPS READ getCamVideoFPS WRITE setCamVideoFPS NOTIFY camVideoFPSChanged FINAL) public: enum class StyleType { NONE = 0, WITH_CHARS = 1, WITHOUT_CHARS = 2 }; enum class FriendListSortingMode { Name, Activity, }; public: static Settings& getInstance(); static void destroyInstance(); QString getSettingsDirPath() const; QString getAppDataDirPath() const; QString getAppCacheDirPath() const; void createSettingsDir(); void createPersonal(QString basename); void savePersonal(); void loadGlobal(); bool isToxPortable(); void loadPersonal(QString profileName, const ToxEncrypt* passKey); void resetToDefault(); struct Request { QString address; QString message; bool read; }; public slots: void saveGlobal(); void sync(); void setAutoLogin(bool state); void updateProfileData(Profile* profile, const QCommandLineParser* parser); signals: // General void autorunChanged(bool enabled); void autoSaveEnabledChanged(bool enabled); void autostartInTrayChanged(bool enabled); void closeToTrayChanged(bool enabled); void lightTrayIconChanged(bool enabled); void minimizeToTrayChanged(bool enabled); void notifyChanged(bool enabled); void desktopNotifyChanged(bool enabled); void showWindowChanged(bool enabled); void makeToxPortableChanged(bool enabled); void busySoundChanged(bool enabled); void notifySoundChanged(bool enabled); void notifyHideChanged(bool enabled); void groupAlwaysNotifyChanged(bool enabled); void translationChanged(const QString& translation); void currentProfileIdChanged(quint32 id); void enableLoggingChanged(bool enabled); void autoAwayTimeChanged(int minutes); void globalAutoAcceptDirChanged(const QString& path); void autoAcceptMaxSizeChanged(size_t size); void checkUpdatesChanged(bool enabled); void widgetDataChanged(const QString& key); // GUI void autoLoginChanged(bool enabled); void nameColorsChanged(bool enabled); void separateWindowChanged(bool enabled); void showSystemTrayChanged(bool enabled); bool minimizeOnCloseChanged(bool enabled); void windowGeometryChanged(const QByteArray& rect); void windowStateChanged(const QByteArray& state); void splitterStateChanged(const QByteArray& state); void dialogGeometryChanged(const QByteArray& rect); void dialogSplitterStateChanged(const QByteArray& state); void dialogSettingsGeometryChanged(const QByteArray& rect); void styleChanged(const QString& style); void themeColorChanged(int color); void compactLayoutChanged(bool enabled); void sortingModeChanged(FriendListSortingMode mode); void showIdenticonsChanged(bool enabled); // ChatView void useEmoticonsChanged(bool enabled); void smileyPackChanged(const QString& name); void emojiFontPointSizeChanged(int size); void dontGroupWindowsChanged(bool enabled); void groupchatPositionChanged(bool enabled); void chatMessageFontChanged(const QFont& font); void stylePreferenceChanged(StyleType type); void timestampFormatChanged(const QString& format); void dateFormatChanged(const QString& format); void statusChangeNotificationEnabledChanged(bool enabled); void spellCheckingEnabledChanged(bool enabled); // Privacy void typingNotificationChanged(bool enabled); void dbSyncTypeChanged(Db::syncType type); void blackListChanged(QStringList& blist); public: bool applyCommandLineOptions(const QCommandLineParser& parser); static bool verifyProxySettings(const QCommandLineParser& parser); bool getMakeToxPortable() const; void setMakeToxPortable(bool newValue); bool getAutorun() const; void setAutorun(bool newValue); bool getAutostartInTray() const; void setAutostartInTray(bool newValue); bool getCloseToTray() const; void setCloseToTray(bool newValue); bool getMinimizeToTray() const; void setMinimizeToTray(bool newValue); bool getLightTrayIcon() const; void setLightTrayIcon(bool newValue); QString getStyle() const; void setStyle(const QString& newValue); bool getShowSystemTray() const; void setShowSystemTray(bool newValue); bool getUseEmoticons() const; void setUseEmoticons(bool newValue); QString getCurrentProfile() const; uint32_t getCurrentProfileId() const; void setCurrentProfile(const QString& profile); QString getTranslation() const; void setTranslation(const QString& newValue); void setAutoSaveEnabled(bool newValue); bool getAutoSaveEnabled() const; // ICoreSettings bool getEnableIPv6() const override; void setEnableIPv6(bool enabled) override; bool getForceTCP() const override; void setForceTCP(bool enabled) override; bool getEnableLanDiscovery() const override; void setEnableLanDiscovery(bool enabled) override; QString getProxyAddr() const override; void setProxyAddr(const QString& address) override; ICoreSettings::ProxyType getProxyType() const override; void setProxyType(ICoreSettings::ProxyType type) override; quint16 getProxyPort() const override; void setProxyPort(quint16 port) override; QNetworkProxy getProxy() const override; SIGNAL_IMPL(Settings, enableIPv6Changed, bool enabled) SIGNAL_IMPL(Settings, forceTCPChanged, bool enabled) SIGNAL_IMPL(Settings, enableLanDiscoveryChanged, bool enabled) SIGNAL_IMPL(Settings, proxyTypeChanged, ICoreSettings::ProxyType type) SIGNAL_IMPL(Settings, proxyAddressChanged, const QString& address) SIGNAL_IMPL(Settings, proxyPortChanged, quint16 port) bool getEnableLogging() const; void setEnableLogging(bool newValue); Db::syncType getDbSyncType() const; void setDbSyncType(Db::syncType newValue); int getAutoAwayTime() const; void setAutoAwayTime(int newValue); bool getCheckUpdates() const; void setCheckUpdates(bool newValue); bool getNotify() const; void setNotify(bool newValue); bool getShowWindow() const; void setShowWindow(bool newValue); bool getDesktopNotify() const; void setDesktopNotify(bool enabled); bool getNotifySound() const; void setNotifySound(bool newValue); bool getNotifyHide() const; void setNotifyHide(bool newValue); bool getBusySound() const; void setBusySound(bool newValue); bool getGroupAlwaysNotify() const override; void setGroupAlwaysNotify(bool newValue) override; QString getInDev() const override; void setInDev(const QString& deviceSpecifier) override; bool getAudioInDevEnabled() const override; void setAudioInDevEnabled(bool enabled) override; QString getOutDev() const override; void setOutDev(const QString& deviceSpecifier) override; bool getAudioOutDevEnabled() const override; void setAudioOutDevEnabled(bool enabled) override; qreal getAudioInGainDecibel() const override; void setAudioInGainDecibel(qreal dB) override; qreal getAudioThreshold() const override; void setAudioThreshold(qreal percent) override; int getOutVolume() const override; int getOutVolumeMin() const override { return 0; } int getOutVolumeMax() const override { return 100; } void setOutVolume(int volume) override; int getAudioBitrate() const override; void setAudioBitrate(int bitrate) override; bool getEnableTestSound() const override; void setEnableTestSound(bool newValue) override; SIGNAL_IMPL(Settings, inDevChanged, const QString& device) SIGNAL_IMPL(Settings, audioInDevEnabledChanged, bool enabled) SIGNAL_IMPL(Settings, outDevChanged, const QString& device) SIGNAL_IMPL(Settings, audioOutDevEnabledChanged, bool enabled) SIGNAL_IMPL(Settings, audioInGainDecibelChanged, qreal dB) SIGNAL_IMPL(Settings, audioThresholdChanged, qreal percent) SIGNAL_IMPL(Settings, outVolumeChanged, int volume) SIGNAL_IMPL(Settings, audioBitrateChanged, int bitrate) SIGNAL_IMPL(Settings, enableTestSoundChanged, bool newValue) QString getVideoDev() const override; void setVideoDev(const QString& deviceSpecifier) override; QRect getScreenRegion() const override; void setScreenRegion(const QRect& value) override; bool getScreenGrabbed() const override; void setScreenGrabbed(bool value) override; QRect getCamVideoRes() const override; void setCamVideoRes(QRect newValue) override; float getCamVideoFPS() const override; void setCamVideoFPS(float newValue) override; SIGNAL_IMPL(Settings, videoDevChanged, const QString& device) SIGNAL_IMPL(Settings, screenRegionChanged, const QRect& region) SIGNAL_IMPL(Settings, screenGrabbedChanged, bool enabled) SIGNAL_IMPL(Settings, camVideoResChanged, const QRect& region) SIGNAL_IMPL(Settings, camVideoFPSChanged, unsigned short fps) bool isAnimationEnabled() const; void setAnimationEnabled(bool newValue); QString getSmileyPack() const; void setSmileyPack(const QString& value); int getThemeColor() const; void setThemeColor(int value); StyleType getStylePreference() const; void setStylePreference(StyleType newValue); bool isCurstomEmojiFont() const; void setCurstomEmojiFont(bool value); int getEmojiFontPointSize() const; void setEmojiFontPointSize(int value); QString getContactNote(const ToxPk& id) const override; void setContactNote(const ToxPk& id, const QString& note) override; QString getAutoAcceptDir(const ToxPk& id) const override; void setAutoAcceptDir(const ToxPk& id, const QString& dir) override; AutoAcceptCallFlags getAutoAcceptCall(const ToxPk& id) const override; void setAutoAcceptCall(const ToxPk& id, AutoAcceptCallFlags accept) override; QString getGlobalAutoAcceptDir() const; void setGlobalAutoAcceptDir(const QString& dir); size_t getMaxAutoAcceptSize() const; void setMaxAutoAcceptSize(size_t size); bool getAutoGroupInvite(const ToxPk& id) const override; void setAutoGroupInvite(const ToxPk& id, bool accept) override; // ChatView const QFont& getChatMessageFont() const; void setChatMessageFont(const QFont& font); const QString& getTimestampFormat() const; void setTimestampFormat(const QString& format); const QString& getDateFormat() const; void setDateFormat(const QString& format); bool getMinimizeOnClose() const; void setMinimizeOnClose(bool newValue); bool getStatusChangeNotificationEnabled() const; void setStatusChangeNotificationEnabled(bool newValue); bool getSpellCheckingEnabled() const; void setSpellCheckingEnabled(bool newValue); // Privacy bool getTypingNotification() const; void setTypingNotification(bool enabled); QStringList getBlackList() const override; void setBlackList(const QStringList& blist) override; // State QByteArray getWindowGeometry() const; void setWindowGeometry(const QByteArray& value); QByteArray getWindowState() const; void setWindowState(const QByteArray& value); QByteArray getSplitterState() const; void setSplitterState(const QByteArray& value); QByteArray getDialogGeometry() const; void setDialogGeometry(const QByteArray& value); QByteArray getDialogSplitterState() const; void setDialogSplitterState(const QByteArray& value); QByteArray getDialogSettingsGeometry() const; void setDialogSettingsGeometry(const QByteArray& value); QString getFriendAddress(const QString& publicKey) const; void updateFriendAddress(const QString& newAddr); QString getFriendAlias(const ToxPk& id) const override; void setFriendAlias(const ToxPk& id, const QString& alias) override; int getFriendCircleID(const ToxPk& id) const override; void setFriendCircleID(const ToxPk& id, int circleID) override; QDateTime getFriendActivity(const ToxPk& id) const override; void setFriendActivity(const ToxPk& id, const QDateTime& date) override; void saveFriendSettings(const ToxPk& id) override; void removeFriendSettings(const ToxPk& id) override; SIGNAL_IMPL(Settings, autoAcceptCallChanged, const ToxPk& id, IFriendSettings::AutoAcceptCallFlags accept) SIGNAL_IMPL(Settings, autoGroupInviteChanged, const ToxPk& id, bool accept) SIGNAL_IMPL(Settings, autoAcceptDirChanged, const ToxPk& id, const QString& dir) SIGNAL_IMPL(Settings, contactNoteChanged, const ToxPk& id, const QString& note) bool getCompactLayout() const; void setCompactLayout(bool compact); FriendListSortingMode getFriendSortingMode() const; void setFriendSortingMode(FriendListSortingMode mode); bool getSeparateWindow() const; void setSeparateWindow(bool value); bool getDontGroupWindows() const; void setDontGroupWindows(bool value); bool getGroupchatPosition() const; void setGroupchatPosition(bool value); bool getShowIdenticons() const; void setShowIdenticons(bool value); bool getAutoLogin() const; void setEnableGroupChatsColor(bool state); bool getEnableGroupChatsColor() const; int getCircleCount() const; int addCircle(const QString& name = QString()); int removeCircle(int id); QString getCircleName(int id) const; void setCircleName(int id, const QString& name); bool getCircleExpanded(int id) const; void setCircleExpanded(int id, bool expanded); bool addFriendRequest(const QString& friendAddress, const QString& message); unsigned int getUnreadFriendRequests() const; Request getFriendRequest(int index) const; int getFriendRequestSize() const; void clearUnreadFriendRequests(); void removeFriendRequest(int index); void readFriendRequest(int index); QByteArray getWidgetData(const QString& uniqueName) const; void setWidgetData(const QString& uniqueName, const QByteArray& data); // Wrappers around getWidgetData() and setWidgetData() // Assume widget has a unique objectName set template void restoreGeometryState(T* widget) const { widget->restoreGeometry(getWidgetData(widget->objectName() + "Geometry")); widget->restoreState(getWidgetData(widget->objectName() + "State")); } template void saveGeometryState(const T* widget) { setWidgetData(widget->objectName() + "Geometry", widget->saveGeometry()); setWidgetData(widget->objectName() + "State", widget->saveState()); } static uint32_t makeProfileId(const QString& profile); private: struct friendProp; Settings(); ~Settings(); Settings(Settings& settings) = delete; Settings& operator=(const Settings&) = delete; void savePersonal(QString profileName, const ToxEncrypt* passkey); friendProp& getOrInsertFriendPropRef(const ToxPk& id); ICoreSettings::ProxyType fixInvalidProxyType(ICoreSettings::ProxyType proxyType); public slots: void savePersonal(Profile* profile); private: bool loaded; bool useCustomDhtList; int dhtServerId; bool dontShowDhtDialog; bool autoLogin; bool compactLayout; FriendListSortingMode sortingMode; bool groupchatPosition; bool separateWindow; bool dontGroupWindows; bool showIdenticons; bool enableIPv6; QString translation; bool makeToxPortable; bool autostartInTray; bool closeToTray; bool minimizeToTray; bool lightTrayIcon; bool useEmoticons; bool checkUpdates; bool notify; bool desktopNotify; bool showWindow; bool notifySound; bool notifyHide; bool busySound; bool groupAlwaysNotify; bool nameColors; bool forceTCP; bool enableLanDiscovery; ICoreSettings::ProxyType proxyType; QString proxyAddr; quint16 proxyPort; QString currentProfile; uint32_t currentProfileId; bool enableLogging; int autoAwayTime; QHash widgetSettings; QHash autoAccept; bool autoSaveEnabled; QString globalAutoAcceptDir; size_t autoAcceptMaxSize; QList friendRequests; // GUI QString smileyPack; int emojiFontPointSize; bool minimizeOnClose; QByteArray windowGeometry; QByteArray windowState; QByteArray splitterState; QByteArray dialogGeometry; QByteArray dialogSplitterState; QByteArray dialogSettingsGeometry; QString style; bool showSystemTray; // ChatView QFont chatMessageFont; StyleType stylePreference; int firstColumnHandlePos; int secondColumnHandlePosFromRight; QString timestampFormat; QString dateFormat; bool statusChangeNotificationEnabled; bool spellCheckingEnabled; // Privacy bool typingNotification; Db::syncType dbSyncType; QStringList blackList; // Audio QString inDev; bool audioInDevEnabled; qreal audioInGainDecibel; qreal audioThreshold; QString outDev; bool audioOutDevEnabled; int outVolume; int audioBitrate; bool enableTestSound; // Video QString videoDev; QRect camVideoRes; QRect screenRegion; bool screenGrabbed; float camVideoFPS; struct friendProp { friendProp() = delete; friendProp(QString addr) : addr(addr) {} QString alias = ""; QString addr = ""; QString autoAcceptDir = ""; QString note = ""; int circleID = -1; QDateTime activity = QDateTime(); AutoAcceptCallFlags autoAcceptCall; bool autoGroupInvite = false; }; struct circleProp { QString name; bool expanded; }; QHash friendLst; QVector circleLst; int themeColor; static CompatibleRecursiveMutex bigLock; static Settings* settings; static const QString globalSettingsFile; static QThread* settingsThread; }; #endif // SETTINGS_HPP qTox/src/persistence/settingsserializer.cpp000066400000000000000000000403461415623743500216030ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "settingsserializer.h" #include "serialize.h" #include "src/core/toxencrypt.h" #include "src/persistence/profile.h" #include #include #include #include #include /** * @class SettingsSerializer * @brief Serializes a QSettings's data in an (optionally) encrypted binary format. * SettingsSerializer can detect regular .ini files and serialized ones, * it will read both regular and serialized .ini, but only save in serialized format. * The file is encrypted with the current profile's password, if any. * The file is only written to disk if save() is called, the destructor does not save to disk * All member functions are reentrant, but not thread safe. * * @enum SettingsSerializer::RecordTag * @var Value * Followed by a QString key then a QVariant value * @var GroupStart * Followed by a QString group name * @var ArrayStart * Followed by a QString array name and a vuint array size * @var ArrayValue * Followed by a vuint array index, a QString key then a QVariant value * @var ArrayEnd * Not followed by any data */ enum class RecordTag : uint8_t { }; /** * @var static const char magic[]; * @brief Little endian ASCII "QTOX" magic */ const char SettingsSerializer::magic[] = {0x51, 0x54, 0x4F, 0x58}; QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag) { return dataStream << static_cast(tag); } QDataStream& writeStream(QDataStream& dataStream, const QByteArray& data) { QByteArray size = vintToData(data.size()); dataStream.writeRawData(size.data(), size.size()); dataStream.writeRawData(data.data(), data.size()); return dataStream; } QDataStream& writeStream(QDataStream& dataStream, const QString& str) { return writeStream(dataStream, str.toUtf8()); } QDataStream& readStream(QDataStream& dataStream, SettingsSerializer::RecordTag& tag) { return dataStream >> reinterpret_cast(tag); } QDataStream& readStream(QDataStream& dataStream, QByteArray& data) { char num3; int num = 0; int num2 = 0; do { dataStream.readRawData(&num3, 1); num |= (num3 & 0x7f) << num2; num2 += 7; } while ((num3 & 0x80) != 0); data.resize(num); dataStream.readRawData(data.data(), num); return dataStream; } SettingsSerializer::SettingsSerializer(QString filePath, const ToxEncrypt* passKey) : path{filePath} , passKey{passKey} , group{-1} , array{-1} , arrayIndex{-1} { } void SettingsSerializer::beginGroup(const QString& prefix) { if (prefix.isEmpty()) endGroup(); int index = groups.indexOf(prefix); if (index >= 0) { group = index; } else { group = groups.size(); groups.append(prefix); } } void SettingsSerializer::endGroup() { group = -1; } int SettingsSerializer::beginReadArray(const QString& prefix) { auto index = std::find_if(std::begin(arrays), std::end(arrays), [=](const Array& a) { return a.name == prefix; }); if (index != std::end(arrays)) { array = static_cast(index - std::begin(arrays)); arrayIndex = -1; return index->size; } else { array = arrays.size(); arrays.push_back({group, 0, prefix, {}}); arrayIndex = -1; return 0; } } void SettingsSerializer::beginWriteArray(const QString& prefix, int size) { auto index = std::find_if(std::begin(arrays), std::end(arrays), [=](const Array& a) { return a.name == prefix; }); if (index != std::end(arrays)) { array = static_cast(index - std::begin(arrays)); arrayIndex = -1; if (size > 0) index->size = std::max(index->size, size); } else { if (size < 0) size = 0; array = arrays.size(); arrays.push_back({group, size, prefix, {}}); arrayIndex = -1; } } void SettingsSerializer::endArray() { array = -1; } void SettingsSerializer::setArrayIndex(int i) { arrayIndex = i; } void SettingsSerializer::setValue(const QString& key, const QVariant& value) { Value* v = findValue(key); if (v) { v->value = value; } else { Value nv{group, array, arrayIndex, key, value}; if (array >= 0) arrays[array].values.append(values.size()); values.append(nv); } } QVariant SettingsSerializer::value(const QString& key, const QVariant& defaultValue) const { const Value* v = findValue(key); if (v) return v->value; else return defaultValue; } const SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key) const { if (array != -1) { for (const Array& a : arrays) { if (a.group != group) continue; for (int vi : a.values) { const Value& v = values[vi]; if (v.arrayIndex == arrayIndex && v.key == key) return &v; } } } else { for (const Value& v : values) if (v.group == group && v.array == -1 && v.key == key) return &v; } return nullptr; } SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key) { return const_cast(const_cast(this)->findValue(key)); } /** * @brief Checks if the file is serialized settings. * @param filePath Path to file to check. * @return False on error, true otherwise. */ bool SettingsSerializer::isSerializedFormat(QString filePath) { QFile f(filePath); if (!f.open(QIODevice::ReadOnly)) return false; char fmagic[8]; if (f.read(fmagic, sizeof(fmagic)) != sizeof(fmagic)) return false; return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted(reinterpret_cast(fmagic)); } /** * @brief Loads the settings from file. */ void SettingsSerializer::load() { if (isSerializedFormat(path)) readSerialized(); else readIni(); } /** * @brief Saves the current settings back to file */ void SettingsSerializer::save() { QSaveFile f(path); if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) { qWarning() << "Couldn't open file"; return; } QByteArray data(magic, 4); QDataStream stream(&data, QIODevice::ReadWrite | QIODevice::Append); stream.setVersion(QDataStream::Qt_5_0); // prevent signed overflow and the associated warning int numGroups = std::max(0, groups.size()); for (int g = -1; g < numGroups; ++g) { // Save the group name, if any if (g != -1) { writeStream(stream, RecordTag::GroupStart); writeStream(stream, groups[g].toUtf8()); } // Save all the arrays of this group for (const Array& a : arrays) { if (a.group != g) continue; if (a.size <= 0) continue; writeStream(stream, RecordTag::ArrayStart); writeStream(stream, a.name.toUtf8()); writeStream(stream, vintToData(a.size)); for (int vi : a.values) { const Value& v = values[vi]; writeStream(stream, RecordTag::ArrayValue); writeStream(stream, vintToData(values[vi].arrayIndex)); writeStream(stream, v.key.toUtf8()); writePackedVariant(stream, v.value); } writeStream(stream, RecordTag::ArrayEnd); } // Save all the values of this group that aren't in an array for (const Value& v : values) { if (v.group != g || v.array != -1) continue; writeStream(stream, RecordTag::Value); writeStream(stream, v.key.toUtf8()); writePackedVariant(stream, v.value); } } // Encrypt if (passKey) { data = passKey->encrypt(data); } f.write(data); // check if everything got written if (f.flush()) { f.commit(); } else { f.cancelWriting(); qCritical() << "Failed to write, can't save!"; } } void SettingsSerializer::readSerialized() { QFile f(path); if (!f.open(QIODevice::ReadOnly)) { qWarning() << "Couldn't open file"; return; } QByteArray data = f.readAll(); f.close(); // Decrypt if (ToxEncrypt::isEncrypted(data)) { if (!passKey) { qCritical() << "The settings file is encrypted, but we don't have a passkey!"; return; } data = passKey->decrypt(data); if (data.isEmpty()) { qCritical() << "Failed to decrypt the settings file"; return; } } else { if (passKey) qWarning() << "We have a password, but the settings file is not encrypted"; } if (memcmp(data.data(), magic, 4)) { qWarning() << "Bad magic!"; return; } data = data.mid(4); QDataStream stream(&data, QIODevice::ReadOnly); stream.setVersion(QDataStream::Qt_5_0); while (!stream.atEnd()) { RecordTag tag; readStream(stream, tag); if (tag == RecordTag::Value) { QByteArray key; QByteArray value; readStream(stream, key); readStream(stream, value); setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value))); } else if (tag == RecordTag::GroupStart) { QByteArray prefix; readStream(stream, prefix); beginGroup(QString::fromUtf8(prefix)); } else if (tag == RecordTag::ArrayStart) { QByteArray prefix; readStream(stream, prefix); beginReadArray(QString::fromUtf8(prefix)); QByteArray sizeData; readStream(stream, sizeData); if (sizeData.isEmpty()) { qWarning("The personal save file is corrupted!"); return; } int size = dataToVInt(sizeData); arrays[array].size = qMax(size, arrays[array].size); } else if (tag == RecordTag::ArrayValue) { QByteArray indexData; readStream(stream, indexData); if (indexData.isEmpty()) { qWarning("The personal save file is corrupted!"); return; } setArrayIndex(dataToVInt(indexData)); QByteArray key; QByteArray value; readStream(stream, key); readStream(stream, value); setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value))); } else if (tag == RecordTag::ArrayEnd) { endArray(); } } group = array = -1; } void SettingsSerializer::readIni() { QSettings s(path, QSettings::IniFormat); // Read all keys of all groups, reading arrays as raw keys QList gstack; do { // Add all keys if (!s.group().isEmpty()) beginGroup(s.group()); for (QString k : s.childKeys()) { setValue(k, s.value(k)); } // Add all groups gstack.push_back(QString()); for (QString g : s.childGroups()) gstack.push_back(g); // Visit the next group, if any while (!gstack.isEmpty()) { QString g = gstack.takeLast(); if (g.isEmpty()) { if (gstack.isEmpty()) break; else s.endGroup(); } else { s.beginGroup(g); break; } } } while (!gstack.isEmpty()); // We can convert keys that look like arrays into real arrays // If a group's only key is called size, we'll consider it to be an array, // and its elements are all groups matching the pattern "[/]/" // Find groups that only have 1 key std::unique_ptr groupSizes{new int[groups.size()]}; memset(groupSizes.get(), 0, static_cast(groups.size()) * sizeof(int)); for (const Value& v : values) { if (v.group < 0 || v.group > groups.size()) continue; groupSizes[static_cast(v.group)]++; } // Find arrays, remove their size key from the values, and add them to `arrays` QVector groupsToKill; for (int i = values.size() - 1; i >= 0; i--) { const Value& v = values[i]; if (v.group < 0 || v.group > groups.size()) continue; if (groupSizes[static_cast(v.group)] != 1) continue; if (v.key != "size") continue; if (!v.value.canConvert(QVariant::Int)) continue; Array a; a.size = v.value.toInt(); int slashIndex = groups[static_cast(v.group)].lastIndexOf('/'); if (slashIndex == -1) { a.group = -1; a.name = groups[static_cast(v.group)]; a.size = v.value.toInt(); } else { a.group = -1; for (int i = 0; i < groups.size(); ++i) if (groups[i] == groups[static_cast(v.group)].left(slashIndex)) a.group = i; a.name = groups[static_cast(v.group)].mid(slashIndex + 1); } groupSizes[static_cast(v.group)]--; groupsToKill.append(static_cast(v.group)); arrays.append(a); values.removeAt(i); } // Associate each array's values with the array for (int ai = 0; ai < arrays.size(); ++ai) { Array& a = arrays[ai]; QString arrayPrefix; if (a.group != -1) arrayPrefix += groups[static_cast(a.group)] + '/'; arrayPrefix += a.name + '/'; // Find groups which represent each array index for (int g = 0; g < groups.size(); ++g) { if (!groups[g].startsWith(arrayPrefix)) continue; bool ok; int groupArrayIndex = groups[g].mid(arrayPrefix.size()).toInt(&ok); if (!ok) continue; groupsToKill.append(g); if (groupArrayIndex > a.size) a.size = groupArrayIndex; // Associate the values for this array index for (int vi = values.size() - 1; vi >= 0; vi--) { Value& v = values[vi]; if (v.group != g) continue; groupSizes[static_cast(g)]--; v.group = a.group; v.array = ai; v.arrayIndex = groupArrayIndex; a.values.append(vi); } } } // Clean up spurious array element groups std::sort(std::begin(groupsToKill), std::end(groupsToKill), std::greater_equal()); for (int g : groupsToKill) { if (groupSizes[static_cast(g)]) continue; removeGroup(g); } group = array = -1; } /** * @brief Remove group. * @note The group must be empty. * @param group ID of group to remove. */ void SettingsSerializer::removeGroup(int group) { assert(group < groups.size()); for (Array& a : arrays) { assert(a.group != group); if (a.group > group) a.group--; } for (Value& v : values) { assert(v.group != group); if (v.group > group) v.group--; } groups.removeAt(group); } void SettingsSerializer::writePackedVariant(QDataStream& stream, const QVariant& v) { assert(v.canConvert(QVariant::String)); QString str = v.toString(); if (str == "true") writeStream(stream, QString("1")); else if (str == "false") writeStream(stream, QString("0")); else writeStream(stream, str.toUtf8()); } qTox/src/persistence/settingsserializer.h000066400000000000000000000060571415623743500212510ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SETTINGSSERIALIZER_H #define SETTINGSSERIALIZER_H #include "src/core/toxencrypt.h" #include #include #include #include class SettingsSerializer { public: SettingsSerializer(QString filePath, const ToxEncrypt* passKey = nullptr); static bool isSerializedFormat(QString filePath); void load(); void save(); void beginGroup(const QString& prefix); void endGroup(); int beginReadArray(const QString& prefix); void beginWriteArray(const QString& prefix, int size = -1); void endArray(); void setArrayIndex(int i); void setValue(const QString& key, const QVariant& value); QVariant value(const QString& key, const QVariant& defaultValue = QVariant()) const; private: enum class RecordTag : uint8_t { Value = 0, GroupStart = 1, ArrayStart = 2, ArrayValue = 3, ArrayEnd = 4, }; friend QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag); friend QDataStream& readStream(QDataStream& dataStream, SettingsSerializer::RecordTag& tag); struct Value { Value() : group{-2} , array{-2} , arrayIndex{-2} , key{QString()} , value{} { } Value(qint64 group, qint64 array, int arrayIndex, QString key, QVariant value) : group{group} , array{array} , arrayIndex{arrayIndex} , key{key} , value{value} { } qint64 group; qint64 array; int arrayIndex; QString key; QVariant value; }; struct Array { qint64 group; int size; QString name; QVector values; }; private: const Value* findValue(const QString& key) const; Value* findValue(const QString& key); void readSerialized(); void readIni(); void removeValue(const QString& key); void removeGroup(int group); void writePackedVariant(QDataStream& dataStream, const QVariant& v); private: QString path; const ToxEncrypt* passKey; int group, array, arrayIndex; QStringList groups; QVector arrays; QVector values; static const char magic[]; }; #endif // SETTINGSSERIALIZER_H qTox/src/persistence/smileypack.cpp000066400000000000000000000242651415623743500200140ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "smileypack.h" #include "src/persistence/settings.h" #include #include #include #include #include #include #include #if defined(Q_OS_FREEBSD) #include #endif /** * @class SmileyPack * @brief Maps emoticons to smileys. * * @var SmileyPack::filenameTable * @brief Matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png" * * @var SmileyPack::iconCache * @brief representation of a smiley ie. "happy.png" -> data * * @var SmileyPack::emoticons * @brief {{ ":)", ":-)" }, {":(", ...}, ... } * * @var SmileyPack::path * @brief directory containing the cfg and image files * * @var SmileyPack::defaultPaths * @brief Contains all directories where smileys could be found */ QStringList loadDefaultPaths(); static const QStringList DEFAULT_PATHS = loadDefaultPaths(); static const QString RICH_TEXT_PATTERN = QStringLiteral(""); static const QString EMOTICONS_FILE_NAME = QStringLiteral("emoticons.xml"); static constexpr int CLEANUP_TIMEOUT = 5 * 60 * 1000; // 5 minutes /** * @brief Construct list of standard directories with "emoticons" sub dir, whether these directories * exist or not * @return Constructed list of default emoticons directories */ QStringList loadDefaultPaths() { #if defined(Q_OS_FREEBSD) // TODO: Remove when will be fixed. // Workaround to fix https://bugreports.qt.io/browse/QTBUG-57522 setlocale(LC_ALL, ""); #endif const QString EMOTICONS_SUB_PATH = QDir::separator() + QStringLiteral("emoticons"); QStringList paths{":/smileys", "~/.kde4/share/emoticons", "~/.kde/share/emoticons", EMOTICONS_SUB_PATH}; // qTox exclusive emoticons QStandardPaths::StandardLocation location; location = QStandardPaths::AppDataLocation; QStringList locations = QStandardPaths::standardLocations(location); // system wide emoticons locations.append(QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)); for (QString qtoxPath : locations) { qtoxPath.append(EMOTICONS_SUB_PATH); if (!paths.contains(qtoxPath)) { paths.append(qtoxPath); } } return paths; } /** * @brief Wraps passed string into smiley HTML image reference * @param key Describes which smiley is needed * @return Key that wrapped into image ref */ QString getAsRichText(const QString& key) { return RICH_TEXT_PATTERN.arg(key); } SmileyPack::SmileyPack() : cleanupTimer{new QTimer(this)} { loadingMutex.lock(); QtConcurrent::run(this, &SmileyPack::load, Settings::getInstance().getSmileyPack()); connect(&Settings::getInstance(), &Settings::smileyPackChanged, this, &SmileyPack::onSmileyPackChanged); connect(cleanupTimer, &QTimer::timeout, this, &SmileyPack::cleanupIconsCache); cleanupTimer->start(CLEANUP_TIMEOUT); } SmileyPack::~SmileyPack() { delete cleanupTimer; } void SmileyPack::cleanupIconsCache() { QMutexLocker locker(&loadingMutex); for (auto it = cachedIcon.begin(); it != cachedIcon.end();) { std::shared_ptr& icon = it->second; if (icon.use_count() == 1) { it = cachedIcon.erase(it); } else { ++it; } } } /** * @brief Returns the singleton instance. */ SmileyPack& SmileyPack::getInstance() { static SmileyPack smileyPack; return smileyPack; } /** * @brief Does the same as listSmileyPaths, but with default paths */ QList> SmileyPack::listSmileyPacks() { return listSmileyPacks(DEFAULT_PATHS); } /** * @brief Searches all files called "emoticons.xml" within the every passed path in the depth of 2 * @param paths Paths where to search for file * @return Vector of pairs: {directoryName, absolutePathToFile} */ QList> SmileyPack::listSmileyPacks(const QStringList& paths) { QList> smileyPacks; const QString homePath = QDir::homePath(); for (QString path : paths) { if (path.startsWith('~')) { path.replace(0, 1, homePath); } QDir dir(path); if (!dir.exists()) { continue; } for (const QString& subdirectory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { dir.cd(subdirectory); if (dir.exists(EMOTICONS_FILE_NAME)) { QString absPath = dir.absolutePath() + QDir::separator() + EMOTICONS_FILE_NAME; QPair p{dir.dirName(), absPath}; if (!smileyPacks.contains(p)) { smileyPacks.append(p); } } dir.cdUp(); } } return smileyPacks; } /** * @brief Load smile pack * @note The caller must lock loadingMutex and should run it in a thread * @param filename Filename of smilepack. * @return False if cannot open file, true otherwise. */ bool SmileyPack::load(const QString& filename) { QFile xmlFile(filename); if (!xmlFile.exists() || !xmlFile.open(QIODevice::ReadOnly)) { loadingMutex.unlock(); return false; } QDomDocument doc; doc.setContent(xmlFile.readAll()); xmlFile.close(); /* parse the cfg file * sample: * * * * :) * :-) * * * :( * :-( * * */ path = QFileInfo(filename).absolutePath(); QDomNodeList emoticonElements = doc.elementsByTagName("emoticon"); const QString itemName = QStringLiteral("file"); const QString childName = QStringLiteral("string"); const int iconsCount = emoticonElements.size(); emoticons.clear(); emoticonToPath.clear(); cachedIcon.clear(); for (int i = 0; i < iconsCount; ++i) { QDomNode node = emoticonElements.at(i); QString iconName = node.attributes().namedItem(itemName).nodeValue(); QString iconPath = QDir{path}.filePath(iconName); QDomElement stringElement = node.firstChildElement(childName); QStringList emoticonList; while (!stringElement.isNull()) { QString emoticon = stringElement.text().replace("<", "<").replace(">", ">"); emoticonToPath.insert(emoticon, iconPath); emoticonList.append(emoticon); stringElement = stringElement.nextSibling().toElement(); } emoticons.append(emoticonList); } constructRegex(); loadingMutex.unlock(); return true; } /** * @brief Creates the regex for replacing emoticons with the path to their pictures */ void SmileyPack::constructRegex() { QString allPattern = QStringLiteral("("); // construct one big regex that matches on every emoticon for (const QString& emote : emoticonToPath.keys()) { if (emote.toUcs4().length() == 1) { // UTF-8 emoji allPattern = allPattern % emote; } else { // patterns like ":)" or ":smile:", don't match inside a word or else will hit punctuation and html tags allPattern = allPattern % QStringLiteral(R"((?<=^|\s))") % QRegularExpression::escape(emote) % QStringLiteral(R"((?=$|\s))"); } allPattern = allPattern % QStringLiteral("|"); } allPattern[allPattern.size() - 1] = QChar(')'); // compile and optimize regex smilify.setPattern(allPattern); smilify.optimize(); } /** * @brief Replaces all found text emoticons to HTML reference with its according icon filename * @param msg Message where to search for emoticons * @return Formatted copy of message */ QString SmileyPack::smileyfied(const QString& msg) { QMutexLocker locker(&loadingMutex); QString result(msg); int replaceDiff = 0; QRegularExpressionMatchIterator iter = smilify.globalMatch(result); while (iter.hasNext()) { QRegularExpressionMatch match = iter.next(); int startPos = match.capturedStart(); int keyLength = match.capturedLength(); QString imgRichText = getAsRichText(match.captured()); result.replace(startPos + replaceDiff, keyLength, imgRichText); replaceDiff += imgRichText.length() - keyLength; } return result; } /** * @brief Returns all emoticons that was extracted from files, grouped by according icon file */ QList SmileyPack::getEmoticons() const { QMutexLocker locker(&loadingMutex); return emoticons; } /** * @brief Gets icon accoring to passed emoticon * @param emoticon Passed emoticon * @return Returns cached icon according to passed emoticon, null if no icon mapped to this emoticon */ std::shared_ptr SmileyPack::getAsIcon(const QString& emoticon) const { QMutexLocker locker(&loadingMutex); if (cachedIcon.find(emoticon) != cachedIcon.end()) { return cachedIcon[emoticon]; } const auto iconPathIt = emoticonToPath.find(emoticon); if (iconPathIt == emoticonToPath.end()) { return std::make_shared(); } const QString& iconPath = iconPathIt.value(); auto icon = std::make_shared(iconPath); cachedIcon[emoticon] = icon; return icon; } void SmileyPack::onSmileyPackChanged() { loadingMutex.lock(); QtConcurrent::run(this, &SmileyPack::load, Settings::getInstance().getSmileyPack()); } qTox/src/persistence/smileypack.h000066400000000000000000000035541415623743500174570ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SMILEYPACK_H #define SMILEYPACK_H #include #include #include #include #include class QTimer; class SmileyPack : public QObject { Q_OBJECT public: static SmileyPack& getInstance(); static QList> listSmileyPacks(const QStringList& paths); static QList> listSmileyPacks(); QString smileyfied(const QString& msg); QList getEmoticons() const; std::shared_ptr getAsIcon(const QString& key) const; private slots: void onSmileyPackChanged(); void cleanupIconsCache(); private: SmileyPack(); SmileyPack(SmileyPack&) = delete; SmileyPack& operator=(const SmileyPack&) = delete; ~SmileyPack() override; bool load(const QString& filename); void constructRegex(); mutable std::map> cachedIcon; QHash emoticonToPath; QList emoticons; QString path; QTimer* cleanupTimer; QRegularExpression smilify; mutable QMutex loadingMutex; }; #endif // SMILEYPACK_H qTox/src/persistence/toxsave.cpp000066400000000000000000000025421415623743500173360ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toxsave.h" #include "src/widget/gui.h" #include "src/widget/tool/profileimporter.h" #include bool toxSaveEventHandler(const QByteArray& eventData) { if (!eventData.endsWith(".tox")) { return false; } handleToxSave(eventData); return true; } /** * @brief Import new profile. * @note Will wait until the core is ready first. * @param path Path to .tox file. * @return True if import success, false, otherwise. */ bool handleToxSave(const QString& path) { ProfileImporter importer(GUI::getMainWidget()); return importer.importProfile(path); } qTox/src/persistence/toxsave.h000066400000000000000000000016761415623743500170120ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOXSAVE_H #define TOXSAVE_H class QString; class QByteArray; bool handleToxSave(const QString& path); // Internals bool toxSaveEventHandler(const QByteArray& eventData); #endif qTox/src/platform/000077500000000000000000000000001415623743500144365ustar00rootroot00000000000000qTox/src/platform/autorun.h000066400000000000000000000017221415623743500163060ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifdef QTOX_PLATFORM_EXT #ifndef PLATFORM_AUTORUN_H #define PLATFORM_AUTORUN_H namespace Platform { bool setAutorun(bool on); bool getAutorun(); } #endif // PLATFORM_AUTORUN_H #endif // QTOX_PLATFORM_EXT qTox/src/platform/autorun_osx.cpp000066400000000000000000000031551415623743500175340ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/platform/autorun.h" #include #include #include #include #include int state; bool Platform::setAutorun(bool on) { QString qtoxPlist = QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "Library" + QDir::separator() + "LaunchAgents" + QDir::separator() + "chat.tox.qtox.autorun.plist"); QString qtoxDir = QDir::cleanPath(QCoreApplication::applicationDirPath() + QDir::separator() + "qtox"); QSettings autoRun(qtoxPlist, QSettings::NativeFormat); autoRun.setValue("Label", "chat.tox.qtox.autorun"); autoRun.setValue("Program", qtoxDir); state = on; autoRun.setValue("RunAtLoad", state); return true; } bool Platform::getAutorun() { return state; } qTox/src/platform/autorun_win.cpp000066400000000000000000000057001415623743500175160ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/persistence/settings.h" #include "src/platform/autorun.h" #include #include #ifdef UNICODE /** * tstring is either std::wstring or std::string, depending on whether the user * is building a Unicode or Multi-Byte version of qTox. This makes the code * easier to reuse and compatible with both setups. */ using tstring = std::wstring; static inline tstring toTString(QString s) { return s.toStdWString(); } #else using tstring = std::string; static inline tstring toTString(QString s) { return s.toStdString(); } #endif namespace Platform { inline tstring currentCommandLine() { return toTString("\"" + QApplication::applicationFilePath().replace('/', '\\') + "\" -p \"" + Settings::getInstance().getCurrentProfile() + "\""); } inline tstring currentRegistryKeyName() { return toTString("qTox - " + Settings::getInstance().getCurrentProfile()); } } bool Platform::setAutorun(bool on) { HKEY key = 0; if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), 0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS) return false; bool result = false; tstring keyName = currentRegistryKeyName(); if (on) { tstring path = currentCommandLine(); result = RegSetValueEx(key, keyName.c_str(), 0, REG_SZ, (PBYTE)path.c_str(), path.length() * sizeof(TCHAR)) == ERROR_SUCCESS; } else result = RegDeleteValue(key, keyName.c_str()) == ERROR_SUCCESS; RegCloseKey(key); return result; } bool Platform::getAutorun() { HKEY key = 0; if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), 0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS) return false; tstring keyName = currentRegistryKeyName(); TCHAR path[MAX_PATH] = {0}; DWORD length = sizeof(path); DWORD type = REG_SZ; bool result = false; if (RegQueryValueEx(key, keyName.c_str(), 0, &type, (PBYTE)path, &length) == ERROR_SUCCESS && type == REG_SZ) result = true; RegCloseKey(key); return result; } qTox/src/platform/autorun_xdg.cpp000066400000000000000000000042501415623743500175020ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/persistence/settings.h" #include "src/platform/autorun.h" #include #include namespace Platform { QString getAutostartDirPath() { QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString config = env.value("XDG_CONFIG_HOME"); if (config.isEmpty()) config = QDir::homePath() + "/" + ".config"; return config + "/autostart"; } QString getAutostartFilePath(QString dir) { return dir + "/qTox - " + Settings::getInstance().getCurrentProfile() + ".desktop"; } inline QString currentCommandLine() { return "\"" + QApplication::applicationFilePath() + "\" -p \"" + Settings::getInstance().getCurrentProfile() + "\""; } } bool Platform::setAutorun(bool on) { QString dirPath = getAutostartDirPath(); QFile desktop(getAutostartFilePath(dirPath)); if (on) { if (!QDir().mkpath(dirPath) || !desktop.open(QFile::WriteOnly | QFile::Truncate)) return false; desktop.write("[Desktop Entry]\n"); desktop.write("Type=Application\n"); desktop.write("Name=qTox\n"); desktop.write("Exec="); desktop.write(currentCommandLine().toUtf8()); desktop.write("\n"); desktop.close(); return true; } else return desktop.remove(); } bool Platform::getAutorun() { return QFile(getAutostartFilePath(getAutostartDirPath())).exists(); } qTox/src/platform/camera/000077500000000000000000000000001415623743500156665ustar00rootroot00000000000000qTox/src/platform/camera/avfoundation.h000066400000000000000000000022631415623743500205370ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef AVFOUNDATION_H #define AVFOUNDATION_H #include "src/video/videomode.h" #include #include #include #ifndef Q_OS_MACX #error "This file is only meant to be compiled for Mac OS X targets" #endif namespace avfoundation { const QString CAPTURE_SCREEN{"Capture screen"}; QVector getDeviceModes(QString devName); QVector> getDeviceList(); } #endif // AVFOUNDATION_H qTox/src/platform/camera/avfoundation.mm000066400000000000000000000054461415623743500207270ustar00rootroot00000000000000/* Copyright © 2014 Thilo Borgmann Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "avfoundation.h" #include #import QVector > avfoundation::getDeviceList() { QVector > result; NSArray* devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for (AVCaptureDevice* device in devices) { result.append({ QString::fromNSString([device uniqueID]), QString::fromNSString([device localizedName]) }); } uint32_t numScreens = 0; CGGetActiveDisplayList(0, NULL, &numScreens); if (numScreens > 0) { CGDirectDisplayID screens[numScreens]; CGGetActiveDisplayList(numScreens, screens, &numScreens); for (uint32_t i = 0; i < numScreens; i++) { result.append({ QString("%1 %2").arg(CAPTURE_SCREEN).arg(i), QObject::tr("Capture screen %1").arg(i) }); } } return result; } QVector avfoundation::getDeviceModes(QString devName) { QVector result; if (devName.startsWith(CAPTURE_SCREEN)) { return result; } else { NSString* deviceName = [NSString stringWithCString:devName.toUtf8() encoding:NSUTF8StringEncoding]; AVCaptureDevice* device = [AVCaptureDevice deviceWithUniqueID:deviceName]; if (device == nil) { return result; } for (AVCaptureDeviceFormat* format in [device formats]) { CMFormatDescriptionRef formatDescription; CMVideoDimensions dimensions; formatDescription = (CMFormatDescriptionRef)[format performSelector:@selector(formatDescription)]; dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription); for (AVFrameRateRange* range in format.videoSupportedFrameRateRanges) { VideoMode mode; mode.width = dimensions.width; mode.height = dimensions.height; mode.FPS = range.maxFrameRate; result.append(mode); } } } return result; } qTox/src/platform/camera/directshow.cpp000066400000000000000000000175651415623743500205630ustar00rootroot00000000000000/* Copyright © 2010 Ramiro Polla Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "directshow.h" // Because of replacing to incorrect order, which leads to building failing, // this region is ignored for clang-format // clang-format off #include #include #include #include #include #include #include #include // clang-format on /** * Most of this file is adapted from libavdevice's dshow.c, * which retrieves useful information but only exposes it to * stdout and is not part of the public API for some reason. */ static char* wcharToUtf8(wchar_t* w) { int l = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0); char* s = new char[l]; if (s) WideCharToMultiByte(CP_UTF8, 0, w, -1, s, l, 0, 0); return s; } QVector> DirectShow::getDeviceList() { IMoniker* m = nullptr; QVector> devices; ICreateDevEnum* devenum = nullptr; if (CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&devenum) != S_OK) return devices; IEnumMoniker* classenum = nullptr; if (devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, (IEnumMoniker**)&classenum, 0) != S_OK) return devices; while (classenum->Next(1, &m, nullptr) == S_OK) { VARIANT var; IPropertyBag* bag = nullptr; LPMALLOC coMalloc = nullptr; IBindCtx* bindCtx = nullptr; LPOLESTR olestr = nullptr; char *devIdString = nullptr, *devHumanName = nullptr; if (CoGetMalloc(1, &coMalloc) != S_OK) goto fail; if (CreateBindCtx(0, &bindCtx) != S_OK) goto fail; // Get an uuid for the device that we can pass to ffmpeg directly if (m->GetDisplayName(bindCtx, nullptr, &olestr) != S_OK) goto fail; devIdString = wcharToUtf8(olestr); // replace ':' with '_' since FFmpeg uses : to delimitate sources for (size_t i = 0; i < strlen(devIdString); ++i) if (devIdString[i] == ':') devIdString[i] = '_'; // Get a human friendly name/description if (m->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void**)&bag) != S_OK) goto fail; var.vt = VT_BSTR; if (bag->Read(L"FriendlyName", &var, nullptr) != S_OK) goto fail; devHumanName = wcharToUtf8(var.bstrVal); devices += {QString("video=") + devIdString, devHumanName}; fail: if (olestr && coMalloc) coMalloc->Free(olestr); if (bindCtx) bindCtx->Release(); delete[] devIdString; delete[] devHumanName; if (bag) bag->Release(); m->Release(); } classenum->Release(); return devices; } // Used (by getDeviceModes) to select a device // so we can list its properties static IBaseFilter* getDevFilter(QString devName) { IBaseFilter* devFilter = nullptr; devName = devName.mid(6); // Remove the "video=" IMoniker* m = nullptr; ICreateDevEnum* devenum = nullptr; if (CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&devenum) != S_OK) return devFilter; IEnumMoniker* classenum = nullptr; if (devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, (IEnumMoniker**)&classenum, 0) != S_OK) return devFilter; while (classenum->Next(1, &m, nullptr) == S_OK) { LPMALLOC coMalloc = nullptr; IBindCtx* bindCtx = nullptr; LPOLESTR olestr = nullptr; char* devIdString; if (CoGetMalloc(1, &coMalloc) != S_OK) goto fail; if (CreateBindCtx(0, &bindCtx) != S_OK) goto fail; if (m->GetDisplayName(bindCtx, nullptr, &olestr) != S_OK) goto fail; devIdString = wcharToUtf8(olestr); // replace ':' with '_' since FFmpeg uses : to delimitate sources for (size_t i = 0; i < strlen(devIdString); ++i) if (devIdString[i] == ':') devIdString[i] = '_'; if (devName != devIdString) goto fail; if (m->BindToObject(0, 0, IID_IBaseFilter, (void**)&devFilter) != S_OK) goto fail; fail: if (olestr && coMalloc) coMalloc->Free(olestr); if (bindCtx) bindCtx->Release(); delete[] devIdString; m->Release(); } classenum->Release(); if (!devFilter) qWarning() << "Could't find the device " << devName; return devFilter; } QVector DirectShow::getDeviceModes(QString devName) { QVector modes; IBaseFilter* devFilter = getDevFilter(devName); if (!devFilter) return modes; // The outter loop tries to find a valid output pin GUID category; DWORD r2; IEnumPins* pins = nullptr; IPin* pin; if (devFilter->EnumPins(&pins) != S_OK) return modes; while (pins->Next(1, &pin, nullptr) == S_OK) { IKsPropertySet* p = nullptr; PIN_INFO info; pin->QueryPinInfo(&info); info.pFilter->Release(); if (info.dir != PINDIR_OUTPUT) goto next; if (pin->QueryInterface(IID_IKsPropertySet, (void**)&p) != S_OK) goto next; if (p->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, nullptr, 0, &category, sizeof(GUID), &r2) != S_OK) goto next; if (!IsEqualGUID(category, PIN_CATEGORY_CAPTURE)) goto next; // Now we can list the video modes for the current pin // Prepare for another wall of spaghetti DIRECT SHOW QUALITY code { IAMStreamConfig* config = nullptr; VIDEO_STREAM_CONFIG_CAPS* vcaps = nullptr; int size, n; if (pin->QueryInterface(IID_IAMStreamConfig, (void**)&config) != S_OK) goto next; if (config->GetNumberOfCapabilities(&n, &size) != S_OK) goto pinend; assert(size == sizeof(VIDEO_STREAM_CONFIG_CAPS)); vcaps = new VIDEO_STREAM_CONFIG_CAPS; for (int i = 0; i < n; ++i) { AM_MEDIA_TYPE* type = nullptr; VideoMode mode; if (config->GetStreamCaps(i, &type, (BYTE*)vcaps) != S_OK) goto nextformat; if (!IsEqualGUID(type->formattype, FORMAT_VideoInfo) && !IsEqualGUID(type->formattype, FORMAT_VideoInfo2)) goto nextformat; mode.width = vcaps->MaxOutputSize.cx; mode.height = vcaps->MaxOutputSize.cy; mode.FPS = 1e7 / vcaps->MinFrameInterval; if (!modes.contains(mode)) modes.append(std::move(mode)); nextformat: if (type->pbFormat) CoTaskMemFree(type->pbFormat); CoTaskMemFree(type); } pinend: config->Release(); delete vcaps; } next: if (p) p->Release(); pin->Release(); } return modes; } qTox/src/platform/camera/directshow.h000066400000000000000000000021721415623743500202140ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef DIRECTSHOW_H #define DIRECTSHOW_H #include "src/video/videomode.h" #include #include #include #ifndef Q_OS_WIN #error "This file is only meant to be compiled for Windows targets" #endif namespace DirectShow { QVector> getDeviceList(); QVector getDeviceModes(QString devName); } #endif // DIRECTSHOW_H qTox/src/platform/camera/v4l2.cpp000066400000000000000000000144731415623743500171720ustar00rootroot00000000000000/* Copyright © 2000,2001 Fabrice Bellard Copyright © 2006 Luca Abeni Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "v4l2.h" #include #include #include #include #include #include #include #include /** * Most of this file is adapted from libavdevice's v4l2.c, * which retrieves useful information but only exposes it to * stdout and is not part of the public API for some reason. */ static std::map createPixFmtToQuality() { std::map m; m[V4L2_PIX_FMT_H264] = 3; m[V4L2_PIX_FMT_MJPEG] = 2; m[V4L2_PIX_FMT_YUYV] = 1; m[V4L2_PIX_FMT_UYVY] = 1; return m; } const std::map pixFmtToQuality = createPixFmtToQuality(); static std::map createPixFmtToName() { std::map m; m[V4L2_PIX_FMT_H264] = QString("h264"); m[V4L2_PIX_FMT_MJPEG] = QString("mjpeg"); m[V4L2_PIX_FMT_YUYV] = QString("yuyv422"); m[V4L2_PIX_FMT_UYVY] = QString("uyvy422"); return m; } const std::map pixFmtToName = createPixFmtToName(); static int deviceOpen(QString devName, int* error) { struct v4l2_capability cap; int fd; const std::string devNameString = devName.toStdString(); fd = open(devNameString.c_str(), O_RDWR, 0); if (fd < 0) { *error = errno; return fd; } if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) { *error = errno; goto fail; } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { *error = ENODEV; goto fail; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { *error = ENOSYS; goto fail; } return fd; fail: close(fd); return -1; } static QVector getDeviceModeFramerates(int fd, unsigned w, unsigned h, uint32_t pixelFormat) { QVector rates; v4l2_frmivalenum vfve{}; vfve.pixel_format = pixelFormat; vfve.height = h; vfve.width = w; while (!ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &vfve)) { float rate; switch (vfve.type) { case V4L2_FRMSIZE_TYPE_DISCRETE: rate = vfve.discrete.denominator / vfve.discrete.numerator; if (!rates.contains(rate)) rates.append(rate); break; case V4L2_FRMSIZE_TYPE_CONTINUOUS: case V4L2_FRMSIZE_TYPE_STEPWISE: rate = vfve.stepwise.min.denominator / vfve.stepwise.min.numerator; if (!rates.contains(rate)) rates.append(rate); } vfve.index++; } return rates; } QVector v4l2::getDeviceModes(QString devName) { QVector modes; int error = 0; int fd = deviceOpen(devName, &error); if (fd < 0 || error != 0) { return modes; } v4l2_fmtdesc vfd{}; vfd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; while (!ioctl(fd, VIDIOC_ENUM_FMT, &vfd)) { vfd.index++; v4l2_frmsizeenum vfse{}; vfse.pixel_format = vfd.pixelformat; while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &vfse)) { VideoMode mode; mode.pixel_format = vfse.pixel_format; switch (vfse.type) { case V4L2_FRMSIZE_TYPE_DISCRETE: mode.width = vfse.discrete.width; mode.height = vfse.discrete.height; break; case V4L2_FRMSIZE_TYPE_CONTINUOUS: case V4L2_FRMSIZE_TYPE_STEPWISE: mode.width = vfse.stepwise.max_width; mode.height = vfse.stepwise.max_height; break; default: continue; } QVector rates = getDeviceModeFramerates(fd, mode.width, mode.height, vfd.pixelformat); // insert dummy FPS value to have the mode in the list even if we don't know the FPS // this fixes support for some webcams, see #5082 if (rates.isEmpty()) { rates.append(0.0f); } for (float rate : rates) { mode.FPS = rate; if (!modes.contains(mode)) { modes.append(std::move(mode)); } } vfse.index++; } } return modes; } QVector> v4l2::getDeviceList() { QVector> devices; QStringList deviceFiles; DIR* dir = opendir("/dev"); if (!dir) return devices; dirent* e; while ((e = readdir(dir))) if (!strncmp(e->d_name, "video", 5) || !strncmp(e->d_name, "vbi", 3)) deviceFiles += QString("/dev/") + e->d_name; closedir(dir); for (QString file : deviceFiles) { const std::string filePath = file.toStdString(); int fd = open(filePath.c_str(), O_RDWR); if (fd < 0) { continue; } v4l2_capability caps; ioctl(fd, VIDIOC_QUERYCAP, &caps); close(fd); devices += {file, (const char*)caps.card}; } return devices; } QString v4l2::getPixelFormatString(uint32_t pixel_format) { if (pixFmtToName.find(pixel_format) == pixFmtToName.end()) { qWarning() << "Pixel format not found"; return QString("invalid"); } return pixFmtToName.at(pixel_format); } bool v4l2::betterPixelFormat(uint32_t a, uint32_t b) { if (pixFmtToQuality.find(a) == pixFmtToQuality.end()) { return false; } else if (pixFmtToQuality.find(b) == pixFmtToQuality.end()) { return true; } return pixFmtToQuality.at(a) > pixFmtToQuality.at(b); } qTox/src/platform/camera/v4l2.h000066400000000000000000000023611415623743500166300ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef V4L2_H #define V4L2_H #include "src/video/videomode.h" #include #include #include #if !(defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)) #error "This file is only meant to be compiled for Linux or FreeBSD targets" #endif namespace v4l2 { QVector getDeviceModes(QString devName); QVector> getDeviceList(); QString getPixelFormatString(uint32_t pixel_format); bool betterPixelFormat(uint32_t a, uint32_t b); } #endif // V4L2_H qTox/src/platform/capslock.h000066400000000000000000000017001415623743500164040ustar00rootroot00000000000000/* Copyright © 2016-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifdef QTOX_PLATFORM_EXT #ifndef PLATFORM_CAPSLOCK_H #define PLATFORM_CAPSLOCK_H namespace Platform { bool capsLockEnabled(); } #endif // PLATFORM_CAPSLOCK_H #endif // QTOX_PLATFORM_EXT qTox/src/platform/capslock_osx.cpp000066400000000000000000000016341415623743500176360ustar00rootroot00000000000000/* Copyright © 2016-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/platform/capslock.h" // TODO: Implement for osx bool Platform::capsLockEnabled() { return false; } qTox/src/platform/capslock_win.cpp000066400000000000000000000016551415623743500176250ustar00rootroot00000000000000/* Copyright © 2016-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/platform/capslock.h" #include bool Platform::capsLockEnabled() { return GetKeyState(VK_CAPITAL) == 1; } qTox/src/platform/capslock_x11.cpp000066400000000000000000000023421415623743500174330ustar00rootroot00000000000000/* Copyright © 2016-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/platform/capslock.h" #include "src/platform/x11_display.h" #include #undef KeyPress #undef KeyRelease #undef FocusIn #undef FocusOut bool Platform::capsLockEnabled() { Display* d = X11Display::lock(); bool caps_state = false; if (d) { unsigned n; XkbGetIndicatorState(d, XkbUseCoreKbd, &n); caps_state = (n & 0x01) == 1; } X11Display::unlock(); return caps_state; } qTox/src/platform/desktop_notifications/000077500000000000000000000000001415623743500210405ustar00rootroot00000000000000qTox/src/platform/desktop_notifications/desktopnotify.cpp000066400000000000000000000047111415623743500244510ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "desktopnotify.h" #include #include #include DesktopNotify::DesktopNotify() : notifyCore{Snore::SnoreCore::instance()} , snoreIcon{":/img/icons/qtox.svg"} { notifyCore.loadPlugins(Snore::SnorePlugin::Backend); qDebug() << "primary notification backend:" << notifyCore.primaryNotificationBackend(); snoreApp = Snore::Application("qTox", snoreIcon); notifyCore.registerApplication(snoreApp); } void DesktopNotify::createNotification(const QString& title, const QString& text, Snore::Icon& icon) { const Settings& s = Settings::getInstance(); if(!(s.getNotify() && s.getDesktopNotify())) { return; } Snore::Notification notify{snoreApp, Snore::Alert(), title, text, icon}; notifyCore.broadcastNotification(notify); } void DesktopNotify::notifyMessage(const QString& title, const QString& message) { createNotification(title, message, snoreIcon); } void DesktopNotify::notifyMessagePixmap(const QString& title, const QString& message, QPixmap avatar) { Snore::Icon new_icon(avatar); createNotification(title, message, new_icon); } void DesktopNotify::notifyMessageSimple(const MessageType type) { QString message; switch (type) { case MessageType::FRIEND: message = tr("New message"); break; case MessageType::FRIEND_FILE: message = tr("Incoming file transfer"); break; case MessageType::FRIEND_REQUEST: message = tr("Friend request received"); break; case MessageType::GROUP: message = tr("New group message"); break; case MessageType::GROUP_INVITE: message = tr("Group invite received"); break; default: break; } createNotification(message, {}, snoreIcon); } qTox/src/platform/desktop_notifications/desktopnotify.h000066400000000000000000000030311415623743500241100ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef DESKTOPNOTIFY_H #define DESKTOPNOTIFY_H #include #include #include class DesktopNotify : public QObject { Q_OBJECT public: DesktopNotify(); enum class MessageType { FRIEND, FRIEND_FILE, FRIEND_REQUEST, GROUP, GROUP_INVITE }; public slots: void notifyMessage(const QString& title, const QString& message); void notifyMessagePixmap(const QString& title, const QString& message, QPixmap avatar); void notifyMessageSimple(const MessageType type); private: void createNotification(const QString& title, const QString& text, Snore::Icon& icon); private: Snore::SnoreCore& notifyCore; Snore::Application snoreApp; Snore::Icon snoreIcon; }; #endif // DESKTOPNOTIFY_H qTox/src/platform/install_osx.cpp000066400000000000000000000121211415623743500174760ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "install_osx.h" #include #include #include #include #include #include #include #include void osx::moveToAppFolder() { if (qApp->applicationDirPath() != "/Applications/qtox.app/Contents/MacOS") { qDebug() << "OS X: Not in Applications folder"; QMessageBox AskInstall; AskInstall.setIcon(QMessageBox::Question); AskInstall.setWindowModality(Qt::ApplicationModal); AskInstall.setText("Move to Applications folder?"); AskInstall.setInformativeText("I can move myself to the Applications folder, keeping your " "downloads folder less cluttered.\r\n"); AskInstall.setStandardButtons(QMessageBox::Yes | QMessageBox::No); AskInstall.setDefaultButton(QMessageBox::Yes); int AskInstallAttempt = AskInstall.exec(); // Actually ask the user if (AskInstallAttempt == QMessageBox::Yes) { QProcess* sudoprocess = new QProcess; QProcess* qtoxprocess = new QProcess; QString bindir = qApp->applicationDirPath(); QString appdir = bindir; appdir.chop(15); QString appdir_noqtox = appdir; appdir_noqtox.chop(8); if ((appdir_noqtox + "qtox.app") != appdir) // quick safety check { qDebug() << "OS X: Attmepted to delete non qTox directory!"; exit(EXIT_UPDATE_MACX_FAIL); } QDir old_app(appdir); const QString sudoProgram = bindir + "/qtox_sudo"; const QStringList sudoArguments = {"rsync", "-avzhpltK", appdir, "/Applications"}; sudoprocess->start(sudoProgram, sudoArguments); // Where the magic actually happens, safety checks ^ sudoprocess->waitForFinished(); if (old_app.removeRecursively()) // We've just deleted the running program qDebug() << "OS X: Cleaned up old directory"; else qDebug() << "OS X: This should never happen, the directory failed to delete"; if (fork() != 0) // Forking is required otherwise it won't actually cleanly launch exit(EXIT_UPDATE_MACX); const QString qtoxProgram = "open"; const QStringList qtoxArguments = {"/Applications/qtox.app"}; qtoxprocess->start(qtoxProgram, qtoxArguments); exit(0); // Actually kills it } } } // migrateProfiles() is compatabilty code that can be removed down the line when the time seems // right. void osx::migrateProfiles() { QString oldPath = QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "Library" + QDir::separator() + "Preferences" + QDir::separator() + "tox"); QFileInfo checkDir(oldPath); QString newPath = QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "Library" + QDir::separator() + "Application Support" + QDir::separator() + "Tox"); QDir dir; if (!checkDir.exists() || !checkDir.isDir()) { qDebug() << "OS X: Old settings directory not detected"; return; } qDebug() << "OS X: Old settings directory detected migrating to default"; if (!dir.rename(oldPath, newPath)) { qDebug() << "OS X: Profile migration failed. ~/Library/Application Support/Tox already " "exists. Using alternate migration method."; QString OSXMigrater = "../Resources/OSX-Migrater.sh"; QProcess::execute(OSXMigrater, {}); QMessageBox MigrateProfile; MigrateProfile.setIcon(QMessageBox::Information); MigrateProfile.setWindowModality(Qt::ApplicationModal); MigrateProfile.setText("Alternate profile migration method used."); MigrateProfile.setInformativeText( "It has been detected that your profiles \nwhere migrated to the new settings " "directory; \nusing the alternate migration method. \n\nA backup can be found in your: " "\n/Users/[USER]/.Tox-Backup[DATE-TIME] \n\nJust in case. \r\n"); MigrateProfile.exec(); } } // End migrateProfiles() compatibility code qTox/src/platform/install_osx.h000066400000000000000000000022511415623743500171460ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef INSTALLOSX_H #define INSTALLOSX_H #include #ifndef Q_OS_OSX #error "This file is only meant to be compiled for Mac OSX targets" #endif namespace osx { static constexpr int EXIT_UPDATE_MACX = 218; // We track our state using unique exit codes when debugging static constexpr int EXIT_UPDATE_MACX_FAIL = 216; void moveToAppFolder(); void migrateProfiles(); } #endif // INSTALLOSX_H qTox/src/platform/posixsignalnotifier.cpp000066400000000000000000000103001415623743500212340ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "posixsignalnotifier.h" #include #include #include #include #include #include // sigaction() #include // socketpair() #include // may be needed for BSD #include // close() /** * @class PosixSignalNotifier * @brief Class for converting POSIX signals to Qt signals */ namespace detail { static std::atomic_flag g_signalSocketUsageFlag = ATOMIC_FLAG_INIT; static std::array g_signalSocketPair; static void signalHandler(int signum) { // DO NOT call any Qt functions directly, only limited amount of so-called async-signal-safe // functions can be called in signal handlers. // See https://doc.qt.io/qt-4.8/unix-signals.html // If test_and_set() returns true, it means it was already in use (only by ~PosixSignalNotifier()), // so we bail out. Our signal handler is blocking, only one will be called (no race between // threads), hence simple implementation. if (g_signalSocketUsageFlag.test_and_set()) return; if(::write(g_signalSocketPair[0], &signum, sizeof(signum)) == -1) { // We hardly can do anything more usefull in signal handler, and // any ways it's probably very unexpected error (out of memory?), // since we check socket existance with a flag beforehand. abort(); } g_signalSocketUsageFlag.clear(); } } // namespace detail PosixSignalNotifier::~PosixSignalNotifier() { while (detail::g_signalSocketUsageFlag.test_and_set()) { // spin-loop until we aquire flag (signal handler might be running and have flag in use) } // do not leak sockets ::close(detail::g_signalSocketPair[0]); ::close(detail::g_signalSocketPair[1]); // do not clear the usage flag here, signal handler cannot use socket any more! } void PosixSignalNotifier::watchSignal(int signum) { sigset_t blockMask; sigemptyset(&blockMask); // do not prefix with ::, it's a macro on macOS sigaddset(&blockMask, signum); // do not prefix with ::, it's a macro on macOS struct sigaction action = {}; // all zeroes by default action.sa_handler = detail::signalHandler; action.sa_mask = blockMask; // allow old signal to finish before new is raised if (::sigaction(signum, &action, nullptr)) { qFatal("Failed to setup signal %d, error = %d", signum, errno); } } void PosixSignalNotifier::watchSignals(std::initializer_list signalSet) { for (auto s: signalSet) { watchSignal(s); } } void PosixSignalNotifier::watchCommonTerminatingSignals() { watchSignals({SIGHUP, SIGINT, SIGQUIT, SIGTERM}); } PosixSignalNotifier& PosixSignalNotifier::globalInstance() { static PosixSignalNotifier instance; return instance; } void PosixSignalNotifier::onSignalReceived() { int signum{0}; if (::read(detail::g_signalSocketPair[1], &signum, sizeof(signum)) == -1) { qFatal("Failed to read from signal socket, error = %d", errno); } qDebug() << "Signal" << signum << "received"; emit activated(signum); } PosixSignalNotifier::PosixSignalNotifier() { if (::socketpair(AF_UNIX, SOCK_STREAM, 0, detail::g_signalSocketPair.data())) { qFatal("Failed to create socket pair, error = %d", errno); } notifier = new QSocketNotifier(detail::g_signalSocketPair[1], QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, &PosixSignalNotifier::onSignalReceived); } qTox/src/platform/posixsignalnotifier.h000066400000000000000000000025551415623743500207160ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef POSIXSIGNALNOTIFIER_H #define POSIXSIGNALNOTIFIER_H #include class QSocketNotifier; class PosixSignalNotifier : public QObject { Q_OBJECT public: ~PosixSignalNotifier(); static void watchSignal(int signum); static void watchSignals(std::initializer_list signalSet); static void watchCommonTerminatingSignals(); static PosixSignalNotifier& globalInstance(); signals: void activated(int signal); private slots: void onSignalReceived(); private: PosixSignalNotifier(); private: QSocketNotifier* notifier{nullptr}; }; #endif // POSIXSIGNALNOTIFIER_H qTox/src/platform/statusnotifier/000077500000000000000000000000001415623743500175215ustar00rootroot00000000000000qTox/src/platform/statusnotifier/closures.c000066400000000000000000000120321415623743500215220ustar00rootroot00000000000000/* * statusnotifier - Copyright (C) 2014 Olivier Brunel * * closures.c * Copyright (C) 2014 Olivier Brunel * * This file is part of statusnotifier. * * statusnotifier is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * statusnotifier is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * statusnotifier. If not, see http://www.gnu.org/licenses/ */ #include #include "closures.h" #ifdef G_ENABLE_DEBUG #define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) #define g_marshal_value_peek_char(v) g_value_get_schar (v) #define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) #define g_marshal_value_peek_int(v) g_value_get_int (v) #define g_marshal_value_peek_uint(v) g_value_get_uint (v) #define g_marshal_value_peek_long(v) g_value_get_long (v) #define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) #define g_marshal_value_peek_int64(v) g_value_get_int64 (v) #define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) #define g_marshal_value_peek_enum(v) g_value_get_enum (v) #define g_marshal_value_peek_flags(v) g_value_get_flags (v) #define g_marshal_value_peek_float(v) g_value_get_float (v) #define g_marshal_value_peek_double(v) g_value_get_double (v) #define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) #define g_marshal_value_peek_param(v) g_value_get_param (v) #define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) #define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) #define g_marshal_value_peek_object(v) g_value_get_object (v) #define g_marshal_value_peek_variant(v) g_value_get_variant (v) #else /* !G_ENABLE_DEBUG */ /* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. * Do not access GValues directly in your code. Instead, use the * g_value_get_*() functions */ #define g_marshal_value_peek_boolean(v) (v)->data[0].v_int #define g_marshal_value_peek_char(v) (v)->data[0].v_int #define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint #define g_marshal_value_peek_int(v) (v)->data[0].v_int #define g_marshal_value_peek_uint(v) (v)->data[0].v_uint #define g_marshal_value_peek_long(v) (v)->data[0].v_long #define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong #define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 #define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 #define g_marshal_value_peek_enum(v) (v)->data[0].v_long #define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong #define g_marshal_value_peek_float(v) (v)->data[0].v_float #define g_marshal_value_peek_double(v) (v)->data[0].v_double #define g_marshal_value_peek_string(v) (v)->data[0].v_pointer #define g_marshal_value_peek_param(v) (v)->data[0].v_pointer #define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer #define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer #define g_marshal_value_peek_object(v) (v)->data[0].v_pointer #define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer #endif /* !G_ENABLE_DEBUG */ /* BOOLEAN:INT,INT (closures.def:1) */ void g_cclosure_user_marshal_BOOLEAN__INT_INT (GClosure *closure, GValue *return_value G_GNUC_UNUSED, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { typedef gboolean (*GMarshalFunc_BOOLEAN__INT_INT) (gpointer data1, gint arg_1, gint arg_2, gpointer data2); register GMarshalFunc_BOOLEAN__INT_INT callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; gboolean v_return; g_return_if_fail (return_value != NULL); g_return_if_fail (n_param_values == 3); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_BOOLEAN__INT_INT) (marshal_data ? marshal_data : cc->callback); v_return = callback (data1, g_marshal_value_peek_int (param_values + 1), g_marshal_value_peek_int (param_values + 2), data2); g_value_set_boolean (return_value, v_return); } qTox/src/platform/statusnotifier/closures.h000066400000000000000000000025241415623743500215340ustar00rootroot00000000000000/* * statusnotifier - Copyright (C) 2014 Olivier Brunel * * closures.h * Copyright (C) 2014 Olivier Brunel * * This file is part of statusnotifier. * * statusnotifier is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * statusnotifier is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * statusnotifier. If not, see http://www.gnu.org/licenses/ */ #ifndef __g_cclosure_user_marshal_MARSHAL_H__ #define __g_cclosure_user_marshal_MARSHAL_H__ #include G_BEGIN_DECLS /* BOOLEAN:INT,INT (closures.def:1) */ extern void g_cclosure_user_marshal_BOOLEAN__INT_INT(GClosure* closure, GValue* return_value, guint n_param_values, const GValue* param_values, gpointer invocation_hint, gpointer marshal_data); G_END_DECLS #endif /* __g_cclosure_user_marshal_MARSHAL_H__ */ qTox/src/platform/statusnotifier/enums.c000066400000000000000000000110631415623743500210150ustar00rootroot00000000000000/* * statusnotifier - Copyright (C) 2014 Olivier Brunel * * interfaces.h * Copyright (C) 2014 Olivier Brunel * * This file is part of statusnotifier. * * statusnotifier is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * statusnotifier is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * statusnotifier. If not, see http://www.gnu.org/licenses/ */ #include "enums.h" GType status_notifier_error_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { { STATUS_NOTIFIER_ERROR_NO_CONNECTION, "STATUS_NOTIFIER_ERROR_NO_CONNECTION", "connection" }, { STATUS_NOTIFIER_ERROR_NO_NAME, "STATUS_NOTIFIER_ERROR_NO_NAME", "name" }, { STATUS_NOTIFIER_ERROR_NO_WATCHER, "STATUS_NOTIFIER_ERROR_NO_WATCHER", "watcher" }, { STATUS_NOTIFIER_ERROR_NO_HOST, "STATUS_NOTIFIER_ERROR_NO_HOST", "host" }, { 0, NULL, NULL } }; etype = g_enum_register_static ("StatusNotifierError", values); } return etype; } GType status_notifier_state_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { { STATUS_NOTIFIER_STATE_NOT_REGISTERED, "STATUS_NOTIFIER_STATE_NOT_REGISTERED", "not-registered" }, { STATUS_NOTIFIER_STATE_REGISTERING, "STATUS_NOTIFIER_STATE_REGISTERING", "registering" }, { STATUS_NOTIFIER_STATE_REGISTERED, "STATUS_NOTIFIER_STATE_REGISTERED", "registered" }, { STATUS_NOTIFIER_STATE_FAILED, "STATUS_NOTIFIER_STATE_FAILED", "failed" }, { 0, NULL, NULL } }; etype = g_enum_register_static ("StatusNotifierState", values); } return etype; } GType status_notifier_icon_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { { STATUS_NOTIFIER_ICON, "STATUS_NOTIFIER_ICON", "status-notifier-icon" }, { STATUS_NOTIFIER_ATTENTION_ICON, "STATUS_NOTIFIER_ATTENTION_ICON", "status-notifier-attention-icon" }, { STATUS_NOTIFIER_OVERLAY_ICON, "STATUS_NOTIFIER_OVERLAY_ICON", "status-notifier-overlay-icon" }, { STATUS_NOTIFIER_TOOLTIP_ICON, "STATUS_NOTIFIER_TOOLTIP_ICON", "status-notifier-tooltip-icon" }, { _NB_STATUS_NOTIFIER_ICONS, "_NB_STATUS_NOTIFIER_ICONS", "-nb-status-notifier-icons" }, { 0, NULL, NULL } }; etype = g_enum_register_static ("StatusNotifierIcon", values); } return etype; } GType status_notifier_category_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { { STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS, "STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS", "application-status" }, { STATUS_NOTIFIER_CATEGORY_COMMUNICATIONS, "STATUS_NOTIFIER_CATEGORY_COMMUNICATIONS", "communications" }, { STATUS_NOTIFIER_CATEGORY_SYSTEM_SERVICES, "STATUS_NOTIFIER_CATEGORY_SYSTEM_SERVICES", "system-services" }, { STATUS_NOTIFIER_CATEGORY_HARDWARE, "STATUS_NOTIFIER_CATEGORY_HARDWARE", "hardware" }, { 0, NULL, NULL } }; etype = g_enum_register_static ("StatusNotifierCategory", values); } return etype; } GType status_notifier_status_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { { STATUS_NOTIFIER_STATUS_PASSIVE, "STATUS_NOTIFIER_STATUS_PASSIVE", "passive" }, { STATUS_NOTIFIER_STATUS_ACTIVE, "STATUS_NOTIFIER_STATUS_ACTIVE", "active" }, { STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION, "STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION", "needs-attention" }, { 0, NULL, NULL } }; etype = g_enum_register_static ("StatusNotifierStatus", values); } return etype; } GType status_notifier_scroll_orientation_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { { STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL, "STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL", "horizontal" }, { STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL, "STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL", "vertical" }, { 0, NULL, NULL } }; etype = g_enum_register_static ("StatusNotifierScrollOrientation", values); } return etype; } qTox/src/platform/statusnotifier/enums.h000066400000000000000000000032641415623743500210260ustar00rootroot00000000000000/* * statusnotifier - Copyright (C) 2014 Olivier Brunel * * interfaces.h * Copyright (C) 2014 Olivier Brunel * * This file is part of statusnotifier. * * statusnotifier is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * statusnotifier is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * statusnotifier. If not, see http://www.gnu.org/licenses/ */ #ifndef __STATUS_NOTIFIER_ENUMS_H__ #define __STATUS_NOTIFIER_ENUMS_H__ #include "statusnotifier.h" GType status_notifier_error_get_type(void); #define TYPE_STATUS_NOTIFIER_ERROR (status_notifier_error_get_type()) GType status_notifier_state_get_type(void); #define TYPE_STATUS_NOTIFIER_STATE (status_notifier_state_get_type()) GType status_notifier_icon_get_type(void); #define TYPE_STATUS_NOTIFIER_ICON (status_notifier_icon_get_type()) GType status_notifier_category_get_type(void); #define TYPE_STATUS_NOTIFIER_CATEGORY (status_notifier_category_get_type()) GType status_notifier_status_get_type(void); #define TYPE_STATUS_NOTIFIER_STATUS (status_notifier_status_get_type()) GType status_notifier_scroll_orientation_get_type(void); #define TYPE_STATUS_NOTIFIER_SCROLL_ORIENTATION (status_notifier_scroll_orientation_get_type()) G_END_DECLS #endif /* __STATUS_NOTIFIER_ENUMS_H__ */ qTox/src/platform/statusnotifier/interfaces.h000066400000000000000000000073751415623743500220310ustar00rootroot00000000000000/* * statusnotifier - Copyright (C) 2014 Olivier Brunel * * interfaces.h * Copyright (C) 2014 Olivier Brunel * * This file is part of statusnotifier. * * statusnotifier is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * statusnotifier is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * statusnotifier. If not, see http://www.gnu.org/licenses/ */ #ifndef __INTERFACES_H__ #define __INTERFACES_H__ G_BEGIN_DECLS #define WATCHER_NAME "org.kde.StatusNotifierWatcher" #define WATCHER_OBJECT "/StatusNotifierWatcher" #define WATCHER_INTERFACE "org.kde.StatusNotifierWatcher" #define ITEM_NAME "org.kde.StatusNotifierItem" #define ITEM_OBJECT "/StatusNotifierItem" #define ITEM_INTERFACE "org.kde.StatusNotifierItem" static const gchar watcher_xml[] = "" " " " " " " " " " " " " " " " " ""; static const gchar item_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; G_END_DECLS #endif /* __INTERFACES_H__ */ qTox/src/platform/statusnotifier/statusnotifier.c000066400000000000000000001743451415623743500227660ustar00rootroot00000000000000/* * statusnotifier - Copyright (C) 2014 Olivier Brunel * * statusnotifier.c * Copyright (C) 2014 Olivier Brunel * * This file is part of statusnotifier. * * statusnotifier is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * statusnotifier is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * statusnotifier. If not, see http://www.gnu.org/licenses/ */ //#include "config.h" #include #include #include "statusnotifier.h" #include "enums.h" #include "interfaces.h" #include "closures.h" /** * SECTION:statusnotifier * @Short_description: A StatusNotifierItem as per KDE's specifications * * Starting with Plasma Next, KDE doesn't support the XEmbed systray in favor of * their own Status Notifier Specification. * * A #StatusNotifier is a #GObject that can be used to represent a * StatusNotifierItem, handling all the DBus implementation and leaving you * simply dealing with regular properties and signals. * * You can simply create a new #StatusNotifier using one of the helper function, * e.g. status_notifier_new_from_icon_name(), or simply creating an object as * usual - you then just need to make sure to specify #StatusNotifier:id : * * sn = (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER, * "id", "app-id", * "status", STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION, * "main-icon-name", "app-icon", * "attention-icon-pixbuf", pixbuf, * "tooltip-title", "My tooltip", * "tooltip-body", "This is an item about <b>app</b>", * NULL); * * * You can also set properties (other than id) after creation. Once ready, call * status_notifier_register() to register the item on the session bus and to the * StatusNotifierWatcher. * * If an error occurs, signal #StatusNotifier::registration-failed will be * emitted. On success, #StatusNotifier:state will be * %STATUS_NOTIFIER_STATE_REGISTERED. See status_notifier_register() for more. * * Once registered, you can change properties as needed, and the proper DBus * signal will be emitted to let visualizations (hosts) know, and connect to the * signals (such as #StatusNotifier::context-menu) which will be emitted when * the corresponding DBus method was called. * * For reference, the KDE specifications can be found at * http://www.notmart.org/misc/statusnotifieritem/index.html */ enum { PROP_0, PROP_ID, PROP_TITLE, PROP_CATEGORY, PROP_STATUS, PROP_MAIN_ICON_NAME, PROP_MAIN_ICON_PIXBUF, PROP_OVERLAY_ICON_NAME, PROP_OVERLAY_ICON_PIXBUF, PROP_ATTENTION_ICON_NAME, PROP_ATTENTION_ICON_PIXBUF, PROP_ATTENTION_MOVIE_NAME, PROP_TOOLTIP_ICON_NAME, PROP_TOOLTIP_ICON_PIXBUF, PROP_TOOLTIP_TITLE, PROP_TOOLTIP_BODY, PROP_WINDOW_ID, PROP_STATE, NB_PROPS }; static guint prop_name_from_icon[_NB_STATUS_NOTIFIER_ICONS] = { PROP_MAIN_ICON_NAME, PROP_ATTENTION_ICON_NAME, PROP_OVERLAY_ICON_NAME, PROP_TOOLTIP_ICON_NAME }; static guint prop_pixbuf_from_icon[_NB_STATUS_NOTIFIER_ICONS] = { PROP_MAIN_ICON_PIXBUF, PROP_ATTENTION_ICON_PIXBUF, PROP_OVERLAY_ICON_PIXBUF, PROP_TOOLTIP_ICON_PIXBUF }; enum { SIGNAL_REGISTRATION_FAILED, SIGNAL_CONTEXT_MENU, SIGNAL_ACTIVATE, SIGNAL_SECONDARY_ACTIVATE, SIGNAL_SCROLL, NB_SIGNALS }; struct _StatusNotifierPrivate { gchar *id; StatusNotifierCategory category; gchar *title; StatusNotifierStatus status; struct { gboolean has_pixbuf; union { gchar *icon_name; GdkPixbuf *pixbuf; }; } icon[_NB_STATUS_NOTIFIER_ICONS]; gchar *attention_movie_name; gchar *tooltip_title; gchar *tooltip_body; guint32 window_id; guint tooltip_freeze; StatusNotifierState state; guint dbus_watch_id; guint dbus_sid; guint dbus_owner_id; guint dbus_reg_id; GDBusProxy *dbus_proxy; GDBusConnection *dbus_conn; GError *dbus_err; }; static guint uniq_id = 0; static GParamSpec *status_notifier_props[NB_PROPS] = { NULL, }; static guint status_notifier_signals[NB_SIGNALS] = { 0, }; #define notify(sn,prop) \ g_object_notify_by_pspec ((GObject *) sn, status_notifier_props[prop]) static void status_notifier_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void status_notifier_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void status_notifier_finalize (GObject *object); G_DEFINE_TYPE (StatusNotifier, status_notifier, G_TYPE_OBJECT) static void status_notifier_class_init (StatusNotifierClass *klass) { GObjectClass *o_class; o_class = G_OBJECT_CLASS (klass); o_class->set_property = status_notifier_set_property; o_class->get_property = status_notifier_get_property; o_class->finalize = status_notifier_finalize; /** * StatusNotifier:id: * * It's a name that should be unique for this application and consistent * between sessions, such as the application name itself. */ status_notifier_props[PROP_ID] = g_param_spec_string ("id", "id", "Unique application identifier", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * StatusNotifier:title: * * It's a name that describes the application, it can be more descriptive * than #StatusNotifier:id. */ status_notifier_props[PROP_TITLE] = g_param_spec_string ("title", "title", "Descriptive name for the item", NULL, G_PARAM_READWRITE); /** * StatusNotifier:category: * * Describes the category of this item. */ status_notifier_props[PROP_CATEGORY] = g_param_spec_enum ("category", "category", "Category of the item", TYPE_STATUS_NOTIFIER_CATEGORY, STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * StatusNotifier:status: * * Describes the status of this item or of the associated application. */ status_notifier_props[PROP_STATUS] = g_param_spec_enum ("status", "status", "Status of the item", TYPE_STATUS_NOTIFIER_STATUS, STATUS_NOTIFIER_STATUS_PASSIVE, G_PARAM_READWRITE); /** * StatusNotifier:main-icon-name: * * The item can carry an icon that can be used by the visualization to * identify the item. * * An icon can either be identified by its Freedesktop-compliant icon name, * set by this property, or by the icon data itself, set by the property * #StatusNotifier:main-icon-pixbuf. * * It is currently not possible to set both, as setting one will unset the * other. */ status_notifier_props[PROP_MAIN_ICON_NAME] = g_param_spec_string ("main-icon-name", "main-icon-name", "Icon name for the main icon", NULL, G_PARAM_READWRITE); /** * StatusNotifier:main-icon-pixbuf: * * The item can carry an icon that can be used by the visualization to * identify the item. * * An icon can either be identified by its Freedesktop-compliant icon name, * set by property #StatusNotifier:main-icon-name, or by the icon data * itself, set by this property. * * It is currently not possible to set both, as setting one will unset the * other. */ status_notifier_props[PROP_MAIN_ICON_PIXBUF] = g_param_spec_object ("main-icon-pixbuf", "main-icon-pixbuf", "Pixbuf for the main icon", GDK_TYPE_PIXBUF, G_PARAM_READWRITE); /** * StatusNotifier:overlay-icon-name: * * This can be used by the visualization to indicate extra state * information, for instance as an overlay for the main icon. * * An icon can either be identified by its Freedesktop-compliant icon name, * set by this property, or by the icon data itself, set by property * #StatusNotifier:overlay-icon-pixbuf. * * It is currently not possible to set both, as setting one will unset the * other. */ status_notifier_props[PROP_OVERLAY_ICON_NAME] = g_param_spec_string ("overlay-icon-name", "overlay-icon-name", "Icon name for the overlay icon", NULL, G_PARAM_READWRITE); /** * StatusNotifier:overlay-icon-pixbuf: * * This can be used by the visualization to indicate extra state * information, for instance as an overlay for the main icon. * * An icon can either be identified by its Freedesktop-compliant icon name, * set by property #StatusNotifier:overlay-icon-name, or by the icon data * itself, set by this property. * * It is currently not possible to set both, as setting one will unset the * other. */ status_notifier_props[PROP_OVERLAY_ICON_PIXBUF] = g_param_spec_object ("overlay-icon-pixbuf", "overlay-icon-pixbuf", "Pixbuf for the overlay icon", GDK_TYPE_PIXBUF, G_PARAM_READWRITE); /** * StatusNotifier:attention-icon-name: * * This can be used by the visualization to indicate that the item is in * %STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status. * * An icon can either be identified by its Freedesktop-compliant icon name, * set by this property, or by the icon data itself, set by property * #StatusNotifier:attention-icon-pixbuf. * * It is currently not possible to set both, as setting one will unset the * other. */ status_notifier_props[PROP_ATTENTION_ICON_NAME] = g_param_spec_string ("attention-icon-name", "attention-icon-name", "Icon name for the attention icon", NULL, G_PARAM_READWRITE); /** * StatusNotifier:attention-icon-pixbuf: * * This can be used by the visualization to indicate that the item is in * %STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status. * * An icon can either be identified by its Freedesktop-compliant icon name, * set by property #StatusNotifier:attention-icon-name, or by the icon data * itself, set by this property. * * It is currently not possible to set both, as setting one will unset the * other. */ status_notifier_props[PROP_ATTENTION_ICON_PIXBUF] = g_param_spec_object ("attention-icon-pixbuf", "attention-icon-pixbuf", "Pixbuf for the attention icon", GDK_TYPE_PIXBUF, G_PARAM_READWRITE); /** * StatusNotifier:attention-movie-name: * * In addition to the icon, the item can also specify an animation * associated to the #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status. * * This should be either a Freedesktop-compliant icon name or a full path. * The visualization can chose between the movie or icon (or using neither * of those) at its discretion. */ status_notifier_props[PROP_ATTENTION_MOVIE_NAME] = g_param_spec_string ("attention-movie-name", "attention-movie-name", "Animation name/full path when the item is in needs-attention status", NULL, G_PARAM_READWRITE); /** * StatusNotifier:tooltip-icon-name: * * A tooltip can be defined on the item; It can be used by the visualization * to show as a tooltip (or by any other mean it considers appropriate). * * The tooltip is composed of a title, a body, and an icon. Note that * changing any of these will trigger a DBus signal NewToolTip (for hosts to * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for * changing more than one and only emitting one DBus signal at the end. * * The icon can either be identified by its Freedesktop-compliant icon name, * set by this property, or by the icon data itself, set by property * #StatusNotifier:tooltip-icon-pixbuf. * * It is currently not possible to set both, as setting one will unset the * other. */ status_notifier_props[PROP_TOOLTIP_ICON_NAME] = g_param_spec_string ("tooltip-icon-name", "tooltip-icon-name", "Icon name for the tooltip icon", NULL, G_PARAM_READWRITE); /** * StatusNotifier:tooltip-icon-pixbuf: * * A tooltip can be defined on the item; It can be used by the visualization * to show as a tooltip (or by any other mean it considers appropriate). * * The tooltip is composed of a title, a body, and an icon. Note that * changing any of these will trigger a DBus signal NewToolTip (for hosts to * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for * changing more than one and only emitting one DBus signal at the end. * * The icon can either be identified by its Freedesktop-compliant icon name, * set by property #StatusNotifier:tooltip-icon-name, or by the icon data * itself, set by this property. * * It is currently not possible to set both, as setting one will unset the * other. */ status_notifier_props[PROP_TOOLTIP_ICON_PIXBUF] = g_param_spec_object ("tooltip-icon-pixbuf", "tooltip-icon-pixbuf", "Pixbuf for the tooltip icon", GDK_TYPE_PIXBUF, G_PARAM_READWRITE); /** * StatusNotifier:tooltip-title: * * A tooltip can be defined on the item; It can be used by the visualization * to show as a tooltip (or by any other mean it considers appropriate). * * The tooltip is composed of a title, a body, and an icon. Note that * changing any of these will trigger a DBus signal NewToolTip (for hosts to * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for * changing more than one and only emitting one DBus signal at the end. */ status_notifier_props[PROP_TOOLTIP_TITLE] = g_param_spec_string ("tooltip-title", "tooltip-title", "Title of the tooltip", NULL, G_PARAM_READWRITE); /** * StatusNotifier:tooltip-body: * * A tooltip can be defined on the item; It can be used by the visualization * to show as a tooltip (or by any other mean it considers appropriate). * * The tooltip is composed of a title, a body, and an icon. Note that * changing any of these will trigger a DBus signal NewToolTip (for hosts to * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for * changing more than one and only emitting one DBus signal at the end. * * This body can contain some markup, which consists of a small subset of * XHTML. See http://www.notmart.org/misc/statusnotifieritem/markup.html for * more. */ status_notifier_props[PROP_TOOLTIP_BODY] = g_param_spec_string ("tooltip-body", "tooltip-body", "Body of the tooltip", NULL, G_PARAM_READWRITE); /** * StatusNotifier:window-id: * * It's the windowing-system dependent identifier for a window, the * application can chose one of its windows to be available trough this * property or just set 0 if it's not interested. */ status_notifier_props[PROP_WINDOW_ID] = g_param_spec_uint ("window-id", "window-id", "Window ID", 0, G_MAXUINT32, 0, G_PARAM_READWRITE); /** * StatusNotifier:state: * * The state of the item, regarding its DBus registration on the * StatusNotifierWatcher. After you've created the item, you need to call * status_notifier_register() to have it registered via DBus on the watcher. * * See status_notifier_register() for more. */ status_notifier_props[PROP_STATE] = g_param_spec_enum ("state", "state", "DBus registration state of the item", TYPE_STATUS_NOTIFIER_STATE, STATUS_NOTIFIER_STATE_NOT_REGISTERED, G_PARAM_READABLE); g_object_class_install_properties (o_class, NB_PROPS, status_notifier_props); /** * StatusNotifier::registration-failed: * @sn: The #StatusNotifier * @error: A #GError with the reason of failure * * This signal is emited after a call to status_notifier_register() when * registering the item eventually failed (e.g. if there wasn't (yet) any * StatusNotifierHost registered.) * * When this happens, you should fallback to using the systray. You should * also check #StatusNotifier:state as it might still be * %STATUS_NOTIFIER_STATE_REGISTERING if the registration remains eventually * possible (e.g. waiting for a StatusNotifierHost to register) * * See status_notifier_register() for more. */ status_notifier_signals[SIGNAL_REGISTRATION_FAILED] = g_signal_new ( "registration-failed", TYPE_STATUS_NOTIFIER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (StatusNotifierClass, registration_failed), NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, G_TYPE_ERROR); /** * StatusNotifier::context-menu: * @sn: The #StatusNotifier * @x: screen coordinates X * @y: screen coordinates Y * * Emitted when the ContextMenu method was called on the item. Item should * then show a context menu, this is typically a consequence of user input, * such as mouse right click over the graphical representation of the item. * * @x and @y are to be considered an hint to the item about where to show * the context menu. */ status_notifier_signals[SIGNAL_CONTEXT_MENU] = g_signal_new ( "context-menu", TYPE_STATUS_NOTIFIER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (StatusNotifierClass, context_menu), g_signal_accumulator_true_handled, NULL, g_cclosure_user_marshal_BOOLEAN__INT_INT, G_TYPE_BOOLEAN, 2, G_TYPE_INT, G_TYPE_INT); /** * StatusNotifier::activate: * @sn: The #StatusNotifier * @x: screen coordinates X * @y: screen coordinates Y * * Emitted when the Activate method was called on the item. Activation of * the item was requested, this is typically a consequence of user input, * such as mouse left click over the graphical representation of the item. * * @x and @y are to be considered an hint to the item about where to show * the context menu. */ status_notifier_signals[SIGNAL_ACTIVATE] = g_signal_new ( "activate", TYPE_STATUS_NOTIFIER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (StatusNotifierClass, activate), g_signal_accumulator_true_handled, NULL, g_cclosure_user_marshal_BOOLEAN__INT_INT, G_TYPE_BOOLEAN, 2, G_TYPE_INT, G_TYPE_INT); /** * StatusNotifier::secondary-activate: * @sn: The #StatusNotifier * @x: screen coordinates X * @y: screen coordinates Y * * Emitted when the SecondaryActivate method was called on the item. * Secondary and less important form of activation (compared to * #StatusNotifier::activate) of the item was requested. This is typically a * consequence of user input, such as mouse middle click over the graphical * representation of the item. * * @x and @y are to be considered an hint to the item about where to show * the context menu. */ status_notifier_signals[SIGNAL_SECONDARY_ACTIVATE] = g_signal_new ( "secondary-activate", TYPE_STATUS_NOTIFIER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (StatusNotifierClass, secondary_activate), g_signal_accumulator_true_handled, NULL, g_cclosure_user_marshal_BOOLEAN__INT_INT, G_TYPE_BOOLEAN, 2, G_TYPE_INT, G_TYPE_INT); /** * StatusNotifier::scroll: * @sn: The #StatusNotifier * @delta: the amount of scroll * @orientation: orientation of the scroll request * * Emitted when the Scroll method was called on the item. The user asked for * a scroll action. This is caused from input such as mouse wheel over the * graphical representation of the item. */ status_notifier_signals[SIGNAL_SCROLL] = g_signal_new ( "scroll", TYPE_STATUS_NOTIFIER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (StatusNotifierClass, scroll), g_signal_accumulator_true_handled, NULL, g_cclosure_user_marshal_BOOLEAN__INT_INT, G_TYPE_BOOLEAN, 2, G_TYPE_INT, TYPE_STATUS_NOTIFIER_SCROLL_ORIENTATION); g_type_class_add_private (klass, sizeof (StatusNotifierPrivate)); } static void status_notifier_init (StatusNotifier *sn) { sn->priv = G_TYPE_INSTANCE_GET_PRIVATE (sn, TYPE_STATUS_NOTIFIER, StatusNotifierPrivate); } static void status_notifier_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { StatusNotifier *sn = (StatusNotifier *) object; StatusNotifierPrivate *priv = sn->priv; switch (prop_id) { case PROP_ID: /* G_PARAM_CONSTRUCT_ONLY */ priv->id = g_value_dup_string (value); break; case PROP_TITLE: status_notifier_set_title (sn, g_value_get_string (value)); break; case PROP_CATEGORY: /* G_PARAM_CONSTRUCT_ONLY */ priv->category = g_value_get_enum (value); break; case PROP_STATUS: status_notifier_set_status (sn, g_value_get_enum (value)); break; case PROP_MAIN_ICON_NAME: status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_ICON, g_value_get_string (value)); break; case PROP_MAIN_ICON_PIXBUF: status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_ICON, g_value_get_object (value)); break; case PROP_OVERLAY_ICON_NAME: status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_OVERLAY_ICON, g_value_get_string (value)); break; case PROP_OVERLAY_ICON_PIXBUF: status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_OVERLAY_ICON, g_value_get_object (value)); break; case PROP_ATTENTION_ICON_NAME: status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_ATTENTION_ICON, g_value_get_string (value)); break; case PROP_ATTENTION_ICON_PIXBUF: status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_ATTENTION_ICON, g_value_get_object (value)); break; case PROP_ATTENTION_MOVIE_NAME: status_notifier_set_attention_movie_name (sn, g_value_get_string (value)); break; case PROP_TOOLTIP_ICON_NAME: status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON, g_value_get_string (value)); break; case PROP_TOOLTIP_ICON_PIXBUF: status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_TOOLTIP_ICON, g_value_get_object (value)); break; case PROP_TOOLTIP_TITLE: status_notifier_set_tooltip_title (sn, g_value_get_string (value)); break; case PROP_TOOLTIP_BODY: status_notifier_set_tooltip_body (sn, g_value_get_string (value)); break; case PROP_WINDOW_ID: status_notifier_set_window_id (sn, g_value_get_uint (value)); default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void status_notifier_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { StatusNotifier *sn = (StatusNotifier *) object; StatusNotifierPrivate *priv = sn->priv; switch (prop_id) { case PROP_ID: g_value_set_string (value, priv->id); break; case PROP_TITLE: g_value_set_string (value, priv->title); break; case PROP_CATEGORY: g_value_set_enum (value, priv->category); break; case PROP_STATUS: g_value_set_enum (value, priv->status); break; case PROP_MAIN_ICON_NAME: g_value_take_string (value, status_notifier_get_icon_name (sn, STATUS_NOTIFIER_ICON)); break; case PROP_MAIN_ICON_PIXBUF: g_value_take_object (value, status_notifier_get_pixbuf (sn, STATUS_NOTIFIER_ICON)); break; case PROP_OVERLAY_ICON_NAME: g_value_take_string (value, status_notifier_get_icon_name (sn, STATUS_NOTIFIER_OVERLAY_ICON)); break; case PROP_OVERLAY_ICON_PIXBUF: g_value_take_object (value, status_notifier_get_pixbuf (sn, STATUS_NOTIFIER_OVERLAY_ICON)); break; case PROP_ATTENTION_ICON_NAME: g_value_take_string (value, status_notifier_get_icon_name (sn, STATUS_NOTIFIER_ATTENTION_ICON)); break; case PROP_ATTENTION_ICON_PIXBUF: g_value_take_object (value, status_notifier_get_pixbuf (sn, STATUS_NOTIFIER_ATTENTION_ICON)); break; case PROP_ATTENTION_MOVIE_NAME: g_value_set_string (value, priv->attention_movie_name); break; case PROP_TOOLTIP_ICON_NAME: g_value_take_string (value, status_notifier_get_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON)); break; case PROP_TOOLTIP_ICON_PIXBUF: g_value_take_object (value, status_notifier_get_pixbuf (sn, STATUS_NOTIFIER_TOOLTIP_ICON)); break; case PROP_TOOLTIP_TITLE: g_value_set_string (value, priv->tooltip_title); break; case PROP_TOOLTIP_BODY: g_value_set_string (value, priv->tooltip_body); break; case PROP_WINDOW_ID: g_value_set_uint (value, priv->window_id); case PROP_STATE: g_value_set_enum (value, priv->state); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void free_icon (StatusNotifier *sn, StatusNotifierIcon icon) { StatusNotifierPrivate *priv = sn->priv; if (priv->icon[icon].has_pixbuf) g_object_unref (priv->icon[icon].pixbuf); else g_free (priv->icon[icon].icon_name); priv->icon[icon].has_pixbuf = FALSE; priv->icon[icon].icon_name = NULL; } static void dbus_free (StatusNotifier *sn) { StatusNotifierPrivate *priv = sn->priv; if (priv->dbus_watch_id > 0) { g_bus_unwatch_name (priv->dbus_watch_id); priv->dbus_watch_id = 0; } if (priv->dbus_sid > 0) { g_signal_handler_disconnect (priv->dbus_proxy, priv->dbus_sid); priv->dbus_sid = 0; } if (G_LIKELY (priv->dbus_owner_id > 0)) { g_bus_unown_name (priv->dbus_owner_id); priv->dbus_owner_id = 0; } if (priv->dbus_proxy) { g_object_unref (priv->dbus_proxy); priv->dbus_proxy = NULL; } if (priv->dbus_reg_id > 0) { g_dbus_connection_unregister_object (priv->dbus_conn, priv->dbus_reg_id); priv->dbus_reg_id = 0; } if (priv->dbus_conn) { g_object_unref (priv->dbus_conn); priv->dbus_conn = NULL; } } static void status_notifier_finalize (GObject *object) { StatusNotifier *sn = (StatusNotifier *) object; StatusNotifierPrivate *priv = sn->priv; guint i; g_free (priv->id); g_free (priv->title); for (i = 0; i < _NB_STATUS_NOTIFIER_ICONS; ++i) free_icon (sn, i); g_free (priv->attention_movie_name); g_free (priv->tooltip_title); g_free (priv->tooltip_body); dbus_free (sn); G_OBJECT_CLASS (status_notifier_parent_class)->finalize (object); } static void dbus_notify (StatusNotifier *sn, guint prop) { StatusNotifierPrivate *priv = sn->priv; const gchar *signal; if (priv->state != STATUS_NOTIFIER_STATE_REGISTERED) return; switch (prop) { case PROP_STATUS: { const gchar *s_status[] = { "Passive", "Active", "NeedsAttention" }; signal = "NewStatus"; g_dbus_connection_emit_signal (priv->dbus_conn, NULL, ITEM_OBJECT, ITEM_INTERFACE, signal, g_variant_new ("(s)", s_status[priv->status]), NULL); return; } case PROP_TITLE: signal = "NewTitle"; break; case PROP_MAIN_ICON_NAME: case PROP_MAIN_ICON_PIXBUF: signal = "NewIcon"; break; case PROP_ATTENTION_ICON_NAME: case PROP_ATTENTION_ICON_PIXBUF: signal = "NewAttentionIcon"; break; case PROP_OVERLAY_ICON_NAME: case PROP_OVERLAY_ICON_PIXBUF: signal = "NewOverlayIcon"; break; case PROP_TOOLTIP_TITLE: case PROP_TOOLTIP_BODY: case PROP_TOOLTIP_ICON_NAME: case PROP_TOOLTIP_ICON_PIXBUF: signal = "NewToolTip"; break; default: g_return_if_reached (); } g_dbus_connection_emit_signal (priv->dbus_conn, NULL, ITEM_OBJECT, ITEM_INTERFACE, signal, NULL, NULL); } /** * status_notifier_new_from_pixbuf: * @id: The application id * @category: The category for the item * @pixbuf: The icon to use as main icon * * Creates a new item * * Returns: (transfer full): A new #StatusNotifier */ StatusNotifier * status_notifier_new_from_pixbuf (const gchar *id, StatusNotifierCategory category, GdkPixbuf *pixbuf) { return (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER, "id", id, "category", category, "main-icon-pixbuf", pixbuf, NULL); } /** * status_notifier_new_from_icon_name: * @id: The application id * @category: The category for the item * @icon_name: The name of the icon to use as main icon * * Creates a new item * * Returns: (transfer full): A new #StatusNotifier */ StatusNotifier * status_notifier_new_from_icon_name (const gchar *id, StatusNotifierCategory category, const gchar *icon_name) { return (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER, "id", id, "category", category, "main-icon-name", icon_name, NULL); } /** * status_notifier_get_id: * @sn: A #StatusNotifier * * Returns the application id of @sn * * Returns: The application id of @sn. The string is owned by @sn, you should * not free it */ const gchar * status_notifier_get_id (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); return sn->priv->id; } /** * status_notifier_get_category: * @sn: A #StatusNotifier * * Returns the category of @sn * * Returns: The category of @sn */ StatusNotifierCategory status_notifier_get_category (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), -1); return sn->priv->category; } /** * status_notifier_set_from_pixbuf: * @sn: A #StatusNotifier * @icon: Which icon to set * @pixbuf: A #GdkPixbuf to use for @icon * * Sets the icon @icon to @pixbuf. * * An icon can either be identified by its Freedesktop-compliant icon name, * or by the icon data itself (via #GdkPixbuf). * * It is currently not possible to set both, as setting one will unset the * other. */ void status_notifier_set_from_pixbuf (StatusNotifier *sn, StatusNotifierIcon icon, GdkPixbuf *pixbuf) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; free_icon (sn, icon); priv->icon[icon].has_pixbuf = TRUE; priv->icon[icon].pixbuf = g_object_ref (pixbuf); notify (sn, prop_name_from_icon[icon]); if (icon != STATUS_NOTIFIER_TOOLTIP_ICON || priv->tooltip_freeze == 0) dbus_notify (sn, prop_name_from_icon[icon]); } /** * status_notifier_set_from_icon_name: * @sn: A #StatusNotifier * @icon: Which icon to set * @icon_name: Name of an icon to use for @icon * * Sets the icon @icon to be @icon_name. * * An icon can either be identified by its Freedesktop-compliant icon name, * or by the icon data itself (via #GdkPixbuf). * * It is currently not possible to set both, as setting one will unset the * other. */ void status_notifier_set_from_icon_name (StatusNotifier *sn, StatusNotifierIcon icon, const gchar *icon_name) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; free_icon (sn, icon); priv->icon[icon].icon_name = g_strdup (icon_name); notify (sn, prop_pixbuf_from_icon[icon]); if (icon != STATUS_NOTIFIER_TOOLTIP_ICON || priv->tooltip_freeze == 0) dbus_notify (sn, prop_name_from_icon[icon]); } /** * status_notifier_has_pixbuf: * @sn: A #StatusNotifier * @icon: Which icon * * Returns whether icon @icon currently has a #GdkPixbuf set or not. If so, the * icon data will be sent via DBus, else the icon name (if any) will be used. * * Returns: %TRUE is a #GdkPixbuf is set for @icon, else %FALSE */ gboolean status_notifier_has_pixbuf (StatusNotifier *sn, StatusNotifierIcon icon) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), FALSE); return sn->priv->icon[icon].has_pixbuf; } /** * status_notifier_get_pixbuf: * @sn: A #StatusNotifier * @icon: The icon to get * * Returns the #GdkPixbuf set for @icon, if there's one. Not that it will return * %NULL if an icon name is set. * * Returns: (transfer full): The #GdkPixbuf set for @icon, or %NULL */ GdkPixbuf * status_notifier_get_pixbuf (StatusNotifier *sn, StatusNotifierIcon icon) { StatusNotifierPrivate *priv; g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); priv = sn->priv; if (!priv->icon[icon].has_pixbuf) return NULL; return g_object_ref (priv->icon[icon].pixbuf); } /** * status_notifier_get_icon_name: * @sn: A #StatusNotifier * @icon: The icon to get * * Returns the icon name set for @icon, if there's one. Not that it will return * %NULL if a #GdkPixbuf is set. * * Returns: (transfer full): A newly allocated string of the icon name set for * @icon, free using g_free() */ gchar * status_notifier_get_icon_name (StatusNotifier *sn, StatusNotifierIcon icon) { StatusNotifierPrivate *priv; g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); priv = sn->priv; if (priv->icon[icon].has_pixbuf) return NULL; return g_strdup (priv->icon[icon].icon_name); } /** * status_notifier_set_attention_movie_name: * @sn: A #StatusNotifier * @movie_name: The name of the movie * * In addition to the icon, the item can also specify an animation associated to * the #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status. * * This should be either a Freedesktop-compliant icon name or a full path. The * visualization can chose between the movie or icon (or using neither of those) * at its discretion. */ void status_notifier_set_attention_movie_name (StatusNotifier *sn, const gchar *movie_name) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; g_free (priv->attention_movie_name); priv->attention_movie_name = g_strdup (movie_name); notify (sn, PROP_ATTENTION_MOVIE_NAME); } /** * status_notifier_get_attention_movie_name: * @sn: A #StatusNotifier * * Returns the movie name set for animation associated with the * #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status * * Returns: A newly allocated string with the movie name, free using g_free() * when done */ gchar * status_notifier_get_attention_movie_name (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); return g_strdup (sn->priv->attention_movie_name); } /** * status_notifier_set_title: * @sn: A #StatusNotifier * @title: The title * * Sets the title of the item (might be used by visualization e.g. in menu of * hidden items when #STATUS_NOTIFIER_STATUS_PASSIVE) */ void status_notifier_set_title (StatusNotifier *sn, const gchar *title) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; g_free (priv->title); priv->title = g_strdup (title); notify (sn, PROP_TITLE); dbus_notify (sn, PROP_TITLE); } /** * status_notifier_get_title: * @sn: A #StatusNotifier * * Returns the title of the item * * Returns: A newly allocated string, free with g_free() when done */ gchar * status_notifier_get_title (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); return g_strdup (sn->priv->title); } /** * status_notifier_set_status: * @sn: A #StatusNotifier * @status: The new status * * Sets the item status to @status, describing the status of this item or of the * associated application. */ void status_notifier_set_status (StatusNotifier *sn, StatusNotifierStatus status) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; priv->status = status; notify (sn, PROP_STATUS); dbus_notify (sn, PROP_STATUS); } /** * status_notifier_get_status: * @sn: A #StatusNotifier * * Returns the status of @sn * * Returns: Current status of @sn */ StatusNotifierStatus status_notifier_get_status (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), -1); return sn->priv->status; } /** * status_notifier_set_window_id: * @sn: A #StatusNotifier * @window_id: The window ID * * Sets the window ID for @sn * * It's the windowing-system dependent identifier for a window, the application * can chose one of its windows to be available trough this property or just set * 0 if it's not interested. */ void status_notifier_set_window_id (StatusNotifier *sn, guint32 window_id) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; priv->window_id = window_id; notify (sn, PROP_WINDOW_ID); } /** * status_notifier_get_window_id: * @sn: A #StatusNotifier * * Returns the windowing-system dependent idnetifier for a window associated * with @sn * * Returns: The window ID associated with @sn */ guint32 status_notifier_get_window_id (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), 0); return sn->priv->window_id; } /** * status_notifier_freeze_tooltip: * @sn:A #StatusNotifier * * Increases the freeze count for tooltip on @sn. If the freeze count is * non-zero, the emission of a DBus signal for StatusNotifierHost to refresh the * ToolTip property will be blocked until the freeze count drops back to zero * (via status_notifier_thaw_tooltip()) * * This is to allow to set the different properties forming the tooltip (title, * body and icon) without triggering a refresh afetr each change (as there is a * single property ToolTip on the DBus item, with all data). * * Every call to status_notifier_freeze_tooltip() should later be followed by a * call to status_notifier_thaw_tooltip() */ void status_notifier_freeze_tooltip (StatusNotifier *sn) { g_return_if_fail (IS_STATUS_NOTIFIER (sn)); ++sn->priv->tooltip_freeze; } /** * status_notifier_thaw_tooltip: * @sn: A #StatusNotifier * * Reverts the effect of a previous call to status_notifier_freeze_tooltip(). If * the freeze count drops back to zero, a signal NewToolTip will be emitted on * the DBus object for @sn, for StatusNotifierHost to refresh its ToolTip * property. * * It is an error to call this function when the freeze count is zero. */ void status_notifier_thaw_tooltip (StatusNotifier *sn) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; g_return_if_fail (priv->tooltip_freeze > 0); if (--priv->tooltip_freeze == 0) dbus_notify (sn, PROP_TOOLTIP_TITLE); } /** * status_notifier_set_tooltip: * @sn: A #StatusNotifier * @icon_name: The icon name to be used for #STATUS_NOTIFIER_TOOLTIP_ICON * @title: The title of the tooltip * @body: The body of the tooltip * * This is an helper function that allows to set icon name, title and body of * the tooltip and then emit one DBus signal NewToolTip. * * It is equivalent to the following code, and similar code can be used e.g. to * set the icon from a #GdkPixbuf instead: * * status_notifier_freeze_tooltip (sn); * status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON, icon_name); * status_notifier_set_tooltip_title (sn, title); * status_notifier_set_tooltip_body (sn, body); * status_notifier_thaw_tooltip (sn); * */ void status_notifier_set_tooltip (StatusNotifier *sn, const gchar *icon_name, const gchar *title, const gchar *body) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; ++priv->tooltip_freeze; status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON, icon_name); status_notifier_set_tooltip_title (sn, title); status_notifier_set_tooltip_body (sn, body); status_notifier_thaw_tooltip (sn); } /** * status_notifier_set_tooltip_title: * @sn: A #StatusNotifier * @title: The tooltip title * * Sets the title of the tooltip * * The tooltip is composed of a title, a body, and an icon. Note that changing * any of these will trigger a DBus signal NewToolTip (for hosts to refresh DBus * property ToolTip), see status_notifier_freeze_tooltip() for changing more * than one and only emitting one DBus signal at the end. */ void status_notifier_set_tooltip_title (StatusNotifier *sn, const gchar *title) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; g_free (priv->tooltip_title); priv->tooltip_title = g_strdup (title); notify (sn, PROP_TOOLTIP_TITLE); if (priv->tooltip_freeze == 0) dbus_notify (sn, PROP_TOOLTIP_TITLE); } /** * status_notifier_get_tooltip_title: * @sn: A #StatusNotifier * * Returns the tooltip title * * Returns: A newly allocated string of the tooltip title, use g_free() when * done */ gchar * status_notifier_get_tooltip_title (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); return g_strdup (sn->priv->tooltip_title); } /** * status_notifier_set_tooltip_body: * @sn: A #StatusNotifier * @body: The tooltip body * * Sets the body of the tooltip * * This body can contain some markup, which consists of a small subset of XHTML. * See http://www.notmart.org/misc/statusnotifieritem/markup.html for more. * * The tooltip is composed of a title, a body, and an icon. Note that changing * any of these will trigger a DBus signal NewToolTip (for hosts to refresh DBus * property ToolTip), see status_notifier_freeze_tooltip() for changing more * than one and only emitting one DBus signal at the end. */ void status_notifier_set_tooltip_body (StatusNotifier *sn, const gchar *body) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; g_free (priv->tooltip_body); priv->tooltip_body = g_strdup (body); notify (sn, PROP_TOOLTIP_BODY); if (priv->tooltip_freeze == 0) dbus_notify (sn, PROP_TOOLTIP_BODY); } /** * status_notifier_get_tooltip_body: * @sn: A #StatusNotifier * * Returns the tooltip body * * Returns: A newly allocated string of the tooltip body, use g_free() when done */ gchar * status_notifier_get_tooltip_body (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); return g_strdup (sn->priv->tooltip_body); } static void method_call (GDBusConnection *conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *params, GDBusMethodInvocation *invocation, gpointer data) { (void)conn; (void)sender; (void)object; (void)interface; StatusNotifier *sn = (StatusNotifier *) data; guint signal; gint x, y; gboolean ret; if (!g_strcmp0 (method, "ContextMenu")) signal = SIGNAL_CONTEXT_MENU; else if (!g_strcmp0 (method, "Activate")) signal = SIGNAL_ACTIVATE; else if (!g_strcmp0 (method, "SecondaryActivate")) signal = SIGNAL_SECONDARY_ACTIVATE; else if (!g_strcmp0 (method, "Scroll")) { gint delta, orientation; gchar *s_orientation; g_variant_get (params, "(is)", &delta, &s_orientation); if (!g_strcmp0 (s_orientation, "vertical")) orientation = STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL; else orientation = STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL; g_free (s_orientation); g_signal_emit (sn, status_notifier_signals[SIGNAL_SCROLL], 0, delta, orientation, &ret); g_dbus_method_invocation_return_value (invocation, NULL); return; } else /* should never happen */ g_return_if_reached (); g_variant_get (params, "(ii)", &x, &y); g_signal_emit (sn, status_notifier_signals[signal], 0, x, y, &ret); g_dbus_method_invocation_return_value (invocation, NULL); } static GVariant * get_icon_pixmap (StatusNotifier *sn, StatusNotifierIcon icon) { StatusNotifierPrivate *priv = sn->priv; GVariantBuilder *builder; cairo_surface_t *surface; cairo_t *cr; gint width, height, stride; guint *data; if (G_UNLIKELY (!priv->icon[icon].has_pixbuf)) return NULL; width = gdk_pixbuf_get_width (priv->icon[icon].pixbuf); height = gdk_pixbuf_get_height (priv->icon[icon].pixbuf); surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create (surface); gdk_cairo_set_source_pixbuf (cr, priv->icon[icon].pixbuf, 0, 0); cairo_paint (cr); cairo_destroy (cr); stride = cairo_image_surface_get_stride (surface); cairo_surface_flush (surface); data = (guint *) cairo_image_surface_get_data (surface); #if G_BYTE_ORDER == G_LITTLE_ENDIAN guint i, max; max = (stride * height) / sizeof (guint); for (i = 0; i < max; ++i) data[i] = GUINT_TO_BE (data[i]); #endif builder = g_variant_builder_new (G_VARIANT_TYPE ("a(iiay)")); g_variant_builder_open (builder, G_VARIANT_TYPE ("(iiay)")); g_variant_builder_add (builder, "i", width); g_variant_builder_add (builder, "i", height); g_variant_builder_add_value (builder, g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, stride * height, TRUE, (GDestroyNotify) cairo_surface_destroy, surface)); g_variant_builder_close (builder); GVariant *pixmap = g_variant_new ("a(iiay)", builder); g_variant_builder_unref(builder); // Allow the builder to be deallocated return pixmap; } static GVariant * get_prop (GDBusConnection *conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *property, GError **error, gpointer data) { (void)conn; (void)sender; (void)object; (void)interface; (void)error; StatusNotifier *sn = (StatusNotifier *) data; StatusNotifierPrivate *priv = sn->priv; if (!g_strcmp0 (property, "Id")) return g_variant_new ("s", priv->id); else if (!g_strcmp0 (property, "Category")) { const gchar *s_category[] = { "ApplicationStatus", "Communications", "SystemServices", "Hardware" }; return g_variant_new ("s", s_category[priv->category]); } else if (!g_strcmp0 (property, "Title")) return g_variant_new ("s", (priv->title) ? priv->title : ""); else if (!g_strcmp0 (property, "Status")) { const gchar *s_status[] = { "Passive", "Active", "NeedsAttention" }; return g_variant_new ("s", s_status[priv->status]); } else if (!g_strcmp0 (property, "WindowId")) return g_variant_new ("i", priv->window_id); else if (!g_strcmp0 (property, "IconName")) return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_ICON].has_pixbuf) ? ((priv->icon[STATUS_NOTIFIER_ICON].icon_name) ? priv->icon[STATUS_NOTIFIER_ICON].icon_name : "") : ""); else if (!g_strcmp0 (property, "IconPixmap")) return get_icon_pixmap (sn, STATUS_NOTIFIER_ICON); else if (!g_strcmp0 (property, "OverlayIconName")) return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].has_pixbuf) ? ((priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].icon_name) ? priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].icon_name : "") : ""); else if (!g_strcmp0 (property, "OverlayIconPixmap")) return get_icon_pixmap (sn, STATUS_NOTIFIER_OVERLAY_ICON); else if (!g_strcmp0 (property, "AttentionIconName")) return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].has_pixbuf) ? ((priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].icon_name) ? priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].icon_name : "") : ""); else if (!g_strcmp0 (property, "AttentionIconPixmap")) return get_icon_pixmap (sn, STATUS_NOTIFIER_ATTENTION_ICON); else if (!g_strcmp0 (property, "AttentionMovieName")) return g_variant_new ("s", (priv->attention_movie_name) ? priv->attention_movie_name : ""); else if (!g_strcmp0 (property, "ToolTip")) { GVariant *variant; GVariant *pixmap; if (!priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].has_pixbuf) { variant = g_variant_new ("(sa(iiay)ss)", (priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].icon_name) ? priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].icon_name : "", NULL, (priv->tooltip_title) ? priv->tooltip_title : "", (priv->tooltip_body) ? priv->tooltip_body : ""); return variant; } pixmap = get_icon_pixmap (sn, STATUS_NOTIFIER_TOOLTIP_ICON); variant = g_variant_new ("(sa(iiay)ss)", "", pixmap, (priv->tooltip_title) ? priv->tooltip_title : "", (priv->tooltip_body) ? priv->tooltip_body : ""); g_variant_unref (pixmap); return variant; } g_return_val_if_reached (NULL); } static void dbus_failed (StatusNotifier *sn, GError *error, gboolean fatal) { StatusNotifierPrivate *priv = sn->priv; dbus_free (sn); if (fatal) { priv->state = STATUS_NOTIFIER_STATE_FAILED; notify (sn, PROP_STATE); } g_signal_emit (sn, status_notifier_signals[SIGNAL_REGISTRATION_FAILED], 0, error); g_error_free (error); } static void bus_acquired (GDBusConnection *conn, const gchar *name, gpointer data) { (void)name; GError *err = NULL; StatusNotifier *sn = (StatusNotifier *) data; StatusNotifierPrivate *priv = sn->priv; GDBusInterfaceVTable interface_vtable = { .method_call = method_call, .get_property = get_prop, .set_property = NULL }; GDBusNodeInfo *info; info = g_dbus_node_info_new_for_xml (item_xml, NULL); priv->dbus_reg_id = g_dbus_connection_register_object (conn, ITEM_OBJECT, info->interfaces[0], &interface_vtable, sn, NULL, &err); g_dbus_node_info_unref (info); if (priv->dbus_reg_id == 0) { dbus_failed (sn, err, TRUE); return; } priv->dbus_conn = g_object_ref (conn); } static void register_item_cb (GObject *sce, GAsyncResult *result, gpointer data) { GError *err = NULL; StatusNotifier *sn = (StatusNotifier *) data; StatusNotifierPrivate *priv = sn->priv; GVariant *variant; variant = g_dbus_proxy_call_finish ((GDBusProxy *) sce, result, &err); if (!variant) { dbus_failed (sn, err, TRUE); return; } g_variant_unref (variant); priv->state = STATUS_NOTIFIER_STATE_REGISTERED; notify (sn, PROP_STATE); } static void name_acquired (GDBusConnection *conn, const gchar *name, gpointer data) { (void)conn; StatusNotifier *sn = (StatusNotifier *) data; StatusNotifierPrivate *priv = sn->priv; g_dbus_proxy_call (priv->dbus_proxy, "RegisterStatusNotifierItem", g_variant_new ("(s)", name), G_DBUS_CALL_FLAGS_NONE, -1, NULL, register_item_cb, sn); g_object_unref (priv->dbus_proxy); priv->dbus_proxy = NULL; } static void name_lost (GDBusConnection *conn, const gchar *name, gpointer data) { (void)name; GError *err = NULL; StatusNotifier *sn = (StatusNotifier *) data; if (!conn) g_set_error (&err, STATUS_NOTIFIER_ERROR, STATUS_NOTIFIER_ERROR_NO_CONNECTION, "Failed to establish DBus connection"); else g_set_error (&err, STATUS_NOTIFIER_ERROR, STATUS_NOTIFIER_ERROR_NO_NAME, "Failed to acquire name for item"); dbus_failed (sn, err, TRUE); } static void dbus_reg_item (StatusNotifier *sn) { StatusNotifierPrivate *priv = sn->priv; gchar buf[64], *b = buf; if (G_UNLIKELY (g_snprintf (buf, 64, "org.kde.StatusNotifierItem-%u-%u", getpid (), ++uniq_id) >= 64)) b = g_strdup_printf ("org.kde.StatusNotifierItem-%u-%u", getpid (), uniq_id); priv->dbus_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, b, G_BUS_NAME_OWNER_FLAGS_NONE, bus_acquired, name_acquired, name_lost, sn, NULL); if (G_UNLIKELY (b != buf)) g_free (b); } static void watcher_signal (GDBusProxy *proxy, const gchar *sender, const gchar *signal, GVariant *params, StatusNotifier *sn) { (void)proxy; (void)sender; (void)params; StatusNotifierPrivate *priv = sn->priv; if (!g_strcmp0 (signal, "StatusNotifierHostRegistered")) { g_signal_handler_disconnect (priv->dbus_proxy, priv->dbus_sid); priv->dbus_sid = 0; dbus_reg_item (sn); } } static void proxy_cb (GObject *sce, GAsyncResult *result, gpointer data) { (void)sce; GError *err = NULL; StatusNotifier *sn = (StatusNotifier *) data; StatusNotifierPrivate *priv = sn->priv; GVariant *variant; priv->dbus_proxy = g_dbus_proxy_new_for_bus_finish (result, &err); if (!priv->dbus_proxy) { dbus_failed (sn, err, TRUE); return; } variant = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "IsStatusNotifierHostRegistered"); if (!variant || !g_variant_get_boolean (variant)) { GDBusProxy *proxy; g_set_error (&err, STATUS_NOTIFIER_ERROR, STATUS_NOTIFIER_ERROR_NO_HOST, "No Host registered on the Watcher"); if (variant) g_variant_unref (variant); /* keep the proxy, we'll wait for the signal when a host registers */ proxy = priv->dbus_proxy; /* (so dbus_free() from dbus_failed() doesn't unref) */ priv->dbus_proxy = NULL; dbus_failed (sn, err, FALSE); priv->dbus_proxy = proxy; priv->dbus_sid = g_signal_connect (priv->dbus_proxy, "g-signal", (GCallback) watcher_signal, sn); return; } g_variant_unref (variant); dbus_reg_item (sn); } static void watcher_appeared (GDBusConnection *conn, const gchar *name, const gchar *owner, gpointer data) { (void)conn; (void)name; (void)owner; StatusNotifier *sn = data; StatusNotifierPrivate *priv = sn->priv; GDBusNodeInfo *info; g_bus_unwatch_name (priv->dbus_watch_id); priv->dbus_watch_id = 0; info = g_dbus_node_info_new_for_xml (watcher_xml, NULL); g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, info->interfaces[0], WATCHER_NAME, WATCHER_OBJECT, WATCHER_INTERFACE, NULL, proxy_cb, sn); g_dbus_node_info_unref (info); } static void watcher_vanished (GDBusConnection *conn, const gchar *name, gpointer data) { (void)conn; (void)name; GError *err = NULL; StatusNotifier *sn = data; StatusNotifierPrivate *priv = sn->priv; guint id; /* keep the watch active, so if a watcher shows up we'll resume the * registering automatically */ id = priv->dbus_watch_id; /* (so dbus_free() from dbus_failed() doesn't unwatch) */ priv->dbus_watch_id = 0; g_set_error (&err, STATUS_NOTIFIER_ERROR, STATUS_NOTIFIER_ERROR_NO_WATCHER, "No Watcher found"); dbus_failed (sn, err, FALSE); priv->dbus_watch_id = id; } /** * status_notifier_register: * @sn: A #StatusNotifier * * Registers @sn to the StatusNotifierWatcher over DBus. * * Once you have created your #StatusNotifier you need to register it, so any * host/visualization can use it and update their GUI as needed. * * This function will connect to the StatusNotifierWatcher and make sure at * least one StatusNotifierHost is registered. Then, it will register a new * StatusNotifierItem on the session bus and register it with the watcher. * * When done, property #StatusNotifier:state will change to * %STATUS_NOTIFIER_STATE_REGISTERED. If something fails, signal * #StatusNotifier::registration-failed will be emitted, at which point you * should fallback to using the systray. * * However there are two possible types of failures: fatal and non-fatal ones. * Fatal error means that #StatusNotifier:state will be * %STATUS_NOTIFIER_STATE_FAILED and you can unref @sn. * * Non-fatal error means it will still be %STATUS_NOTIFIER_STATE_REGISTERING as * the registration process could still eventually succeed. For example, if * there was no host registered on the watcher, as soon as signal * StatusNotifierHostRegistered is emitted on the watcher, the registration * process for @sn will complete and #StatusNotifier:state set to * %STATUS_NOTIFIER_STATE_REGISTERED, at which point you should stop using the * systray. * * This also means it is possible to have multiple signals * #StatusNotifier::registration-failed emitted on the same #StatusNotifier. * * Note that you can call status_notifier_register() after a fatal error * occured, to try again. You can also unref @sn while it is * %STATUS_NOTIFIER_STATE_REGISTERING safely. */ void status_notifier_register (StatusNotifier *sn) { StatusNotifierPrivate *priv; g_return_if_fail (IS_STATUS_NOTIFIER (sn)); priv = sn->priv; if (priv->state == STATUS_NOTIFIER_STATE_REGISTERING || priv->state == STATUS_NOTIFIER_STATE_REGISTERED) return; priv->state = STATUS_NOTIFIER_STATE_REGISTERING; priv->dbus_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, WATCHER_NAME, G_BUS_NAME_WATCHER_FLAGS_AUTO_START, watcher_appeared, watcher_vanished, sn, NULL); } /** * status_notifier_get_state: * @sn: A #StatusNotifier * * Returns the DBus registration state of @sn. See status_notifier_register() * for more. * * Returns: The DBus registration state of @sn */ StatusNotifierState status_notifier_get_state (StatusNotifier *sn) { g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), FALSE); return sn->priv->state; } qTox/src/platform/statusnotifier/statusnotifier.h000066400000000000000000000251171415623743500227630ustar00rootroot00000000000000/* * statusnotifier - Copyright (C) 2014 Olivier Brunel * * statusnotifier.h * Copyright (C) 2014 Olivier Brunel * * This file is part of statusnotifier. * * statusnotifier is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * statusnotifier is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * statusnotifier. If not, see http://www.gnu.org/licenses/ */ #ifndef __STATUS_NOTIFIER_H__ #define __STATUS_NOTIFIER_H__ #include #include #include #include G_BEGIN_DECLS typedef struct _StatusNotifier StatusNotifier; typedef struct _StatusNotifierPrivate StatusNotifierPrivate; typedef struct _StatusNotifierClass StatusNotifierClass; #define TYPE_STATUS_NOTIFIER (status_notifier_get_type()) #define STATUS_NOTIFIER(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_STATUS_NOTIFIER, StatusNotifier)) #define STATUS_NOTIFIER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_STATUS_NOTIFIER, StatusNotiferClass)) #define IS_STATUS_NOTIFIER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_STATUS_NOTIFIER)) #define IS_STATUS_NOTIFIER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((obj), TYPE_STATUS_NOTIFIER)) #define STATUS_NOTIFIER_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_STATUS_NOTIFIER, StatusNotifierClass)) GType status_notifier_get_type(void) G_GNUC_CONST; #define STATUS_NOTIFIER_ERROR g_quark_from_static_string("StatusNotifier error") /** * StatusNotifierError: * @STATUS_NOTIFIER_ERROR_NO_CONNECTION: Failed to establish connection to * register service on session bus * @STATUS_NOTIFIER_ERROR_NO_NAME: Failed to acquire name for the item on the * session bus * @STATUS_NOTIFIER_ERROR_NO_WATCHER: No StatusNotifierWatcher found on the * session bus * @STATUS_NOTIFIER_ERROR_NO_HOST: No StatusNotifierHost registered with the * StatusNotifierWatcher * * Errors that can occur while trying to register the item. Note that errors * other the #StatusNotifierError might be returned. */ typedef enum { STATUS_NOTIFIER_ERROR_NO_CONNECTION = 0, STATUS_NOTIFIER_ERROR_NO_NAME, STATUS_NOTIFIER_ERROR_NO_WATCHER, STATUS_NOTIFIER_ERROR_NO_HOST } StatusNotifierError; /** * StatusNotifierState: * @STATUS_NOTIFIER_STATE_NOT_REGISTERED: Item hasn't yet been asked to * register, i.e. no call to status_notifier_register() have been made yet * @STATUS_NOTIFIER_STATE_REGISTERING: Item is in the process of registering. * This state is also valid after #StatusNotifier::registration-failed was * emitted, if the item is waiting for possible "recovery" (e.g. if no host was * registered on watcher, waiting for one to do so) * @STATUS_NOTIFIER_STATE_REGISTERED: Item was sucessfully registered on DBus * and StatusNotifierWatcher * @STATUS_NOTIFIER_STATE_FAILED: Registration failed, with no possible pending * recovery * * State in which a #StatusNotifier item can be. See status_notifier_register() * for more */ typedef enum { STATUS_NOTIFIER_STATE_NOT_REGISTERED = 0, STATUS_NOTIFIER_STATE_REGISTERING, STATUS_NOTIFIER_STATE_REGISTERED, STATUS_NOTIFIER_STATE_FAILED } StatusNotifierState; /** * StatusNotifierIcon: * @STATUS_NOTIFIER_ICON: The icon that can be used by the visualization to * identify the item. * @STATUS_NOTIFIER_ATTENTION_ICON: The icon that can be used by the * visualization when the item's status is * %STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION. * @STATUS_NOTIFIER_OVERLAY_ICON: This can be used by the visualization to * indicate extra state information, for instance as an overlay for the main * icon. * @STATUS_NOTIFIER_TOOLTIP_ICON: The icon that can be used be the visualization * in the tooltip of the item. * * Possible icons that can be used on a status notifier item. */ typedef enum { STATUS_NOTIFIER_ICON = 0, STATUS_NOTIFIER_ATTENTION_ICON, STATUS_NOTIFIER_OVERLAY_ICON, STATUS_NOTIFIER_TOOLTIP_ICON, /*< private >*/ _NB_STATUS_NOTIFIER_ICONS } StatusNotifierIcon; /** * StatusNotifierCategory: * @STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS: The item describes the status * of a generic application, for instance the current state of a media player. * In the case where the category of the item can not be known, such as when the * item is being proxied from another incompatible or emulated system, this can * be used a sensible default fallback. * @STATUS_NOTIFIER_CATEGORY_COMMUNICATIONS: The item describes the status of * communication oriented applications, like an instant messenger or an email * client. * @STATUS_NOTIFIER_CATEGORY_SYSTEM_SERVICES: The item describes services of the * system not seen as a stand alone application by the user, such as an * indicator for the activity of a disk indexing service. * @STATUS_NOTIFIER_CATEGORY_HARDWARE: The item describes the state and control * of a particular hardware, such as an indicator of the battery charge or sound * card volume control. * * The category of the status notifier item. */ typedef enum { STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS = 0, STATUS_NOTIFIER_CATEGORY_COMMUNICATIONS, STATUS_NOTIFIER_CATEGORY_SYSTEM_SERVICES, STATUS_NOTIFIER_CATEGORY_HARDWARE } StatusNotifierCategory; /** * StatusNotifierStatus: * @STATUS_NOTIFIER_STATUS_PASSIVE: The item doesn't convey important * information to the user, it can be considered an "idle" status and is likely * that visualizations will chose to hide it. * @STATUS_NOTIFIER_STATUS_ACTIVE: The item is active, is more important that * the item will be shown in some way to the user. * @STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION: The item carries really important * information for the user, such as battery charge running out and is wants to * incentive the direct user intervention. Visualizations should emphasize in * some way the items with this status. * * The status of the status notifier item or its associated application. */ typedef enum { STATUS_NOTIFIER_STATUS_PASSIVE = 0, STATUS_NOTIFIER_STATUS_ACTIVE, STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION } StatusNotifierStatus; /** * StatusNotifierScrollOrientation: * @STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL: Scroll request was * horizontal. * @STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL: Scroll request was vertical. * * The orientation of a scroll request performed on the representation of the * item in the visualization. */ typedef enum { STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL = 0, STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL } StatusNotifierScrollOrientation; struct _StatusNotifier { /*< private >*/ GObject parent; StatusNotifierPrivate* priv; }; /** * StatusNotifierClass: * @parent_class: Parent class * @registration_failed: When registering the item failed, e.g. because there's * no StatusNotifierHost registered (yet); If this occurs, you should fallback * to using the systray * @context_menu: Item should show a context menu, this is typically a * consequence of user input, such as mouse right click over the graphical * representation of the item. * @activate: Activation of the item was requested, this is typically a * consequence of user input, such as mouse left click over the graphical * representation of the item. * @secondary_activate: Secondary and less important form of activation * (compared to @activate) of the item was requested. This is typically a * consequence of user input, such as mouse middle click over the graphical * representation of the item. * @scroll: The user asked for a scroll action. This is caused from input such * as mouse wheel over the graphical representation of the item. */ struct _StatusNotifierClass { GObjectClass parent_class; /* signals */ void (*registration_failed)(StatusNotifier* sn, GError* error); gboolean (*context_menu)(StatusNotifier* sn, gint x, gint y); gboolean (*activate)(StatusNotifier* sn, gint x, gint y); gboolean (*secondary_activate)(StatusNotifier* sn, gint x, gint y); gboolean (*scroll)(StatusNotifier* sn, gint delta, StatusNotifierScrollOrientation orientation); }; StatusNotifier* status_notifier_new_from_pixbuf(const gchar* id, StatusNotifierCategory category, GdkPixbuf* pixbuf); StatusNotifier* status_notifier_new_from_icon_name(const gchar* id, StatusNotifierCategory category, const gchar* icon_name); const gchar* status_notifier_get_id(StatusNotifier* sn); StatusNotifierCategory status_notifier_get_category(StatusNotifier* sn); void status_notifier_set_from_pixbuf(StatusNotifier* sn, StatusNotifierIcon icon, GdkPixbuf* pixbuf); void status_notifier_set_from_icon_name(StatusNotifier* sn, StatusNotifierIcon icon, const gchar* icon_name); gboolean status_notifier_has_pixbuf(StatusNotifier* sn, StatusNotifierIcon icon); GdkPixbuf* status_notifier_get_pixbuf(StatusNotifier* sn, StatusNotifierIcon icon); gchar* status_notifier_get_icon_name(StatusNotifier* sn, StatusNotifierIcon icon); void status_notifier_set_attention_movie_name(StatusNotifier* sn, const gchar* movie_name); gchar* status_notifier_get_attention_movie_name(StatusNotifier* sn); void status_notifier_set_title(StatusNotifier* sn, const gchar* title); gchar* status_notifier_get_title(StatusNotifier* sn); void status_notifier_set_status(StatusNotifier* sn, StatusNotifierStatus status); StatusNotifierStatus status_notifier_get_status(StatusNotifier* sn); void status_notifier_set_window_id(StatusNotifier* sn, guint32 window_id); guint32 status_notifier_get_window_id(StatusNotifier* sn); void status_notifier_freeze_tooltip(StatusNotifier* sn); void status_notifier_thaw_tooltip(StatusNotifier* sn); void status_notifier_set_tooltip(StatusNotifier* sn, const gchar* icon_name, const gchar* title, const gchar* body); void status_notifier_set_tooltip_title(StatusNotifier* sn, const gchar* title); gchar* status_notifier_get_tooltip_title(StatusNotifier* sn); void status_notifier_set_tooltip_body(StatusNotifier* sn, const gchar* body); gchar* status_notifier_get_tooltip_body(StatusNotifier* sn); void status_notifier_register(StatusNotifier* sn); StatusNotifierState status_notifier_get_state(StatusNotifier* sn); G_END_DECLS #endif /* __STATUS_NOTIFIER_H__ */ qTox/src/platform/timer.h000066400000000000000000000017131415623743500157310ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifdef QTOX_PLATFORM_EXT #ifndef PLATFORM_TIMER_H #define PLATFORM_TIMER_H #include namespace Platform { uint32_t getIdleTime(); } #endif // PLATFORM_TIMER_H #endif // QTOX_PLATFORM_EXT qTox/src/platform/timer_osx.cpp000066400000000000000000000036751415623743500171660ustar00rootroot00000000000000/* Pidgin is the legal property of its developers, whose names are too numerous to list here. Please refer to the COPYRIGHT file distributed with this source distribution (which can be found at ). Copyright © 2006 by Richard Laager Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/platform/timer.h" #include #include uint32_t Platform::getIdleTime() { // https://hg.pidgin.im/pidgin/main/file/13e4ae613a6a/pidgin/gtkidle.c // relevant code introduced to Pidgin in: // https://hg.pidgin.im/pidgin/main/diff/8ff1c408ef3e/src/gtkidle.c static io_service_t service = 0; CFTypeRef property; uint64_t idleTime_ns = 0; if (!service) { mach_port_t master; IOMasterPort(MACH_PORT_NULL, &master); service = IOServiceGetMatchingService(master, IOServiceMatching("IOHIDSystem")); } property = IORegistryEntryCreateCFProperty(service, CFSTR("HIDIdleTime"), kCFAllocatorDefault, 0); CFNumberGetValue((CFNumberRef)property, kCFNumberSInt64Type, &idleTime_ns); CFRelease(property); return idleTime_ns / 1000000; } qTox/src/platform/timer_win.cpp000066400000000000000000000020361415623743500171400ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/platform/timer.h" #include uint32_t Platform::getIdleTime() { LASTINPUTINFO info = {0, 0}; info.cbSize = sizeof(info); if (GetLastInputInfo(&info)) return GetTickCount() - info.dwTime; return 0; } qTox/src/platform/timer_x11.cpp000066400000000000000000000031651415623743500167600ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/platform/timer.h" #include "src/platform/x11_display.h" #include #include uint32_t Platform::getIdleTime() { uint32_t idleTime = 0; Display* display = X11Display::lock(); if (!display) { qDebug() << "XOpenDisplay failed"; X11Display::unlock(); return 0; } int32_t x11event = 0, x11error = 0; static int32_t hasExtension = XScreenSaverQueryExtension(display, &x11event, &x11error); if (hasExtension) { XScreenSaverInfo* info = XScreenSaverAllocInfo(); if (info) { XScreenSaverQueryInfo(display, DefaultRootWindow(display), info); idleTime = info->idle; XFree(info); } else qDebug() << "XScreenSaverAllocInfo() failed"; } X11Display::unlock(); return idleTime; } qTox/src/platform/x11_display.cpp000066400000000000000000000031141415623743500172770ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include "src/platform/x11_display.h" #include #include namespace Platform { struct X11DisplayPrivate { Display* display; QMutex mutex; X11DisplayPrivate() : display(XOpenDisplay(nullptr)) { } ~X11DisplayPrivate() { if (display) { XCloseDisplay(display); } } static X11DisplayPrivate& getSingleInstance() { // object created on-demand static X11DisplayPrivate singleInstance; return singleInstance; } }; Display* X11Display::lock() { X11DisplayPrivate& singleInstance = X11DisplayPrivate::getSingleInstance(); singleInstance.mutex.lock(); return singleInstance.display; } void X11Display::unlock() { X11DisplayPrivate::getSingleInstance().mutex.unlock(); } } qTox/src/platform/x11_display.h000066400000000000000000000020161415623743500167440ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifdef QTOX_PLATFORM_EXT #ifndef PLATFORM_X11_DISPLAY_H #define PLATFORM_X11_DISPLAY_H typedef struct _XDisplay Display; namespace Platform { namespace X11Display { Display* lock(); void unlock(); } } #endif // PLATFORM_X11_DISPLAY_H #endif // QTOX_PLATFORM_EXT qTox/src/util/000077500000000000000000000000001415623743500135675ustar00rootroot00000000000000qTox/src/util/compatiblerecursivemutex.h000066400000000000000000000020571415623743500210760ustar00rootroot00000000000000/* Copyright © 2021 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #pragma once #include #include #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) class CompatibleRecursiveMutex : public QRecursiveMutex {}; #else class CompatibleRecursiveMutex : public QMutex { public: CompatibleRecursiveMutex() : QMutex(QMutex::Recursive) {} }; #endif qTox/src/util/strongtype.h000066400000000000000000000075151415623743500161660ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef STRONGTYPE_H #define STRONGTYPE_H #include template struct Addable { T operator+(T const& other) const { return static_cast(*this).get() + other.get(); }; }; template struct UnderlyingAddable { T operator+(Underlying const& other) const { return T(static_cast(*this).get() + other); }; }; template struct UnitlessDifferencable { T operator-(Underlying const& other) const { return T(static_cast(*this).get() - other); }; Underlying operator-(T const& other) const { return static_cast(*this).get() - other.get(); } }; template struct Incrementable { T& operator++() { auto& underlying = static_cast(*this).get(); ++underlying; return static_cast(*this); } T operator++(int) { auto ret = T(static_cast(*this)); ++(*this); return ret; } }; template struct EqualityComparible { bool operator==(const T& other) const { return static_cast(*this).get() == other.get(); }; bool operator!=(const T& other) const { return static_cast(*this).get() != other.get(); }; }; template struct Hashable { friend uint qHash(const Hashable& key, uint seed = 0) { return qHash(static_cast(*key).get(), seed); } }; template struct Orderable : EqualityComparible { bool operator<(const T& rhs) const { return static_cast(*this).get() < rhs.get(); } bool operator>(const T& rhs) const { return static_cast(*this).get() > rhs.get(); } bool operator>=(const T& rhs) const { return static_cast(*this).get() >= rhs.get(); } bool operator<=(const T& rhs) const { return static_cast(*this).get() <= rhs.get(); } }; /* This class facilitates creating a named class which wraps underlying POD, * avoiding implict casts and arithmetic of the underlying data. * Usage: Declare named type with arbitrary tag, then hook up Qt metatype for use * in signals/slots. For queued connections, registering the metatype is also * required before the type is used. * using ReceiptNum = NamedType; * Q_DECLARE_METATYPE(ReceiptNum); * qRegisterMetaType(); */ template class... Properties> class NamedType : public Properties, T>... { public: using UnderlyingType = T; NamedType() {} explicit NamedType(T const& value) : value_(value) {} T& get() { return value_; } T const& get() const {return value_; } private: T value_; }; template class... Properties> uint qHash(const NamedType& key, uint seed = 0) { return qHash(key.get(), seed); } #endif // STRONGTYPE_H qTox/src/video/000077500000000000000000000000001415623743500137205ustar00rootroot00000000000000qTox/src/video/cameradevice.cpp000066400000000000000000000367641415623743500170540ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #include #include extern "C" { #include #include } #include "cameradevice.h" #include "src/persistence/settings.h" #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #define USING_V4L 1 #else #define USING_V4L 0 #endif #ifdef Q_OS_WIN #include "src/platform/camera/directshow.h" #endif #if USING_V4L #include "src/platform/camera/v4l2.h" #endif #ifdef Q_OS_OSX #include "src/platform/camera/avfoundation.h" #endif /** * @class CameraDevice * * Maintains an FFmpeg context for open camera devices, * takes care of sharing the context accross users and closing * the camera device when not in use. The device can be opened * recursively, and must then be closed recursively */ /** * @var const QString CameraDevice::devName * @brief Short name of the device * * @var AVFormatContext* CameraDevice::context * @brief Context of the open device, must always be valid * * @var std::atomic_int CameraDevice::refcount; * @brief Number of times the device was opened */ QHash CameraDevice::openDevices; QMutex CameraDevice::openDeviceLock, CameraDevice::iformatLock; AVInputFormat* CameraDevice::iformat{nullptr}; AVInputFormat* CameraDevice::idesktopFormat{nullptr}; CameraDevice::CameraDevice(const QString& devName, AVFormatContext* context) : devName{devName} , context{context} , refcount{1} { } CameraDevice* CameraDevice::open(QString devName, AVDictionary** options) { openDeviceLock.lock(); AVFormatContext* fctx = nullptr; CameraDevice* dev = openDevices.value(devName); int aduration; std::string devString; if (dev) { goto out; } AVInputFormat* format; if (devName.startsWith("x11grab#")) { devName = devName.mid(8); format = idesktopFormat; } else if (devName.startsWith("gdigrab#")) { devName = devName.mid(8); format = idesktopFormat; } else { format = iformat; } devString = devName.toStdString(); if (avformat_open_input(&fctx, devString.c_str(), format, options) < 0) { goto out; } // Fix avformat_find_stream_info hanging on garbage input #if FF_API_PROBESIZE_32 aduration = fctx->max_analyze_duration2 = 0; #else aduration = fctx->max_analyze_duration = 0; #endif if (avformat_find_stream_info(fctx, nullptr) < 0) { avformat_close_input(&fctx); goto out; } #if FF_API_PROBESIZE_32 fctx->max_analyze_duration2 = aduration; #else fctx->max_analyze_duration = aduration; #endif dev = new CameraDevice{devName, fctx}; openDevices[devName] = dev; out: openDeviceLock.unlock(); return dev; } /** * @brief Opens a device. * * Opens a device, creating a new one if needed * If the device is alreay open in another mode, the mode * will be ignored and the existing device is used * If the mode does not exist, a new device can't be opened. * * @param devName Device name to open. * @param mode Mode of device to open. * @return CameraDevice if the device could be opened, nullptr otherwise. */ CameraDevice* CameraDevice::open(QString devName, VideoMode mode) { if (!getDefaultInputFormat()) return nullptr; if (devName == "none") { qDebug() << "Tried to open the null device"; return nullptr; } float FPS = 5; if (mode.FPS > 0.0f) { FPS = mode.FPS; } else { qWarning() << "VideoMode could be invalid!"; } const std::string videoSize = QStringLiteral("%1x%2").arg(mode.width).arg(mode.height).toStdString(); const std::string framerate = QString{}.setNum(FPS).toStdString(); AVDictionary* options = nullptr; if (!iformat) ; #if USING_V4L else if (devName.startsWith("x11grab#")) { QSize screen; if (mode.width && mode.height) { screen.setWidth(mode.width); screen.setHeight(mode.height); } else { QScreen* defaultScreen = QApplication::primaryScreen(); qreal pixRatio = defaultScreen->devicePixelRatio(); screen = defaultScreen->size(); // Workaround https://trac.ffmpeg.org/ticket/4574 by choping 1 px bottom and right // Actually, let's chop two pixels, toxav hates odd resolutions (off by one stride) screen.setWidth((screen.width() * pixRatio) - 2); screen.setHeight((screen.height() * pixRatio) - 2); } const std::string screenVideoSize = QStringLiteral("%1x%2").arg(screen.width()).arg(screen.height()).toStdString(); av_dict_set(&options, "video_size", screenVideoSize.c_str(), 0); devName += QString("+%1,%2").arg(QString().setNum(mode.x), QString().setNum(mode.y)); av_dict_set(&options, "framerate", framerate.c_str(), 0); } else if (iformat->name == QString("video4linux2,v4l2") && mode) { av_dict_set(&options, "video_size", videoSize.c_str(), 0); av_dict_set(&options, "framerate", framerate.c_str(), 0); const std::string pixelFormatStr = v4l2::getPixelFormatString(mode.pixel_format).toStdString(); // don't try to set a format string that doesn't exist if (pixelFormatStr != "unknown" && pixelFormatStr != "invalid") { const char* pixel_format = pixelFormatStr.c_str(); av_dict_set(&options, "pixel_format", pixel_format, 0); } } #endif #ifdef Q_OS_WIN else if (devName.startsWith("gdigrab#")) { const std::string offsetX = QString().setNum(mode.x).toStdString(); const std::string offsetY = QString().setNum(mode.y).toStdString(); av_dict_set(&options, "framerate", framerate.c_str(), 0); av_dict_set(&options, "offset_x", offsetX.c_str(), 0); av_dict_set(&options, "offset_y", offsetY.c_str(), 0); av_dict_set(&options, "video_size", videoSize.c_str(), 0); } else if (iformat->name == QString("dshow") && mode) { av_dict_set(&options, "video_size", videoSize.c_str(), 0); av_dict_set(&options, "framerate", framerate.c_str(), 0); } #endif #ifdef Q_OS_OSX else if (iformat->name == QString("avfoundation")) { if (mode) { av_dict_set(&options, "video_size", videoSize.c_str(), 0); av_dict_set(&options, "framerate", framerate.c_str(), 0); } else if (devName.startsWith(avfoundation::CAPTURE_SCREEN)) { av_dict_set(&options, "framerate", framerate.c_str(), 0); av_dict_set_int(&options, "capture_cursor", 1, 0); av_dict_set_int(&options, "capture_mouse_clicks", 1, 0); } } #endif else if (mode) { qWarning() << "Video mode-setting not implemented for input " << iformat->name; Q_UNUSED(mode); } CameraDevice* dev = open(devName, &options); if (options) { av_dict_free(&options); } return dev; } /** * @brief Opens the device again. Never fails */ void CameraDevice::open() { ++refcount; } /** * @brief Closes the device. Never fails. * @note If returns true, "this" becomes invalid. * @return True, if device finally deleted (closed last reference), * false otherwise (if other references exist). */ bool CameraDevice::close() { if (--refcount > 0) return false; openDeviceLock.lock(); openDevices.remove(devName); openDeviceLock.unlock(); avformat_close_input(&context); delete this; return true; } /** * @brief Get raw device list * @note Uses avdevice_list_devices * @return Raw device list */ QVector> CameraDevice::getRawDeviceListGeneric() { QVector> devices; if (!getDefaultInputFormat()) return devices; // Alloc an input device context AVFormatContext* s; if (!(s = avformat_alloc_context())) return devices; if (!iformat->priv_class || !AV_IS_INPUT_DEVICE(iformat->priv_class->category)) { avformat_free_context(s); return devices; } s->iformat = iformat; if (s->iformat->priv_data_size > 0) { s->priv_data = av_mallocz(s->iformat->priv_data_size); if (!s->priv_data) { avformat_free_context(s); return devices; } if (s->iformat->priv_class) { *(const AVClass**)s->priv_data = s->iformat->priv_class; av_opt_set_defaults(s->priv_data); } } else { s->priv_data = nullptr; } // List the devices for this context AVDeviceInfoList* devlist = nullptr; AVDictionary* tmp = nullptr; av_dict_copy(&tmp, nullptr, 0); if (av_opt_set_dict2(s, &tmp, AV_OPT_SEARCH_CHILDREN) < 0) { av_dict_free(&tmp); avformat_free_context(s); return devices; } avdevice_list_devices(s, &devlist); av_dict_free(&tmp); avformat_free_context(s); if (!devlist) { qWarning() << "avdevice_list_devices failed"; return devices; } // Convert the list to a QVector devices.resize(devlist->nb_devices); for (int i = 0; i < devlist->nb_devices; ++i) { AVDeviceInfo* dev = devlist->devices[i]; devices[i].first = dev->device_name; devices[i].second = dev->device_description; } avdevice_free_list_devices(&devlist); return devices; } /** * @brief Get device list with desciption * @return A list of device names and descriptions. * The names are the first part of the pair and can be passed to open(QString). */ QVector> CameraDevice::getDeviceList() { QVector> devices; devices.append({"none", QObject::tr("None", "No camera device set")}); if (!getDefaultInputFormat()) return devices; if (!iformat) ; #ifdef Q_OS_WIN else if (iformat->name == QString("dshow")) devices += DirectShow::getDeviceList(); #endif #if USING_V4L else if (iformat->name == QString("video4linux2,v4l2")) devices += v4l2::getDeviceList(); #endif #ifdef Q_OS_OSX else if (iformat->name == QString("avfoundation")) devices += avfoundation::getDeviceList(); #endif else devices += getRawDeviceListGeneric(); if (idesktopFormat) { if (idesktopFormat->name == QString("x11grab")) { QString dev = "x11grab#"; QByteArray display = qgetenv("DISPLAY"); if (display.isNull()) dev += ":0"; else dev += display.constData(); devices.push_back(QPair{ dev, QObject::tr("Desktop", "Desktop as a camera input for screen sharing")}); } if (idesktopFormat->name == QString("gdigrab")) devices.push_back(QPair{ "gdigrab#desktop", QObject::tr("Desktop", "Desktop as a camera input for screen sharing")}); } return devices; } /** * @brief Get the default device name. * @return The short name of the default device * This is either the device in the settings or the system default. */ QString CameraDevice::getDefaultDeviceName() { QString defaultdev = Settings::getInstance().getVideoDev(); if (!getDefaultInputFormat()) return defaultdev; QVector> devlist = getDeviceList(); for (const QPair& device : devlist) if (defaultdev == device.first) return defaultdev; if (devlist.isEmpty()) return defaultdev; return devlist[0].first; } /** * @brief Checks if a device name specifies a display. * @param devName Device name to check. * @return True, if device is screen, false otherwise. */ bool CameraDevice::isScreen(const QString& devName) { return devName.startsWith("x11grab") || devName.startsWith("gdigrab"); } /** * @brief Get list of resolutions and position of screens * @return Vector of avaliable screen modes with offset */ QVector CameraDevice::getScreenModes() { QList screens = QApplication::screens(); QVector result; std::for_each(screens.begin(), screens.end(), [&result](QScreen* s) { QRect rect = s->geometry(); QPoint p = rect.topLeft(); qreal pixRatio = s->devicePixelRatio(); VideoMode mode(rect.width() * pixRatio, rect.height() * pixRatio, p.x() * pixRatio, p.y() * pixRatio); result.push_back(mode); }); return result; } /** * @brief Get the list of video modes for a device. * @param devName Device name to get nodes from. * @return Vector of available modes for the device. */ QVector CameraDevice::getVideoModes(QString devName) { Q_UNUSED(devName); if (!iformat) ; else if (isScreen(devName)) return getScreenModes(); #ifdef Q_OS_WIN else if (iformat->name == QString("dshow")) return DirectShow::getDeviceModes(devName); #endif #if USING_V4L else if (iformat->name == QString("video4linux2,v4l2")) return v4l2::getDeviceModes(devName); #endif #ifdef Q_OS_OSX else if (iformat->name == QString("avfoundation")) return avfoundation::getDeviceModes(devName); #endif else qWarning() << "Video mode listing not implemented for input " << iformat->name; return {}; } /** * @brief Get the name of the pixel format of a video mode. * @param pixel_format Pixel format to get the name from. * @return Name of the pixel format. */ QString CameraDevice::getPixelFormatString(uint32_t pixel_format) { #if USING_V4L return v4l2::getPixelFormatString(pixel_format); #else return QString("unknown"); #endif } /** * @brief Compare two pixel formats. * @param a First pixel format to compare. * @param b Second pixel format to compare. * @return True if we prefer format a to b, * false otherwise (such as if there's no preference). */ bool CameraDevice::betterPixelFormat(uint32_t a, uint32_t b) { #if USING_V4L return v4l2::betterPixelFormat(a, b); #else return false; #endif } /** * @brief Sets CameraDevice::iformat to default. * @return True if success, false if failure. */ bool CameraDevice::getDefaultInputFormat() { QMutexLocker locker(&iformatLock); if (iformat) return true; avdevice_register_all(); // Desktop capture input formats #if USING_V4L idesktopFormat = av_find_input_format("x11grab"); #endif #ifdef Q_OS_WIN idesktopFormat = av_find_input_format("gdigrab"); #endif // Webcam input formats #if USING_V4L if ((iformat = av_find_input_format("v4l2"))) return true; #endif #ifdef Q_OS_WIN if ((iformat = av_find_input_format("dshow"))) return true; if ((iformat = av_find_input_format("vfwcap"))) return true; #endif #ifdef Q_OS_OSX if ((iformat = av_find_input_format("avfoundation"))) return true; if ((iformat = av_find_input_format("qtkit"))) return true; #endif qWarning() << "No valid input format found"; return false; } qTox/src/video/cameradevice.h000066400000000000000000000040741415623743500165060ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CAMERADEVICE_H #define CAMERADEVICE_H #include "videomode.h" #include #include #include #include #include struct AVFormatContext; struct AVInputFormat; struct AVDeviceInfoList; struct AVDictionary; class CameraDevice { public: static CameraDevice* open(QString devName, VideoMode mode = VideoMode()); void open(); bool close(); static QVector> getDeviceList(); static QVector getVideoModes(QString devName); static QString getPixelFormatString(uint32_t pixel_format); static bool betterPixelFormat(uint32_t a, uint32_t b); static QString getDefaultDeviceName(); static bool isScreen(const QString& devName); private: CameraDevice(const QString& devName, AVFormatContext* context); static CameraDevice* open(QString devName, AVDictionary** options); static bool getDefaultInputFormat(); static QVector> getRawDeviceListGeneric(); static QVector getScreenModes(); public: const QString devName; AVFormatContext* context; private: std::atomic_int refcount; static QHash openDevices; static QMutex openDeviceLock, iformatLock; static AVInputFormat *iformat, *idesktopFormat; }; #endif // CAMERADEVICE_H qTox/src/video/camerasource.cpp000066400000000000000000000304021415623743500170740ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ extern "C" { #include #include #include #include } #include "cameradevice.h" #include "camerasource.h" #include "videoframe.h" #include "src/persistence/settings.h" #include #include #include #include #include #include /** * @class CameraSource * @brief This class is a wrapper to share a camera's captured video frames * * It allows objects to suscribe and unsuscribe to the stream, starting * the camera and streaming new video frames only when needed. * This is a singleton, since we can only capture from one * camera at the same time without thread-safety issues. * The source is lazy in the sense that it will only keep the video * device open as long as there are subscribers, the source can be * open but the device closed if there are zero subscribers. */ /** * @var QVector> CameraSource::freelist * @brief Frames that need freeing before we can safely close the device * * @var QFuture CameraSource::streamFuture * @brief Future of the streaming thread * * @var QString CameraSource::deviceName * @brief Short name of the device for CameraDevice's open(QString) * * @var CameraDevice* CameraSource::device * @brief Non-owning pointer to an open CameraDevice, or nullptr. Not atomic, synced with memfences * when becomes null. * * @var VideoMode CameraSource::mode * @brief What mode we tried to open the device in, all zeros means default mode * * @var AVCodecContext* CameraSource::cctx * @brief Codec context of the camera's selected video stream * * @var AVCodecContext* CameraSource::cctxOrig * @brief Codec context of the camera's selected video stream * @deprecated * * @var int CameraSource::videoStreamIndex * @brief A camera can have multiple streams, this is the one we're decoding * * @var QMutex CameraSource::biglock * @brief True when locked. Faster than mutexes for video decoding. * * @var QMutex CameraSource::freelistLock * @brief True when locked. Faster than mutexes for video decoding. * * @var std::atomic_bool CameraSource::streamBlocker * @brief Holds the streaming thread still when true * * @var std::atomic_int CameraSource::subscriptions * @brief Remember how many times we subscribed for RAII */ CameraSource* CameraSource::instance{nullptr}; CameraSource::CameraSource() : deviceThread{new QThread} , deviceName{"none"} , device{nullptr} , mode(VideoMode()) // clang-format off , cctx{nullptr} #if LIBAVCODEC_VERSION_INT < 3747941 , cctxOrig{nullptr} #endif , videoStreamIndex{-1} , _isNone{true} , subscriptions{0} { qRegisterMetaType("VideoMode"); deviceThread->setObjectName("Device thread"); deviceThread->start(); moveToThread(deviceThread); subscriptions = 0; // TODO(sudden6): remove code when minimum ffmpeg version >= 4.0 #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) av_register_all(); #endif avdevice_register_all(); } // clang-format on /** * @brief Returns the singleton instance. */ CameraSource& CameraSource::getInstance() { if (!instance) instance = new CameraSource(); return *instance; } void CameraSource::destroyInstance() { if (instance) { delete instance; instance = nullptr; } } /** * @brief Setup default device * @note If a device is already open, the source will seamlessly switch to the new device. */ void CameraSource::setupDefault() { QString deviceName = CameraDevice::getDefaultDeviceName(); bool isScreen = CameraDevice::isScreen(deviceName); VideoMode mode = VideoMode(Settings::getInstance().getScreenRegion()); if (!isScreen) { mode = VideoMode(Settings::getInstance().getCamVideoRes()); mode.FPS = Settings::getInstance().getCamVideoFPS(); } setupDevice(deviceName, mode); } /** * @brief Change the device and mode. * @note If a device is already open, the source will seamlessly switch to the new device. */ void CameraSource::setupDevice(const QString& DeviceName, const VideoMode& Mode) { if (QThread::currentThread() != deviceThread) { QMetaObject::invokeMethod(this, "setupDevice", Q_ARG(const QString&, DeviceName), Q_ARG(const VideoMode&, Mode)); return; } QWriteLocker locker{&deviceMutex}; if (DeviceName == deviceName && Mode == mode) { return; } if (subscriptions) { // To force close, ignoring optimization int subs = subscriptions; subscriptions = 0; closeDevice(); subscriptions = subs; } deviceName = DeviceName; mode = Mode; _isNone = (deviceName == "none"); if (subscriptions && !_isNone) { openDevice(); } } bool CameraSource::isNone() const { return _isNone; } CameraSource::~CameraSource() { QWriteLocker locker{&streamMutex}; QWriteLocker locker2{&deviceMutex}; // Stop the device thread deviceThread->exit(0); deviceThread->wait(); delete deviceThread; if (_isNone) { return; } // Free all remaining VideoFrame VideoFrame::untrackFrames(id, true); if (cctx) { avcodec_free_context(&cctx); } #if LIBAVCODEC_VERSION_INT < 3747941 if (cctxOrig) { avcodec_close(cctxOrig); } #endif if (device) { for (int i = 0; i < subscriptions; ++i) device->close(); device = nullptr; } locker.unlock(); // Synchronize with our stream thread while (streamFuture.isRunning()) QThread::yieldCurrentThread(); } void CameraSource::subscribe() { QWriteLocker locker{&deviceMutex}; ++subscriptions; openDevice(); } void CameraSource::unsubscribe() { QWriteLocker locker{&deviceMutex}; --subscriptions; if (subscriptions == 0) { closeDevice(); } } /** * @brief Opens the video device and starts streaming. * @note Callers must own the biglock. */ void CameraSource::openDevice() { if (QThread::currentThread() != deviceThread) { QMetaObject::invokeMethod(this, "openDevice"); return; } QWriteLocker locker{&streamMutex}; if (subscriptions == 0) { return; } qDebug() << "Opening device" << deviceName << "subscriptions:" << subscriptions; if (device) { device->open(); emit openFailed(); return; } // We need to create a new CameraDevice AVCodec* codec; device = CameraDevice::open(deviceName, mode); if (!device) { qWarning() << "Failed to open device!"; emit openFailed(); return; } // We need to open the device as many time as we already have subscribers, // otherwise the device could get closed while we still have subscribers for (int i = 0; i < subscriptions; ++i) { device->open(); } // Find the first video stream, if any for (unsigned i = 0; i < device->context->nb_streams; ++i) { AVMediaType type; #if LIBAVCODEC_VERSION_INT < 3747941 type = device->context->streams[i]->codec->codec_type; #else type = device->context->streams[i]->codecpar->codec_type; #endif if (type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) { qWarning() << "Video stream not found"; emit openFailed(); return; } AVCodecID codecId; #if LIBAVCODEC_VERSION_INT < 3747941 cctxOrig = device->context->streams[videoStreamIndex]->codec; codecId = cctxOrig->codec_id; #else // Get the stream's codec's parameters and find a matching decoder AVCodecParameters* cparams = device->context->streams[videoStreamIndex]->codecpar; codecId = cparams->codec_id; #endif codec = avcodec_find_decoder(codecId); if (!codec) { qWarning() << "Codec not found"; emit openFailed(); return; } #if LIBAVCODEC_VERSION_INT < 3747941 // Copy context, since we apparently aren't allowed to use the original cctx = avcodec_alloc_context3(codec); if (avcodec_copy_context(cctx, cctxOrig) != 0) { qWarning() << "Can't copy context"; emit openFailed(); return; } cctx->refcounted_frames = 1; #else // Create a context for our codec, using the existing parameters cctx = avcodec_alloc_context3(codec); if (avcodec_parameters_to_context(cctx, cparams) < 0) { qWarning() << "Can't create AV context from parameters"; emit openFailed(); return; } #endif // Open codec if (avcodec_open2(cctx, codec, nullptr) < 0) { qWarning() << "Can't open codec"; avcodec_free_context(&cctx); emit openFailed(); return; } if (streamFuture.isRunning()) qDebug() << "The stream thread is already running! Keeping the current one open."; else streamFuture = QtConcurrent::run(std::bind(&CameraSource::stream, this)); // Synchronize with our stream thread while (!streamFuture.isRunning()) QThread::yieldCurrentThread(); emit deviceOpened(); } /** * @brief Closes the video device and stops streaming. * @note Callers must own the biglock. */ void CameraSource::closeDevice() { if (QThread::currentThread() != deviceThread) { QMetaObject::invokeMethod(this, "closeDevice"); return; } QWriteLocker locker{&streamMutex}; if (subscriptions != 0) { return; } qDebug() << "Closing device" << deviceName << "subscriptions:" << subscriptions; // Free all remaining VideoFrame VideoFrame::untrackFrames(id, true); // Free our resources and close the device videoStreamIndex = -1; avcodec_free_context(&cctx); #if LIBAVCODEC_VERSION_INT < 3747941 avcodec_close(cctxOrig); cctxOrig = nullptr; #endif while (device && !device->close()) { } device = nullptr; } /** * @brief Blocking. Decodes video stream and emits new frames. * @note Designed to run in its own thread. */ void CameraSource::stream() { auto streamLoop = [=]() { AVPacket packet; if (av_read_frame(device->context, &packet) != 0) { return; } #if LIBAVCODEC_VERSION_INT < 3747941 AVFrame* frame = av_frame_alloc(); if (!frame) { return; } // Only keep packets from the right stream; if (packet.stream_index == videoStreamIndex) { // Decode video frame int frameFinished; avcodec_decode_video2(cctx, frame, &frameFinished, &packet); if (!frameFinished) { return; } VideoFrame* vframe = new VideoFrame(id, frame); emit frameAvailable(vframe->trackFrame()); } #else // Forward packets to the decoder and grab the decoded frame bool isVideo = packet.stream_index == videoStreamIndex; bool readyToRecive = isVideo && !avcodec_send_packet(cctx, &packet); if (readyToRecive) { AVFrame* frame = av_frame_alloc(); if (frame && !avcodec_receive_frame(cctx, frame)) { VideoFrame* vframe = new VideoFrame(id, frame); emit frameAvailable(vframe->trackFrame()); } else { av_frame_free(&frame); } } #endif av_packet_unref(&packet); }; forever { QReadLocker locker{&streamMutex}; // Exit if device is no longer valid if (!device) { break; } streamLoop(); } } qTox/src/video/camerasource.h000066400000000000000000000040541415623743500165450ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CAMERA_H #define CAMERA_H #include "src/video/videomode.h" #include "src/video/videosource.h" #include #include #include #include #include #include class CameraDevice; struct AVCodecContext; class CameraSource : public VideoSource { Q_OBJECT public: static CameraSource& getInstance(); static void destroyInstance(); void setupDefault(); bool isNone() const; // VideoSource interface virtual void subscribe() override; virtual void unsubscribe() override; public slots: void setupDevice(const QString& deviceName, const VideoMode& mode); signals: void deviceOpened(); void openFailed(); private: CameraSource(); ~CameraSource(); void stream(); private slots: void openDevice(); void closeDevice(); private: QFuture streamFuture; QThread* deviceThread; QString deviceName; CameraDevice* device; VideoMode mode; AVCodecContext* cctx; // TODO: Remove when ffmpeg version will be bumped to the 3.1.0 AVCodecContext* cctxOrig; int videoStreamIndex; QReadWriteLock deviceMutex; QReadWriteLock streamMutex; std::atomic_bool _isNone; std::atomic_int subscriptions; static CameraSource* instance; }; #endif // CAMERA_H qTox/src/video/corevideosource.cpp000066400000000000000000000074211415623743500176300ustar00rootroot00000000000000/* Copyright © 2015-2019 The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ extern "C" { #include #include } #include "corevideosource.h" #include "videoframe.h" /** * @class CoreVideoSource * @brief A VideoSource that emits frames received by Core. */ /** * @var std::atomic_int subscribers * @brief Number of suscribers * * @var std::atomic_bool deleteOnClose * @brief If true, self-delete after the last suscriber is gone */ /** * @brief CoreVideoSource constructor. * @note Only CoreAV should create a CoreVideoSource since * only CoreAV can push images to it. */ CoreVideoSource::CoreVideoSource() : subscribers{0} , deleteOnClose{false} , stopped{false} { } /** * @brief Makes a copy of the vpx_image_t and emits it as a new VideoFrame. * @param vpxframe Frame to copy. */ void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe) { if (stopped) return; QMutexLocker locker(&biglock); std::shared_ptr vframe; int width = vpxframe->d_w; int height = vpxframe->d_h; if (subscribers <= 0) return; AVFrame* avframe = av_frame_alloc(); if (!avframe) return; avframe->width = width; avframe->height = height; avframe->format = AV_PIX_FMT_YUV420P; int bufSize = av_image_alloc(avframe->data, avframe->linesize, width, height, static_cast(AV_PIX_FMT_YUV420P), VideoFrame::dataAlignment); if (bufSize < 0) { av_frame_free(&avframe); return; } for (int i = 0; i < 3; ++i) { int dstStride = avframe->linesize[i]; int srcStride = vpxframe->stride[i]; int minStride = std::min(dstStride, srcStride); int size = (i == 0) ? height : height / 2; for (int j = 0; j < size; ++j) { uint8_t* dst = avframe->data[i] + dstStride * j; uint8_t* src = vpxframe->planes[i] + srcStride * j; memcpy(dst, src, minStride); } } vframe = std::make_shared(id, avframe, true); emit frameAvailable(vframe); } void CoreVideoSource::subscribe() { QMutexLocker locker(&biglock); ++subscribers; } void CoreVideoSource::unsubscribe() { biglock.lock(); if (--subscribers == 0) { if (deleteOnClose) { biglock.unlock(); // DANGEROUS: No member access after this point, that's why we manually unlock delete this; return; } } biglock.unlock(); } /** * @brief Setup delete on close * @param If true, self-delete after the last suscriber is gone */ void CoreVideoSource::setDeleteOnClose(bool newstate) { QMutexLocker locker(&biglock); deleteOnClose = newstate; } /** * @brief Stopping the source. * @see The callers in CoreAV for the rationale * * Stopping the source will block any pushFrame calls from doing anything */ void CoreVideoSource::stopSource() { QMutexLocker locker(&biglock); stopped = true; emit sourceStopped(); } void CoreVideoSource::restartSource() { QMutexLocker locker(&biglock); stopped = false; } qTox/src/video/corevideosource.h000066400000000000000000000027061415623743500172760ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef COREVIDEOSOURCE_H #define COREVIDEOSOURCE_H #include "videosource.h" #include #include #include class CoreVideoSource : public VideoSource { Q_OBJECT public: // VideoSource interface virtual void subscribe() override; virtual void unsubscribe() override; private: CoreVideoSource(); void pushFrame(const vpx_image_t* frame); void setDeleteOnClose(bool newstate); void stopSource(); void restartSource(); private: std::atomic_int subscribers; std::atomic_bool deleteOnClose; QMutex biglock; std::atomic_bool stopped; friend class CoreAV; friend class ToxFriendCall; }; #endif // COREVIDEOSOURCE_H qTox/src/video/genericnetcamview.cpp000066400000000000000000000157571415623743500201420ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "genericnetcamview.h" #include #include #include #include #include #include #include namespace { const auto BTN_STATE_NONE = QVariant("none"); const auto BTN_STATE_RED = QVariant("red"); const int BTN_PANEL_HEIGHT = 55; const int BTN_PANEL_WIDTH = 250; const auto BTN_STYLE_SHEET_PATH = QStringLiteral("chatForm/fullScreenButtons.css"); } GenericNetCamView::GenericNetCamView(QWidget* parent) : QWidget(parent) { verLayout = new QVBoxLayout(this); setWindowTitle(tr("Tox video")); buttonLayout = new QHBoxLayout(); toggleMessagesButton = new QPushButton(); enterFullScreenButton = new QPushButton(); enterFullScreenButton->setText(tr("Full Screen")); buttonLayout->addStretch(); buttonLayout->addWidget(toggleMessagesButton); buttonLayout->addWidget(enterFullScreenButton); connect(toggleMessagesButton, &QPushButton::clicked, this, &GenericNetCamView::showMessageClicked); connect(enterFullScreenButton, &QPushButton::clicked, this, &GenericNetCamView::toggleFullScreen); verLayout->addLayout(buttonLayout); verLayout->setContentsMargins(0, 0, 0, 0); setShowMessages(false); setStyleSheet("NetCamView { background-color: #c1c1c1; }"); buttonPanel = new QFrame(this); buttonPanel->setStyleSheet(Style::getStylesheet(BTN_STYLE_SHEET_PATH)); buttonPanel->setGeometry(0, 0, BTN_PANEL_WIDTH, BTN_PANEL_HEIGHT); QHBoxLayout* buttonPanelLayout = new QHBoxLayout(buttonPanel); buttonPanelLayout->setContentsMargins(20, 0, 20, 0); videoPreviewButton = createButton("videoPreviewButton", "none"); videoPreviewButton->setToolTip(tr("Toggle video preview")); volumeButton = createButton("volButtonFullScreen", "none"); volumeButton->setToolTip(tr("Mute audio")); microphoneButton = createButton("micButtonFullScreen", "none"); microphoneButton->setToolTip(tr("Mute microphone")); endVideoButton = createButton("videoButtonFullScreen", "none"); endVideoButton->setToolTip(tr("End video call")); exitFullScreenButton = createButton("exitFullScreenButton", "none"); exitFullScreenButton->setToolTip(tr("Exit full screen")); connect(videoPreviewButton, &QPushButton::clicked, this, &GenericNetCamView::toggleVideoPreview); connect(volumeButton, &QPushButton::clicked, this, &GenericNetCamView::volMuteToggle); connect(microphoneButton, &QPushButton::clicked, this, &GenericNetCamView::micMuteToggle); connect(endVideoButton, &QPushButton::clicked, this, &GenericNetCamView::endVideoCall); connect(exitFullScreenButton, &QPushButton::clicked, this, &GenericNetCamView::toggleFullScreen); buttonPanelLayout->addStretch(); buttonPanelLayout->addWidget(videoPreviewButton); buttonPanelLayout->addWidget(volumeButton); buttonPanelLayout->addWidget(microphoneButton); buttonPanelLayout->addWidget(endVideoButton); buttonPanelLayout->addWidget(exitFullScreenButton); buttonPanelLayout->addStretch(); } QSize GenericNetCamView::getSurfaceMinSize() { QSize surfaceSize = videoSurface->minimumSize(); QSize buttonSize = toggleMessagesButton->size(); QSize panelSize(0, 45); return surfaceSize + buttonSize + panelSize; } void GenericNetCamView::setShowMessages(bool show, bool notify) { if (!show) { toggleMessagesButton->setText(tr("Hide Messages")); toggleMessagesButton->setIcon(QIcon()); return; } toggleMessagesButton->setText(tr("Show Messages")); if (notify) { toggleMessagesButton->setIcon(QIcon(Style::getImagePath("chatArea/info.svg"))); } } void GenericNetCamView::toggleFullScreen() { if (isFullScreen()) { exitFullScreen(); } else { enterFullScreen(); } } void GenericNetCamView::enterFullScreen() { setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::FramelessWindowHint); showFullScreen(); enterFullScreenButton->hide(); toggleMessagesButton->hide(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) const auto screenSize = QGuiApplication::screenAt(this->pos())->geometry(); #else const QRect screenSize = QApplication::desktop()->screenGeometry(this); #endif buttonPanel->setGeometry((screenSize.width() / 2) - buttonPanel->width() / 2, screenSize.height() - BTN_PANEL_HEIGHT - 25, BTN_PANEL_WIDTH, BTN_PANEL_HEIGHT); buttonPanel->show(); buttonPanel->activateWindow(); buttonPanel->raise(); } void GenericNetCamView::exitFullScreen() { setWindowFlags(Qt::Widget); showNormal(); buttonPanel->hide(); enterFullScreenButton->show(); toggleMessagesButton->show(); } void GenericNetCamView::endVideoCall() { toggleFullScreen(); emit videoCallEnd(); } void GenericNetCamView::toggleVideoPreview() { toggleButtonState(videoPreviewButton); emit videoPreviewToggle(); } QPushButton *GenericNetCamView::createButton(const QString& name, const QString& state) { QPushButton* btn = new QPushButton(); btn->setAttribute(Qt::WA_LayoutUsesWidgetRect); btn->setObjectName(name); btn->setProperty("state", QVariant(state)); btn->setStyleSheet(Style::getStylesheet(BTN_STYLE_SHEET_PATH)); return btn; } void GenericNetCamView::updateMuteVolButton(bool isMuted) { updateButtonState(volumeButton, !isMuted); } void GenericNetCamView::updateMuteMicButton(bool isMuted) { updateButtonState(microphoneButton, !isMuted); } void GenericNetCamView::toggleButtonState(QPushButton* btn) { if (btn->property("state") == BTN_STATE_RED) { btn->setProperty("state", BTN_STATE_NONE); } else { btn->setProperty("state", BTN_STATE_RED); } btn->setStyleSheet(Style::getStylesheet(BTN_STYLE_SHEET_PATH)); } void GenericNetCamView::updateButtonState(QPushButton* btn, bool active) { if (active) { btn->setProperty("state", BTN_STATE_NONE); } else { btn->setProperty("state", BTN_STATE_RED); } btn->setStyleSheet(Style::getStylesheet(BTN_STYLE_SHEET_PATH)); } void GenericNetCamView::keyPressEvent(QKeyEvent *event) { int key = event->key(); if (key == Qt::Key_Escape && isFullScreen()) { exitFullScreen(); } } void GenericNetCamView::closeEvent(QCloseEvent *event) { exitFullScreen(); event->ignore(); } qTox/src/video/genericnetcamview.h000066400000000000000000000045111415623743500175710ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GENERICNETCAMVIEW_H #define GENERICNETCAMVIEW_H #include #include #include #include #include "src/video/videosurface.h" #include "src/widget/style.h" class GenericNetCamView : public QWidget { Q_OBJECT public: explicit GenericNetCamView(QWidget* parent); QSize getSurfaceMinSize(); signals: void showMessageClicked(); void videoCallEnd(); void volMuteToggle(); void micMuteToggle(); void videoPreviewToggle(); public slots: void setShowMessages(bool show, bool notify = false); void updateMuteVolButton(bool isMuted); void updateMuteMicButton(bool isMuted); protected: QVBoxLayout* verLayout; VideoSurface* videoSurface; QPushButton* enterFullScreenButton = nullptr; private: QHBoxLayout* buttonLayout = nullptr; QPushButton* toggleMessagesButton = nullptr; QFrame* buttonPanel = nullptr; QPushButton* videoPreviewButton = nullptr; QPushButton* volumeButton = nullptr; QPushButton* microphoneButton = nullptr; QPushButton* endVideoButton = nullptr; QPushButton* exitFullScreenButton = nullptr; private: QPushButton* createButton(const QString& name, const QString& state); void toggleFullScreen(); void enterFullScreen(); void exitFullScreen(); void endVideoCall(); void toggleVideoPreview(); void toggleButtonState(QPushButton* btn); void updateButtonState(QPushButton* btn, bool active); void keyPressEvent(QKeyEvent *event) override; void closeEvent(QCloseEvent *event) override; }; #endif // GENERICNETCAMVIEW_H qTox/src/video/groupnetcamview.cpp000066400000000000000000000170201415623743500176430ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupnetcamview.h" #include "src/audio/audio.h" #include "src/core/core.h" #include "src/core/toxpk.h" #include "src/friendlist.h" #include "src/model/friend.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/video/videosurface.h" #include "src/widget/tool/croppinglabel.h" #include #include #include #include #include #include class LabeledVideo : public QFrame { public: LabeledVideo(const QPixmap& avatar, QString fontColorString, QWidget* parent = nullptr, bool expanding = true) : QFrame(parent) { qDebug() << "Created expanding? " << expanding; videoSurface = new VideoSurface(avatar, nullptr, expanding); videoSurface->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); videoSurface->setMinimumHeight(32); connect(videoSurface, &VideoSurface::ratioChanged, this, &LabeledVideo::updateSize); label = new CroppingLabel(this); label->setTextFormat(Qt::PlainText); label->setStyleSheet(QString("color: %1").arg(fontColorString)); label->setAlignment(Qt::AlignCenter); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(videoSurface, 1); layout->addWidget(label); } ~LabeledVideo() {} VideoSurface* getVideoSurface() const { return videoSurface; } void setText(const QString& text) { label->setText(text); } QString getText() const { return label->text(); } void setActive(bool active = true) { if (active) setStyleSheet("QFrame { background-color: #414141; border-radius: 10px; }"); else setStyleSheet(QString()); } protected: void resizeEvent(QResizeEvent* event) final override { updateSize(); QWidget::resizeEvent(event); } private slots: void updateSize() { if (videoSurface->isExpanding()) { int width = videoSurface->height() * videoSurface->getRatio(); videoSurface->setMinimumWidth(width); videoSurface->setMaximumWidth(width); } } private: CroppingLabel* label; VideoSurface* videoSurface; }; GroupNetCamView::GroupNetCamView(int group, QWidget* parent) : GenericNetCamView(parent) , group(group) { videoLabelSurface = new LabeledVideo(QPixmap(), "white", this, false); videoSurface = videoLabelSurface->getVideoSurface(); videoSurface->setMinimumHeight(256); videoSurface->setContentsMargins(6, 6, 6, 0); videoLabelSurface->setContentsMargins(0, 0, 0, 0); videoLabelSurface->layout()->setMargin(0); videoLabelSurface->setStyleSheet("QFrame { background-color: black; }"); // remove full screen button in audio group chat since it's useless there enterFullScreenButton->hide(); QSplitter* splitter = new QSplitter(Qt::Vertical, this); splitter->setChildrenCollapsible(false); verLayout->insertWidget(0, splitter, 1); splitter->addWidget(videoLabelSurface); splitter->setStyleSheet( "QSplitter { background-color: black; } QSplitter::handle { background-color: black; }"); QScrollArea* scrollArea = new QScrollArea(); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Note this is needed to prevent oscillations that result in segfaults scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scrollArea->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum)); scrollArea->setFrameStyle(QFrame::NoFrame); QWidget* widget = new QWidget(nullptr); scrollArea->setWidgetResizable(true); horLayout = new QHBoxLayout(widget); horLayout->addStretch(1); selfVideoSurface = new LabeledVideo(Nexus::getProfile()->loadAvatar(), "black", this); horLayout->addWidget(selfVideoSurface); horLayout->addStretch(1); splitter->addWidget(scrollArea); scrollArea->setWidget(widget); QTimer* timer = new QTimer(this); timer->setInterval(1000); connect(timer, &QTimer::timeout, this, &GroupNetCamView::onUpdateActivePeer); timer->start(); connect(Nexus::getProfile(), &Profile::selfAvatarChanged, [this](const QPixmap& pixmap) { selfVideoSurface->getVideoSurface()->setAvatar(pixmap); setActive(); }); connect(Core::getInstance(), &Core::usernameSet, [this](const QString& username) { selfVideoSurface->setText(username); setActive(); }); connect(Nexus::getProfile(), &Profile::friendAvatarChanged, this, &GroupNetCamView::friendAvatarChanged); selfVideoSurface->setText(Core::getInstance()->getUsername()); } void GroupNetCamView::clearPeers() { for (const auto& peerPk : videoList.keys()) { removePeer(peerPk); } } void GroupNetCamView::addPeer(const ToxPk& peer, const QString& name) { QPixmap groupAvatar = Nexus::getProfile()->loadAvatar(peer); LabeledVideo* labeledVideo = new LabeledVideo(groupAvatar, "black", this); labeledVideo->setText(name); horLayout->insertWidget(horLayout->count() - 1, labeledVideo); PeerVideo peerVideo; peerVideo.video = labeledVideo; videoList.insert(peer, peerVideo); setActive(); } void GroupNetCamView::removePeer(const ToxPk& peer) { auto peerVideo = videoList.find(peer); if (peerVideo != videoList.end()) { LabeledVideo* labeledVideo = peerVideo.value().video; horLayout->removeWidget(labeledVideo); labeledVideo->deleteLater(); videoList.remove(peer); setActive(); } } void GroupNetCamView::onUpdateActivePeer() { setActive(); } void GroupNetCamView::setActive(const ToxPk& peer) { if (peer.isEmpty()) { videoLabelSurface->setText(selfVideoSurface->getText()); activePeer = -1; return; } // TODO(sudden6): check if we can remove the code, it won't be reached right now #if 0 auto peerVideo = videoList.find(peer); if (peerVideo != videoList.end()) { // When group video exists: // videoSurface->setSource(peerVideo.value()->getVideoSurface()->source); auto lastVideo = videoList.find(activePeer); if (lastVideo != videoList.end()) lastVideo.value().video->setActive(false); LabeledVideo* labeledVideo = peerVideo.value().video; videoLabelSurface->setText(labeledVideo->getText()); videoLabelSurface->getVideoSurface()->setAvatar(labeledVideo->getVideoSurface()->getAvatar()); labeledVideo->setActive(); activePeer = peer; } #endif } void GroupNetCamView::friendAvatarChanged(ToxPk friendPk, const QPixmap& pixmap) { auto peerVideo = videoList.find(friendPk); if (peerVideo != videoList.end()) { peerVideo.value().video->getVideoSurface()->setAvatar(pixmap); setActive(); } } qTox/src/video/groupnetcamview.h000066400000000000000000000031311415623743500173060ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUPNETCAMVIEW_H #define GROUPNETCAMVIEW_H #include "genericnetcamview.h" #include "src/core/toxpk.h" #include class LabeledVideo; class QHBoxLayout; class GroupNetCamView : public GenericNetCamView { public: GroupNetCamView(int group, QWidget* parent = nullptr); void clearPeers(); void addPeer(const ToxPk& peer, const QString& name); void removePeer(const ToxPk& peer); private slots: void onUpdateActivePeer(); void friendAvatarChanged(ToxPk friendPk, const QPixmap& pixmap); private: struct PeerVideo { LabeledVideo* video; }; void setActive(const ToxPk& peer = ToxPk{}); QHBoxLayout* horLayout; QMap videoList; LabeledVideo* videoLabelSurface; LabeledVideo* selfVideoSurface; int activePeer; int group; }; #endif // GROUPNETCAMVIEW_H qTox/src/video/ivideosettings.h000066400000000000000000000034241415623743500171340ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef I_VIDEO_SETTINGS_H #define I_VIDEO_SETTINGS_H #include "src/model/interface.h" #include #include class IVideoSettings { public: virtual ~IVideoSettings() = default; virtual QString getVideoDev() const = 0; virtual void setVideoDev(const QString& deviceSpecifier) = 0; virtual QRect getScreenRegion() const = 0; virtual void setScreenRegion(const QRect& value) = 0; virtual bool getScreenGrabbed() const = 0; virtual void setScreenGrabbed(bool value) = 0; virtual QRect getCamVideoRes() const = 0; virtual void setCamVideoRes(QRect newValue) = 0; virtual float getCamVideoFPS() const = 0; virtual void setCamVideoFPS(float newValue) = 0; DECLARE_SIGNAL(videoDevChanged, const QString& device); DECLARE_SIGNAL(screenRegionChanged, const QRect& region); DECLARE_SIGNAL(screenGrabbedChanged, bool enabled); DECLARE_SIGNAL(camVideoResChanged, const QRect& region); DECLARE_SIGNAL(camVideoFPSChanged, unsigned short fps); }; #endif // I_VIDEO_SETTINGS_H qTox/src/video/netcamview.cpp000066400000000000000000000113131415623743500165650ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "netcamview.h" #include "camerasource.h" #include "src/core/core.h" #include "src/friendlist.h" #include "src/model/friend.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/video/videosurface.h" #include "src/widget/tool/movablewidget.h" #include #include #include NetCamView::NetCamView(ToxPk friendPk, QWidget* parent) : GenericNetCamView(parent) , selfFrame{nullptr} , friendPk{friendPk} , e(false) { videoSurface = new VideoSurface(Nexus::getProfile()->loadAvatar(friendPk), this); videoSurface->setMinimumHeight(256); verLayout->insertWidget(0, videoSurface, 1); selfVideoSurface = new VideoSurface(Nexus::getProfile()->loadAvatar(), this, true); selfVideoSurface->setObjectName(QStringLiteral("CamVideoSurface")); selfVideoSurface->setMouseTracking(true); selfVideoSurface->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); selfFrame = new MovableWidget(videoSurface); selfFrame->show(); QHBoxLayout* frameLayout = new QHBoxLayout(selfFrame); frameLayout->addWidget(selfVideoSurface); frameLayout->setMargin(0); updateRatio(); connections += connect(selfVideoSurface, &VideoSurface::ratioChanged, this, &NetCamView::updateRatio); connections += connect(videoSurface, &VideoSurface::boundaryChanged, [this]() { QRect boundingRect = videoSurface->getBoundingRect(); updateFrameSize(boundingRect.size()); selfFrame->setBoundary(boundingRect); }); connections += connect(videoSurface, &VideoSurface::ratioChanged, [this]() { selfFrame->setMinimumWidth(selfFrame->minimumHeight() * selfVideoSurface->getRatio()); QRect boundingRect = videoSurface->getBoundingRect(); updateFrameSize(boundingRect.size()); selfFrame->resetBoundary(boundingRect); }); connections += connect(Nexus::getProfile(), &Profile::selfAvatarChanged, [this](const QPixmap& pixmap) { selfVideoSurface->setAvatar(pixmap); }); connections += connect(Nexus::getProfile(), &Profile::friendAvatarChanged, [this](ToxPk friendPk, const QPixmap& pixmap) { if (this->friendPk == friendPk) videoSurface->setAvatar(pixmap); }); QRect videoSize = Settings::getInstance().getCamVideoRes(); qDebug() << "SIZER" << videoSize; } NetCamView::~NetCamView() { for (QMetaObject::Connection conn : connections) disconnect(conn); } void NetCamView::show(VideoSource* source, const QString& title) { setSource(source); selfVideoSurface->setSource(&CameraSource::getInstance()); setTitle(title); QWidget::show(); } void NetCamView::hide() { setSource(nullptr); selfVideoSurface->setSource(nullptr); if (selfFrame) selfFrame->deleteLater(); selfFrame = nullptr; QWidget::hide(); } void NetCamView::setSource(VideoSource* s) { videoSurface->setSource(s); } void NetCamView::setTitle(const QString& title) { setWindowTitle(title); } void NetCamView::showEvent(QShowEvent* event) { Q_UNUSED(event); selfFrame->resetBoundary(videoSurface->getBoundingRect()); } void NetCamView::updateRatio() { selfFrame->setMinimumWidth(selfFrame->minimumHeight() * selfVideoSurface->getRatio()); selfFrame->setRatio(selfVideoSurface->getRatio()); } void NetCamView::updateFrameSize(QSize size) { selfFrame->setMaximumSize(size.height() / 3, size.width() / 3); if (selfFrame->maximumWidth() > selfFrame->maximumHeight()) selfFrame->setMaximumWidth(selfFrame->maximumHeight() * selfVideoSurface->getRatio()); else selfFrame->setMaximumHeight(selfFrame->maximumWidth() / selfVideoSurface->getRatio()); } void NetCamView::toggleVideoPreview() { if (selfFrame->isHidden()) { selfFrame->show(); } else { selfFrame->hide(); } } qTox/src/video/netcamview.h000066400000000000000000000031741415623743500162400ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef NETCAMVIEW_H #define NETCAMVIEW_H #include "src/core/toxpk.h" #include "genericnetcamview.h" #include class QHBoxLayout; struct vpx_image; class VideoSource; class QFrame; class MovableWidget; class NetCamView : public GenericNetCamView { Q_OBJECT public: NetCamView(ToxPk friendPk, QWidget* parent = nullptr); ~NetCamView(); virtual void show(VideoSource* source, const QString& title); virtual void hide(); void setSource(VideoSource* s); void setTitle(const QString& title); void toggleVideoPreview(); protected: void showEvent(QShowEvent* event) final override; private slots: void updateRatio(); private: void updateFrameSize(QSize size); VideoSurface* selfVideoSurface; MovableWidget* selfFrame; ToxPk friendPk; bool e; QVector connections; }; #endif // NETCAMVIEW_H qTox/src/video/videoframe.cpp000066400000000000000000000635231415623743500165560ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "videoframe.h" extern "C" { #include #include } /** * @struct ToxYUVFrame * @brief A simple structure to represent a ToxYUV video frame (corresponds to a frame encoded * under format: AV_PIX_FMT_YUV420P [FFmpeg] or VPX_IMG_FMT_I420 [WebM]). * * This structure exists for convenience and code clarity when ferrying YUV420 frames from one * source to another. The buffers pointed to by the struct should not be owned by the struct nor * should they be freed from the struct, instead this struct functions only as a simple alias to a * more complicated frame container like AVFrame. * * The creation of this structure was done to replace existing code which mis-used vpx_image * structs when passing frame data to toxcore. * * * @class VideoFrame * @brief An ownernship and management class for AVFrames. * * VideoFrame takes ownership of an AVFrame* and allows fast conversions to other formats. * Ownership of all video frame buffers is kept by the VideoFrame, even after conversion. All * references to the frame data become invalid when the VideoFrame is deleted. We try to avoid * pixel format conversions as much as possible, at the cost of some memory. * * Every function in this class is thread safe apart from concurrent construction and deletion of * the object. * * This class uses the phrase "frame alignment" to specify the property that each frame's width is * equal to it's maximum linesize. Note: this is NOT "data alignment" which specifies how allocated * buffers are aligned in memory. Though internally the two are related, unless otherwise specified * all instances of the term "alignment" exposed from public functions refer to frame alignment. * * Frame alignment is an important concept because ToxAV does not support frames with linesizes not * directly equal to the width. * * * @var VideoFrame::dataAlignment * @brief Data alignment parameter used to populate AVFrame buffers. * * This field is public in effort to standardize the data alignment parameter for all AVFrame * allocations. * * It's currently set to 32-byte alignment for AVX2 support. * * * @class FrameBufferKey * @brief A class representing a structure that stores frame properties to be used as the key * value for a std::unordered_map. */ // Initialize static fields VideoFrame::AtomicIDType VideoFrame::frameIDs{0}; std::unordered_map VideoFrame::mutexMap{}; std::unordered_map>> VideoFrame::refsMap{}; QReadWriteLock VideoFrame::refsLock{}; /** * @brief Constructs a new instance of a VideoFrame, sourced by a given AVFrame pointer. * * @param sourceID the VideoSource's ID to track the frame under. * @param sourceFrame the source AVFrame pointer to use, must be valid. * @param dimensions the dimensions of the AVFrame, obtained from the AVFrame if not given. * @param pixFmt the pixel format of the AVFrame, obtained from the AVFrame if not given. * @param freeSourceFrame whether to free the source frame buffers or not. */ VideoFrame::VideoFrame(IDType sourceID, AVFrame* sourceFrame, QRect dimensions, int pixFmt, bool freeSourceFrame) : frameID(frameIDs++) , sourceID(sourceID) , sourceDimensions(dimensions) , sourceFrameKey(getFrameKey(dimensions.size(), pixFmt, sourceFrame->linesize[0])) , freeSourceFrame(freeSourceFrame) { // We override the pixel format in the case a deprecated one is used switch (pixFmt) { case AV_PIX_FMT_YUVJ420P: { sourcePixelFormat = AV_PIX_FMT_YUV420P; sourceFrame->color_range = AVCOL_RANGE_MPEG; break; } case AV_PIX_FMT_YUVJ411P: { sourcePixelFormat = AV_PIX_FMT_YUV411P; sourceFrame->color_range = AVCOL_RANGE_MPEG; break; } case AV_PIX_FMT_YUVJ422P: { sourcePixelFormat = AV_PIX_FMT_YUV422P; sourceFrame->color_range = AVCOL_RANGE_MPEG; break; } case AV_PIX_FMT_YUVJ444P: { sourcePixelFormat = AV_PIX_FMT_YUV444P; sourceFrame->color_range = AVCOL_RANGE_MPEG; break; } case AV_PIX_FMT_YUVJ440P: { sourcePixelFormat = AV_PIX_FMT_YUV440P; sourceFrame->color_range = AVCOL_RANGE_MPEG; break; } default: { sourcePixelFormat = pixFmt; sourceFrame->color_range = AVCOL_RANGE_UNSPECIFIED; } } frameBuffer[sourceFrameKey] = sourceFrame; } VideoFrame::VideoFrame(IDType sourceID, AVFrame* sourceFrame, bool freeSourceFrame) : VideoFrame(sourceID, sourceFrame, QRect{0, 0, sourceFrame->width, sourceFrame->height}, sourceFrame->format, freeSourceFrame) { } /** * @brief Destructor for VideoFrame. */ VideoFrame::~VideoFrame() { // Release frame frameLock.lockForWrite(); deleteFrameBuffer(); frameLock.unlock(); // Delete tracked reference refsLock.lockForRead(); if (refsMap.count(sourceID) > 0) { QMutex& sourceMutex = mutexMap[sourceID]; sourceMutex.lock(); refsMap[sourceID].erase(frameID); sourceMutex.unlock(); } refsLock.unlock(); } /** * @brief Returns the validity of this VideoFrame. * * A VideoFrame is valid if it manages at least one AVFrame. A VideoFrame can be invalidated * by calling releaseFrame() on it. * * @return true if the VideoFrame is valid, false otherwise. */ bool VideoFrame::isValid() { frameLock.lockForRead(); bool retValue = frameBuffer.size() > 0; frameLock.unlock(); return retValue; } /** * @brief Causes the VideoFrame class to maintain an internal reference for the frame. * * The internal reference is managed via a std::weak_ptr such that it doesn't inhibit * destruction of the object once all external references are no longer reachable. * * @return a std::shared_ptr holding a reference to this frame. */ std::shared_ptr VideoFrame::trackFrame() { // Add frame to tracked reference list refsLock.lockForRead(); if (refsMap.count(sourceID) == 0) { // We need to add a new source to our reference map, obtain write lock refsLock.unlock(); refsLock.lockForWrite(); } QMutex& sourceMutex = mutexMap[sourceID]; sourceMutex.lock(); std::shared_ptr ret{this}; refsMap[sourceID][frameID] = ret; sourceMutex.unlock(); refsLock.unlock(); return ret; } /** * @brief Untracks all the frames for the given VideoSource, releasing them if specified. * * This function causes all internally tracked frames for the given VideoSource to be dropped. * If the releaseFrames option is set to true, the frames are sequentially released on the * caller's thread in an unspecified order. * * @param sourceID the ID of the VideoSource to untrack frames from. * @param releaseFrames true to release the frames as necessary, false otherwise. Defaults to * false. */ void VideoFrame::untrackFrames(const VideoFrame::IDType& sourceID, bool releaseFrames) { refsLock.lockForWrite(); if (refsMap.count(sourceID) == 0) { // No tracking reference exists for source, simply return refsLock.unlock(); return; } if (releaseFrames) { QMutex& sourceMutex = mutexMap[sourceID]; sourceMutex.lock(); for (auto& frameIterator : refsMap[sourceID]) { std::shared_ptr frame = frameIterator.second.lock(); if (frame) { frame->releaseFrame(); } } sourceMutex.unlock(); } refsMap[sourceID].clear(); mutexMap.erase(sourceID); refsMap.erase(sourceID); refsLock.unlock(); } /** * @brief Releases all frames managed by this VideoFrame and invalidates it. */ void VideoFrame::releaseFrame() { frameLock.lockForWrite(); deleteFrameBuffer(); frameLock.unlock(); } /** * @brief Retrieves an AVFrame derived from the source based on the given parameters. * * If a given frame does not exist, this function will perform appropriate conversions to * return a frame that fulfills the given parameters. * * @param frameSize the dimensions of the frame to get. Defaults to source frame size if frameSize * is invalid. * @param pixelFormat the desired pixel format of the frame. * @param requireAligned true if the returned frame must be frame aligned, false if not. * @return a pointer to a AVFrame with the given parameters or nullptr if the VideoFrame is no * longer valid. */ const AVFrame* VideoFrame::getAVFrame(QSize frameSize, const int pixelFormat, const bool requireAligned) { if (!frameSize.isValid()) { frameSize = sourceDimensions.size(); } // Since we are retrieving the AVFrame* directly, we merely need to pass the arguement through const std::function converter = [](AVFrame* const frame) { return frame; }; // We need an explicit null pointer holding object to pass to toGenericObject() AVFrame* nullPointer = nullptr; // Returns std::nullptr case of invalid generation return toGenericObject(frameSize, pixelFormat, requireAligned, converter, nullPointer); } /** * @brief Converts this VideoFrame to a QImage that shares this VideoFrame's buffer. * * The VideoFrame will be scaled into the RGB24 pixel format along with the given * dimension. * * @param frameSize the given frame size of QImage to generate. Defaults to source frame size if * frameSize is invalid. * @return a QImage that represents this VideoFrame, sharing it's buffers or a null image if * this VideoFrame is no longer valid. */ QImage VideoFrame::toQImage(QSize frameSize) { if (!frameSize.isValid()) { frameSize = sourceDimensions.size(); } // Converter function (constructs QImage out of AVFrame*) const std::function converter = [&](AVFrame* const frame) { return QImage{*(frame->data), frameSize.width(), frameSize.height(), *(frame->linesize), QImage::Format_RGB888}; }; // Returns an empty constructed QImage in case of invalid generation return toGenericObject(frameSize, AV_PIX_FMT_RGB24, false, converter, QImage{}); } /** * @brief Converts this VideoFrame to a ToxAVFrame that shares this VideoFrame's buffer. * * The given ToxAVFrame will be frame aligned under a pixel format of planar YUV with a chroma * subsampling format of 4:2:0 (i.e. AV_PIX_FMT_YUV420P). * * @param frameSize the given frame size of ToxAVFrame to generate. Defaults to source frame size * if frameSize is invalid. * @return a ToxAVFrame structure that represents this VideoFrame, sharing it's buffers or an * empty structure if this VideoFrame is no longer valid. */ ToxYUVFrame VideoFrame::toToxYUVFrame(QSize frameSize) { if (!frameSize.isValid()) { frameSize = sourceDimensions.size(); } // Converter function (constructs ToxAVFrame out of AVFrame*) const std::function converter = [&](AVFrame* const frame) { ToxYUVFrame ret{static_cast(frameSize.width()), static_cast(frameSize.height()), frame->data[0], frame->data[1], frame->data[2]}; return ret; }; return toGenericObject(frameSize, AV_PIX_FMT_YUV420P, true, converter, ToxYUVFrame{0, 0, nullptr, nullptr, nullptr}); } /** * @brief Returns the ID for the given frame. * * Frame IDs are globally unique (with respect to the running instance). * * @return an integer representing the ID of this frame. */ VideoFrame::IDType VideoFrame::getFrameID() const { return frameID; } /** * @brief Returns the ID for the VideoSource which created this frame. * * @return an integer representing the ID of the VideoSource which created this frame. */ VideoFrame::IDType VideoFrame::getSourceID() const { return sourceID; } /** * @brief Retrieves a copy of the source VideoFrame's dimensions. * * @return QRect copy representing the source VideoFrame's dimensions. */ QRect VideoFrame::getSourceDimensions() const { return sourceDimensions; } /** * @brief Retrieves a copy of the source VideoFormat's pixel format. * * @return integer copy representing the source VideoFrame's pixel format. */ int VideoFrame::getSourcePixelFormat() const { return sourcePixelFormat; } /** * @brief Constructs a new FrameBufferKey with the given attributes. * * @param width the width of the frame. * @param height the height of the frame. * @param pixFmt the pixel format of the frame. * @param lineAligned whether the linesize matches the width of the image. */ VideoFrame::FrameBufferKey::FrameBufferKey(const int width, const int height, const int pixFmt, const bool lineAligned) : frameWidth(width) , frameHeight(height) , pixelFormat(pixFmt) , linesizeAligned(lineAligned) { } /** * @brief Comparison operator for FrameBufferKey. * * @param other instance to compare against. * @return true if instances are equivilent, false otherwise. */ bool VideoFrame::FrameBufferKey::operator==(const FrameBufferKey& other) const { return pixelFormat == other.pixelFormat && frameWidth == other.frameWidth && frameHeight == other.frameHeight && linesizeAligned == other.linesizeAligned; } /** * @brief Not equal to operator for FrameBufferKey. * * @param other instance to compare against * @return true if instances are not equivilent, false otherwise. */ bool VideoFrame::FrameBufferKey::operator!=(const FrameBufferKey& other) const { return !operator==(other); } /** * @brief Hash function for FrameBufferKey. * * This function computes a hash value for use with std::unordered_map. * * @param key the given instance to compute hash value of. * @return the hash of the given instance. */ size_t VideoFrame::FrameBufferKey::hash(const FrameBufferKey& key) { std::hash intHasher; std::hash boolHasher; // Use java-style hash function to combine fields // See: https://en.wikipedia.org/wiki/Java_hashCode%28%29#hashCode.28.29_in_general size_t ret = 47; ret = 37 * ret + intHasher(key.frameWidth); ret = 37 * ret + intHasher(key.frameHeight); ret = 37 * ret + intHasher(key.pixelFormat); ret = 37 * ret + boolHasher(key.linesizeAligned); return ret; } /** * @brief Generates a key object based on given parameters. * * @param frameSize the given size of the frame. * @param pixFmt the pixel format of the frame. * @param linesize the maximum linesize of the frame, may be larger than the width. * @return a FrameBufferKey object representing the key for the frameBuffer map. */ VideoFrame::FrameBufferKey VideoFrame::getFrameKey(const QSize& frameSize, const int pixFmt, const int linesize) { return getFrameKey(frameSize, pixFmt, frameSize.width() == linesize); } /** * @brief Generates a key object based on given parameters. * * @param frameSize the given size of the frame. * @param pixFmt the pixel format of the frame. * @param frameAligned true if the frame is aligned, false otherwise. * @return a FrameBufferKey object representing the key for the frameBuffer map. */ VideoFrame::FrameBufferKey VideoFrame::getFrameKey(const QSize& frameSize, const int pixFmt, const bool frameAligned) { return {frameSize.width(), frameSize.height(), pixFmt, frameAligned}; } /** * @brief Retrieves an AVFrame derived from the source based on the given parameters without * obtaining a lock. * * This function is not thread-safe and must be called from a thread-safe context. * * Note: this function differs from getAVFrame() in that it returns a nullptr if no frame was * found. * * @param dimensions the dimensions of the frame, must be valid. * @param pixelFormat the desired pixel format of the frame. * @param requireAligned true if the frame must be frame aligned, false otherwise. * @return a pointer to a AVFrame with the given parameters or nullptr if no such frame was * found. */ AVFrame* VideoFrame::retrieveAVFrame(const QSize& dimensions, const int pixelFormat, const bool requireAligned) { if (!requireAligned) { /* * We attempt to obtain a unaligned frame first because an unaligned linesize corresponds * to a data aligned frame. */ FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, false); if (frameBuffer.count(frameKey) > 0) { return frameBuffer[frameKey]; } } FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, true); if (frameBuffer.count(frameKey) > 0) { return frameBuffer[frameKey]; } else { return nullptr; } } /** * @brief Generates an AVFrame based on the given specifications. * * This function is not thread-safe and must be called from a thread-safe context. * * @param dimensions the required dimensions for the frame, must be valid. * @param pixelFormat the required pixel format for the frame. * @param requireAligned true if the generated frame needs to be frame aligned, false otherwise. * @return an AVFrame with the given specifications. */ AVFrame* VideoFrame::generateAVFrame(const QSize& dimensions, const int pixelFormat, const bool requireAligned) { AVFrame* ret = av_frame_alloc(); if (!ret) { return nullptr; } // Populate AVFrame fields ret->width = dimensions.width(); ret->height = dimensions.height(); ret->format = pixelFormat; /* * We generate a frame under data alignment only if the dimensions allow us to be frame aligned * or if the caller doesn't require frame alignment */ int bufSize; const bool alreadyAligned = dimensions.width() % dataAlignment == 0 && dimensions.height() % dataAlignment == 0; if (!requireAligned || alreadyAligned) { bufSize = av_image_alloc(ret->data, ret->linesize, dimensions.width(), dimensions.height(), static_cast(pixelFormat), dataAlignment); } else { bufSize = av_image_alloc(ret->data, ret->linesize, dimensions.width(), dimensions.height(), static_cast(pixelFormat), 1); } if (bufSize < 0) { av_frame_free(&ret); return nullptr; } // Bilinear is better for shrinking, bicubic better for upscaling int resizeAlgo = sourceDimensions.width() > dimensions.width() ? SWS_BILINEAR : SWS_BICUBIC; SwsContext* swsCtx = sws_getContext(sourceDimensions.width(), sourceDimensions.height(), static_cast(sourcePixelFormat), dimensions.width(), dimensions.height(), static_cast(pixelFormat), resizeAlgo, nullptr, nullptr, nullptr); if (!swsCtx) { av_freep(&ret->data[0]); #if LIBAVCODEC_VERSION_INT < 3747941 av_frame_unref(ret); #endif av_frame_free(&ret); return nullptr; } AVFrame* source = frameBuffer[sourceFrameKey]; sws_scale(swsCtx, source->data, source->linesize, 0, sourceDimensions.height(), ret->data, ret->linesize); sws_freeContext(swsCtx); return ret; } /** * @brief Stores a given AVFrame within the frameBuffer map. * * As protection against duplicate frames, the storage mechanism will only allow one frame of a * given type to exist in the frame buffer. Should the given frame type already exist in the frame * buffer, the given frame will be freed and have it's buffers invalidated. In order to ensure * correct operation, always replace the frame pointer with the one returned by this function. * * As an example: * @code{.cpp} * AVFrame* frame = // create AVFrame... * * frame = storeAVFrame(frame, dimensions, pixelFormat); * @endcode * * This function is not thread-safe and must be called from a thread-safe context. * * @param frame the given frame to store. * @param dimensions the dimensions of the frame, must be valid. * @param pixelFormat the pixel format of the frame. * @return The given AVFrame* or a pre-existing AVFrame* that already exists in the frameBuffer. */ AVFrame* VideoFrame::storeAVFrame(AVFrame* frame, const QSize& dimensions, const int pixelFormat) { FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, frame->linesize[0]); // We check the prescence of the frame in case of double-computation if (frameBuffer.count(frameKey) > 0) { AVFrame* old_ret = frameBuffer[frameKey]; // Free new frame av_freep(&frame->data[0]); #if LIBAVCODEC_VERSION_INT < 3747941 av_frame_unref(frame); #endif av_frame_free(&frame); return old_ret; } else { frameBuffer[frameKey] = frame; return frame; } } /** * @brief Releases all frames within the frame buffer. * * This function is not thread-safe and must be called from a thread-safe context. */ void VideoFrame::deleteFrameBuffer() { // An empty framebuffer represents a frame that's already been freed if (frameBuffer.empty()) { return; } for (const auto& frameIterator : frameBuffer) { AVFrame* frame = frameIterator.second; // Treat source frame and derived frames separately if (sourceFrameKey == frameIterator.first) { if (freeSourceFrame) { av_freep(&frame->data[0]); } #if LIBAVCODEC_VERSION_INT < 3747941 av_frame_unref(frame); #endif av_frame_free(&frame); } else { av_freep(&frame->data[0]); #if LIBAVCODEC_VERSION_INT < 3747941 av_frame_unref(frame); #endif av_frame_free(&frame); } } frameBuffer.clear(); } /** * @brief Converts this VideoFrame to a generic type T based on the given parameters and * supplied converter functions. * * This function is used internally to create various toXObject functions that all follow the * same generation pattern (where XObject is some existing type like QImage). * * In order to create such a type, a object constructor function is required that takes the * generated AVFrame object and creates type T out of it. This function additionally requires * a null object of type T that represents an invalid/null object for when the generation * process fails (e.g. when the VideoFrame is no longer valid). * * @param dimensions the dimensions of the frame, must be valid. * @param pixelFormat the pixel format of the frame. * @param requireAligned true if the generated frame needs to be frame aligned, false otherwise. * @param objectConstructor a std::function that takes the generated AVFrame and converts it * to an object of type T. * @param nullObject an object of type T that represents the null/invalid object to be used * when the generation process fails. */ template T VideoFrame::toGenericObject(const QSize& dimensions, const int pixelFormat, const bool requireAligned, const std::function& objectConstructor, const T& nullObject) { frameLock.lockForRead(); // We return nullObject if the VideoFrame is no longer valid if (frameBuffer.size() == 0) { frameLock.unlock(); return nullObject; } AVFrame* frame = retrieveAVFrame(dimensions, static_cast(pixelFormat), requireAligned); if (frame) { T ret = objectConstructor(frame); frameLock.unlock(); return ret; } // VideoFrame does not contain an AVFrame to spec, generate one here frame = generateAVFrame(dimensions, static_cast(pixelFormat), requireAligned); /* * We need to "upgrade" the lock to a write lock so we can update our frameBuffer map. * * It doesn't matter if another thread obtains the write lock before we finish since it is * likely writing to somewhere else. Worst-case scenario, we merely perform the generation * process twice, and discard the old result. */ frameLock.unlock(); frameLock.lockForWrite(); frame = storeAVFrame(frame, dimensions, static_cast(pixelFormat)); T ret = objectConstructor(frame); frameLock.unlock(); return ret; } // Explicitly specialize VideoFrame::toGenericObject() function template QImage VideoFrame::toGenericObject( const QSize& dimensions, const int pixelFormat, const bool requireAligned, const std::function &objectConstructor, const QImage& nullObject); template ToxYUVFrame VideoFrame::toGenericObject( const QSize& dimensions, const int pixelFormat, const bool requireAligned, const std::function &objectConstructor, const ToxYUVFrame& nullObject); /** * @brief Returns whether the given ToxYUVFrame represents a valid frame or not. * * Valid frames are frames in which both width and height are greater than zero. * * @return true if the frame is valid, false otherwise. */ bool ToxYUVFrame::isValid() const { return width > 0 && height > 0; } /** * @brief Checks if the given ToxYUVFrame is valid or not, delegates to isValid(). */ ToxYUVFrame::operator bool() const { return isValid(); } qTox/src/video/videoframe.h000066400000000000000000000116341415623743500162170ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef VIDEOFRAME_H #define VIDEOFRAME_H #include #include #include #include #include extern "C" { #include } #include #include #include #include #include struct ToxYUVFrame { public: bool isValid() const; explicit operator bool() const; const std::uint16_t width; const std::uint16_t height; const uint8_t* y; const uint8_t* u; const uint8_t* v; }; class VideoFrame { public: // Declare type aliases using IDType = std::uint_fast64_t; using AtomicIDType = std::atomic_uint_fast64_t; public: VideoFrame(IDType sourceID, AVFrame* sourceFrame, QRect dimensions, int pixFmt, bool freeSourceFrame = false); VideoFrame(IDType sourceID, AVFrame* sourceFrame, bool freeSourceFrame = false); ~VideoFrame(); // Copy/Move operations are disabled for the VideoFrame, encapsulate with a std::shared_ptr to // manage. VideoFrame(const VideoFrame& other) = delete; VideoFrame(VideoFrame&& other) = delete; const VideoFrame& operator=(const VideoFrame& other) = delete; const VideoFrame& operator=(VideoFrame&& other) = delete; bool isValid(); std::shared_ptr trackFrame(); static void untrackFrames(const IDType& sourceID, bool releaseFrames = false); void releaseFrame(); const AVFrame* getAVFrame(QSize frameSize, const int pixelFormat, const bool requireAligned); QImage toQImage(QSize frameSize = {}); ToxYUVFrame toToxYUVFrame(QSize frameSize = {}); IDType getFrameID() const; IDType getSourceID() const; QRect getSourceDimensions() const; int getSourcePixelFormat() const; static constexpr int dataAlignment = 32; private: class FrameBufferKey { public: FrameBufferKey(const int width, const int height, const int pixFmt, const bool lineAligned); // Explictly state default constructor/destructor FrameBufferKey(const FrameBufferKey&) = default; FrameBufferKey(FrameBufferKey&&) = default; ~FrameBufferKey() = default; // Assignment operators are disabled for the FrameBufferKey const FrameBufferKey& operator=(const FrameBufferKey&) = delete; const FrameBufferKey& operator=(FrameBufferKey&&) = delete; bool operator==(const FrameBufferKey& other) const; bool operator!=(const FrameBufferKey& other) const; static size_t hash(const FrameBufferKey& key); public: const int frameWidth; const int frameHeight; const int pixelFormat; const bool linesizeAligned; }; private: static FrameBufferKey getFrameKey(const QSize& frameSize, const int pixFmt, const int linesize); static FrameBufferKey getFrameKey(const QSize& frameSize, const int pixFmt, const bool frameAligned); AVFrame* retrieveAVFrame(const QSize& dimensions, const int pixelFormat, const bool requireAligned); AVFrame* generateAVFrame(const QSize& dimensions, const int pixelFormat, const bool requireAligned); AVFrame* storeAVFrame(AVFrame* frame, const QSize& dimensions, const int pixelFormat); void deleteFrameBuffer(); template T toGenericObject(const QSize& dimensions, const int pixelFormat, const bool requireAligned, const std::function& objectConstructor, const T& nullObject); private: // ID const IDType frameID; const IDType sourceID; // Main framebuffer store std::unordered_map> frameBuffer{3, FrameBufferKey::hash}; // Source frame const QRect sourceDimensions; int sourcePixelFormat; const FrameBufferKey sourceFrameKey; const bool freeSourceFrame; // Reference store static AtomicIDType frameIDs; static std::unordered_map mutexMap; static std::unordered_map>> refsMap; // Concurrency QReadWriteLock frameLock{}; static QReadWriteLock refsLock; }; #endif // VIDEOFRAME_H qTox/src/video/videomode.cpp000066400000000000000000000046061415623743500164050ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "videomode.h" /** * @struct VideoMode * @brief Describes a video mode supported by a device. * * @var unsigned short VideoMode::width, VideoMode::height * @brief Displayed video resolution (NOT frame resolution). * * @var unsigned short VideoMode::x, VideoMode::y * @brief Coordinates of upper-left corner. * * @var float VideoMode::FPS * @brief Frames per second supported by the device at this resolution * @note a value < 0 indicates an invalid value */ VideoMode::VideoMode(int width, int height, int x, int y, float FPS) : width(width) , height(height) , x(x) , y(y) , FPS(FPS) { } VideoMode::VideoMode(QRect rect) : width(rect.width()) , height(rect.height()) , x(rect.x()) , y(rect.y()) { } QRect VideoMode::toRect() const { return QRect(x, y, width, height); } bool VideoMode::operator==(const VideoMode& other) const { return width == other.width && height == other.height && x == other.x && y == other.y && qFuzzyCompare(FPS, other.FPS) && pixel_format == other.pixel_format; } uint32_t VideoMode::norm(const VideoMode& other) const { return qAbs(this->width - other.width) + qAbs(this->height - other.height); } uint32_t VideoMode::tolerance() const { constexpr uint32_t minTolerance = 300; // keep wider tolerance for low res cameras constexpr uint32_t toleranceFactor = 10; // video mode must be within 10% to be "close enough" to ideal return std::max((width + height)/toleranceFactor, minTolerance); } /** * @brief All zeros means a default/unspecified mode */ VideoMode::operator bool() const { return width || height || static_cast(FPS); } qTox/src/video/videomode.h000066400000000000000000000024171415623743500160500ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef VIDEOMODE_H #define VIDEOMODE_H #include #include struct VideoMode { int width, height; int x, y; float FPS = -1.0f; uint32_t pixel_format = 0; VideoMode(int width = 0, int height = 0, int x = 0, int y = 0, float FPS = -1.0f); explicit VideoMode(QRect rect); QRect toRect() const; operator bool() const; bool operator==(const VideoMode& other) const; uint32_t norm(const VideoMode& other) const; uint32_t tolerance() const; }; #endif // VIDEOMODE_H qTox/src/video/videosource.cpp000066400000000000000000000021351415623743500167540ustar00rootroot00000000000000/* Copyright © 2016-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "videosource.h" /** * @class VideoSource * @brief An abstract source of video frames * * When it has at least one subscriber the source will emit new video frames. * Subscribing is recursive, multiple users can subscribe to the same VideoSource. */ // Initialize sourceIDs to 0 VideoSource::AtomicIDType VideoSource::sourceIDs{0}; qTox/src/video/videosource.h000066400000000000000000000037321415623743500164250ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef VIDEOSOURCE_H #define VIDEOSOURCE_H #include #include #include class VideoFrame; class VideoSource : public QObject { Q_OBJECT public: // Declare type aliases using IDType = std::uint_fast64_t; using AtomicIDType = std::atomic_uint_fast64_t; public: VideoSource() : id(sourceIDs++) { } virtual ~VideoSource() = default; /** * @brief If subscribe sucessfully opens the source, it will start emitting frameAvailable * signals. */ virtual void subscribe() = 0; /** * @brief Stop emitting frameAvailable signals, and free associated resources if necessary. */ virtual void unsubscribe() = 0; /// ID of this VideoSource const IDType id; signals: /** * @brief Emitted when new frame available to use. * @param frame New frame. */ void frameAvailable(std::shared_ptr frame); /** * @brief Emitted when the source is stopped for an indefinite amount of time, but might restart * sending frames again later */ void sourceStopped(); private: // Used to manage a global ID for all VideoSources static AtomicIDType sourceIDs; }; #endif // VIDEOSOURCE_H qTox/src/video/videosurface.cpp000066400000000000000000000136631415623743500171140ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "videosurface.h" #include "src/core/core.h" #include "src/model/friend.h" #include "src/friendlist.h" #include "src/persistence/settings.h" #include "src/video/videoframe.h" #include "src/widget/friendwidget.h" #include "src/widget/style.h" #include #include #include /** * @var std::atomic_bool VideoSurface::frameLock * @brief Fast lock for lastFrame. */ float getSizeRatio(const QSize size) { return size.width() / static_cast(size.height()); } VideoSurface::VideoSurface(const QPixmap& avatar, QWidget* parent, bool expanding) : QWidget{parent} , source{nullptr} , frameLock{false} , hasSubscribed{0} , avatar{avatar} , ratio{1.0f} , expanding{expanding} { recalulateBounds(); } VideoSurface::VideoSurface(const QPixmap& avatar, VideoSource* source, QWidget* parent) : VideoSurface(avatar, parent) { setSource(source); } VideoSurface::~VideoSurface() { unsubscribe(); } bool VideoSurface::isExpanding() const { return expanding; } /** * @brief Update source. * @note nullptr is a valid option. * @param src source to set. * * Unsubscribe from old source and subscribe to new. */ void VideoSurface::setSource(VideoSource* src) { if (source == src) return; unsubscribe(); source = src; subscribe(); } QRect VideoSurface::getBoundingRect() const { QRect bRect = boundingRect; bRect.setBottomRight(QPoint(boundingRect.bottom() + 1, boundingRect.right() + 1)); return boundingRect; } float VideoSurface::getRatio() const { return ratio; } void VideoSurface::setAvatar(const QPixmap& pixmap) { avatar = pixmap; update(); } QPixmap VideoSurface::getAvatar() const { return avatar; } void VideoSurface::subscribe() { if (source && hasSubscribed++ == 0) { source->subscribe(); connect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable); connect(source, &VideoSource::sourceStopped, this, &VideoSurface::onSourceStopped); } } void VideoSurface::unsubscribe() { if (!source || hasSubscribed == 0) return; if (--hasSubscribed != 0) return; lock(); lastFrame.reset(); unlock(); ratio = 1.0f; recalulateBounds(); emit ratioChanged(); emit boundaryChanged(); disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable); disconnect(source, &VideoSource::sourceStopped, this, &VideoSurface::onSourceStopped); source->unsubscribe(); } void VideoSurface::onNewFrameAvailable(const std::shared_ptr& newFrame) { QSize newSize; lock(); lastFrame = newFrame; newSize = lastFrame->getSourceDimensions().size(); unlock(); float newRatio = getSizeRatio(newSize); if (!qFuzzyCompare(newRatio, ratio) && isVisible()) { ratio = newRatio; recalulateBounds(); emit ratioChanged(); emit boundaryChanged(); } update(); } void VideoSurface::onSourceStopped() { // If the source's stream is on hold, just revert back to the avatar view lastFrame.reset(); update(); } void VideoSurface::paintEvent(QPaintEvent*) { lock(); QPainter painter(this); painter.fillRect(painter.viewport(), Qt::black); if (lastFrame) { QImage frame = lastFrame->toQImage(rect().size()); if (frame.isNull()) lastFrame.reset(); painter.drawImage(boundingRect, frame, frame.rect(), Qt::NoFormatConversion); } else { painter.fillRect(boundingRect, Qt::white); QPixmap drawnAvatar = avatar; if (drawnAvatar.isNull()) drawnAvatar = Style::scaleSvgImage(":/img/contact_dark.svg", boundingRect.width(), boundingRect.height()); painter.drawPixmap(boundingRect, drawnAvatar, drawnAvatar.rect()); } unlock(); } void VideoSurface::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); recalulateBounds(); emit boundaryChanged(); } void VideoSurface::showEvent(QShowEvent* e) { Q_UNUSED(e); // emit ratioChanged(); } void VideoSurface::recalulateBounds() { if (expanding) { boundingRect = contentsRect(); } else { QPoint pos; QSize size; QSize usableSize = contentsRect().size(); int possibleWidth = usableSize.height() * ratio; if (possibleWidth > usableSize.width()) size = (QSize(usableSize.width(), usableSize.width() / ratio)); else size = (QSize(possibleWidth, usableSize.height())); pos.setX(width() / 2 - size.width() / 2); pos.setY(height() / 2 - size.height() / 2); boundingRect.setRect(pos.x(), pos.y(), size.width(), size.height()); } update(); } void VideoSurface::lock() { // Fast lock bool expected = false; while (!frameLock.compare_exchange_weak(expected, true)) expected = false; } void VideoSurface::unlock() { frameLock = false; } qTox/src/video/videosurface.h000066400000000000000000000042211415623743500165470ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SELFCAMVIEW_H #define SELFCAMVIEW_H #include "src/video/videosource.h" #include #include #include class VideoSurface : public QWidget { Q_OBJECT public: VideoSurface(const QPixmap& avatar, QWidget* parent = nullptr, bool expanding = false); VideoSurface(const QPixmap& avatar, VideoSource* source, QWidget* parent = nullptr); ~VideoSurface(); bool isExpanding() const; void setSource(VideoSource* src); QRect getBoundingRect() const; float getRatio() const; void setAvatar(const QPixmap& pixmap); QPixmap getAvatar() const; signals: void ratioChanged(); void boundaryChanged(); protected: void subscribe(); void unsubscribe(); virtual void paintEvent(QPaintEvent* event) final override; virtual void resizeEvent(QResizeEvent* event) final override; virtual void showEvent(QShowEvent* event) final override; private slots: void onNewFrameAvailable(const std::shared_ptr& newFrame); void onSourceStopped(); private: void recalulateBounds(); void lock(); void unlock(); QRect boundingRect; VideoSource* source; std::shared_ptr lastFrame; std::atomic_bool frameLock; uint8_t hasSubscribed; QPixmap avatar; float ratio; bool expanding; }; #endif // SELFCAMVIEW_H qTox/src/widget/000077500000000000000000000000001415623743500140755ustar00rootroot00000000000000qTox/src/widget/about/000077500000000000000000000000001415623743500152075ustar00rootroot00000000000000qTox/src/widget/about/aboutfriendform.cpp000066400000000000000000000121231415623743500211000ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "aboutfriendform.h" #include "src/widget/gui.h" #include "ui_aboutfriendform.h" #include "src/core/core.h" #include "src/widget/style.h" #include #include AboutFriendForm::AboutFriendForm(std::unique_ptr _about, QWidget* parent) : QDialog(parent) , ui(new Ui::AboutFriendForm) , about{std::move(_about)} { ui->setupUi(this); ui->label_4->hide(); ui->aliases->hide(); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &AboutFriendForm::onAcceptedClicked); connect(ui->autoacceptfile, &QCheckBox::clicked, this, &AboutFriendForm::onAutoAcceptDirClicked); connect(ui->autoacceptcall, SIGNAL(activated(int)), this, SLOT(onAutoAcceptCallClicked(void))); connect(ui->autogroupinvite, &QCheckBox::clicked, this, &AboutFriendForm::onAutoGroupInvite); connect(ui->selectSaveDir, &QPushButton::clicked, this, &AboutFriendForm::onSelectDirClicked); connect(ui->removeHistory, &QPushButton::clicked, this, &AboutFriendForm::onRemoveHistoryClicked); about->connectTo_autoAcceptDirChanged(this, [=](const QString& dir){ onAutoAcceptDirChanged(dir); }); const QString dir = about->getAutoAcceptDir(); ui->autoacceptfile->setChecked(!dir.isEmpty()); ui->removeHistory->setEnabled(about->isHistoryExistence()); const int index = static_cast(about->getAutoAcceptCall()); ui->autoacceptcall->setCurrentIndex(index); ui->selectSaveDir->setEnabled(ui->autoacceptfile->isChecked()); ui->autogroupinvite->setChecked(about->getAutoGroupInvite()); if (ui->autoacceptfile->isChecked()) { ui->selectSaveDir->setText(about->getAutoAcceptDir()); } const QString name = about->getName(); setWindowTitle(name); ui->userName->setText(name); ui->publicKey->setText(about->getPublicKey().toString()); ui->publicKey->setCursorPosition(0); // scroll textline to left ui->note->setPlainText(about->getNote()); ui->statusMessage->setText(about->getStatusMessage()); ui->avatar->setPixmap(about->getAvatar()); setStyleSheet(Style::getStylesheet("window/general.css")); } static QString getAutoAcceptDir(const QString& dir) { //: popup title const QString title = AboutFriendForm::tr("Choose an auto accept directory"); return QFileDialog::getExistingDirectory(Q_NULLPTR, title, dir); } void AboutFriendForm::onAutoAcceptDirClicked() { const QString dir = [&]{ if (!ui->autoacceptfile->isChecked()) { return QString{}; } return getAutoAcceptDir(about->getAutoAcceptDir()); }(); about->setAutoAcceptDir(dir); } void AboutFriendForm::onAutoAcceptDirChanged(const QString& path) { const bool enabled = !path.isNull(); ui->autoacceptfile->setChecked(enabled); ui->selectSaveDir->setEnabled(enabled); ui->selectSaveDir->setText(enabled ? path : tr("Auto accept for this contact is disabled")); } void AboutFriendForm::onAutoAcceptCallClicked() { const int index = ui->autoacceptcall->currentIndex(); const IFriendSettings::AutoAcceptCallFlags flag{index}; about->setAutoAcceptCall(flag); } /** * @brief Sets the AutoGroupinvite status and saves the settings. */ void AboutFriendForm::onAutoGroupInvite() { about->setAutoGroupInvite(ui->autogroupinvite->isChecked()); } void AboutFriendForm::onSelectDirClicked() { const QString dir = getAutoAcceptDir(about->getAutoAcceptDir()); about->setAutoAcceptDir(dir); } /** * @brief Called when user clicks the bottom OK button, save all settings */ void AboutFriendForm::onAcceptedClicked() { about->setNote(ui->note->toPlainText()); } void AboutFriendForm::onRemoveHistoryClicked() { const bool retYes = GUI::askQuestion(tr("Confirmation"), tr("Are you sure to remove %1 chat history?").arg(about->getName()), /* defaultAns = */ false, /* warning = */ true, /* yesno = */ true); if (!retYes) { return; } const bool result = about->clearHistory(); if (!result) { GUI::showWarning(tr("History removed"), tr("Failed to remove chat history with %1!").arg(about->getName()).toHtmlEscaped()); return; } emit histroyRemoved(); ui->removeHistory->setEnabled(false); // For know clearly to has removed the history } AboutFriendForm::~AboutFriendForm() { delete ui; } qTox/src/widget/about/aboutfriendform.h000066400000000000000000000030221415623743500205430ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ABOUT_USER_FORM_H #define ABOUT_USER_FORM_H #include "src/model/about/iaboutfriend.h" #include #include #include namespace Ui { class AboutFriendForm; } class AboutFriendForm : public QDialog { Q_OBJECT public: AboutFriendForm(std::unique_ptr about, QWidget* parent = nullptr); ~AboutFriendForm(); private: Ui::AboutFriendForm* ui; const std::unique_ptr about; signals: void histroyRemoved(); private slots: void onAutoAcceptDirChanged(const QString& path); void onAcceptedClicked(); void onAutoAcceptDirClicked(); void onAutoAcceptCallClicked(); void onAutoGroupInvite(); void onSelectDirClicked(); void onRemoveHistoryClicked(); }; #endif // ABOUT_USER_FORM_H qTox/src/widget/about/aboutfriendform.ui000066400000000000000000000225451415623743500207440ustar00rootroot00000000000000 AboutFriendForm 0 0 506 521 Dialog username Qt::PlainText Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse 0 0 false status message Qt::PlainText Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse 0 0 75 75 75 75 Qt::PlainText ../../../img/contact_dark.svg true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Qt::LeftToRight Public key (not ToxID): true false false 0 true Qt::LogicalMoveStyle Used aliases: HISTORY OF ALIASES Qt::PlainText Automatically accept files from contact if set Auto accept files Default directory to save files: Auto accept for this contact is disabled Auto accept call: Manual Audio Audio + Video Automatically accept group chat invitations from this contact if set. Auto accept group invites Remove history (operation can not be undone!) Notes Input field for notes about the contact You can save comment about this contact here. Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok publicKey autoacceptcall autoacceptfile selectSaveDir removeHistory note buttonBox accepted() AboutFriendForm accept() 248 254 157 274 buttonBox rejected() AboutFriendForm reject() 316 260 286 274 qTox/src/widget/categorywidget.cpp000066400000000000000000000207771415623743500176370ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "categorywidget.h" #include "friendlistlayout.h" #include "friendlistwidget.h" #include "friendwidget.h" #include "src/model/status.h" #include "src/widget/style.h" #include "tool/croppinglabel.h" #include #include #include void CategoryWidget::emitChatroomWidget(QLayout* layout, int index) { QWidget* widget = layout->itemAt(index)->widget(); GenericChatroomWidget* chatWidget = qobject_cast(widget); if (chatWidget != nullptr) { emit chatWidget->chatroomWidgetClicked(chatWidget); } } CategoryWidget::CategoryWidget(bool compact, QWidget* parent) : GenericChatItemWidget(compact, parent) { container = new QWidget(this); container->setObjectName("circleWidgetContainer"); container->setLayoutDirection(Qt::LeftToRight); statusLabel = new QLabel(this); statusLabel->setObjectName("status"); statusLabel->setTextFormat(Qt::PlainText); statusPic.setPixmap(QPixmap(Style::getImagePath("chatArea/scrollBarRightArrow.svg"))); fullLayout = new QVBoxLayout(this); fullLayout->setSpacing(0); fullLayout->setMargin(0); fullLayout->addWidget(container); lineFrame = new QFrame(container); lineFrame->setObjectName("line"); lineFrame->setFrameShape(QFrame::HLine); lineFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); lineFrame->resize(0, 0); listLayout = new FriendListLayout(); listWidget = new QWidget(this); listWidget->setLayout(listLayout); fullLayout->addWidget(listWidget); setAcceptDrops(true); onCompactChanged(isCompact()); setExpanded(true, false); updateStatus(); } bool CategoryWidget::isExpanded() const { return expanded; } void CategoryWidget::setExpanded(bool isExpanded, bool save) { if (expanded == isExpanded) { return; } expanded = isExpanded; setMouseTracking(true); listWidget->setVisible(isExpanded); QString pixmapPath; if (isExpanded) pixmapPath = Style::getImagePath("chatArea/scrollBarDownArrow.svg"); else pixmapPath = Style::getImagePath("chatArea/scrollBarRightArrow.svg"); statusPic.setPixmap(QPixmap(pixmapPath)); // The listWidget will recieve a enterEvent for some reason if now visible. // Using the following, we prevent that. QApplication::processEvents(QEventLoop::ExcludeSocketNotifiers); container->hide(); container->show(); if (save) onExpand(); } void CategoryWidget::leaveEvent(QEvent* event) { event->ignore(); } void CategoryWidget::setName(const QString& name, bool save) { nameLabel->setText(name); if (isCompact()) nameLabel->minimizeMaximumWidth(); if (save) onSetName(); } void CategoryWidget::editName() { nameLabel->editBegin(); nameLabel->setMaximumWidth(QWIDGETSIZE_MAX); } void CategoryWidget::addFriendWidget(FriendWidget* w, Status::Status s) { listLayout->addFriendWidget(w, s); updateStatus(); onAddFriendWidget(w); w->reloadTheme(); // Otherwise theme will change when moving to another circle. } void CategoryWidget::removeFriendWidget(FriendWidget* w, Status::Status s) { listLayout->removeFriendWidget(w, s); updateStatus(); } void CategoryWidget::updateStatus() { QString online = QString::number(listLayout->friendOnlineCount()); QString offline = QString::number(listLayout->friendTotalCount()); QString text = online + QStringLiteral(" / ") + offline; statusLabel->setText(text); } bool CategoryWidget::hasChatrooms() const { return listLayout->hasChatrooms(); } void CategoryWidget::search(const QString& searchString, bool updateAll, bool hideOnline, bool hideOffline) { if (updateAll) { listLayout->searchChatrooms(searchString, hideOnline, hideOffline); } bool inCategory = searchString.isEmpty() && !(hideOnline && hideOffline); setVisible(inCategory || listLayout->hasChatrooms()); } bool CategoryWidget::cycleContacts(bool forward) { if (listLayout->friendTotalCount() == 0) { return false; } if (forward) { if (!listLayout->getLayoutOnline()->isEmpty()) { setExpanded(true); emitChatroomWidget(listLayout->getLayoutOnline(), 0); return true; } else if (!listLayout->getLayoutOffline()->isEmpty()) { setExpanded(true); emitChatroomWidget(listLayout->getLayoutOffline(), 0); return true; } } else { if (!listLayout->getLayoutOffline()->isEmpty()) { setExpanded(true); emitChatroomWidget(listLayout->getLayoutOffline(), listLayout->getLayoutOffline()->count() - 1); return true; } else if (!listLayout->getLayoutOnline()->isEmpty()) { setExpanded(true); emitChatroomWidget(listLayout->getLayoutOnline(), listLayout->getLayoutOnline()->count() - 1); return true; } } return false; } bool CategoryWidget::cycleContacts(FriendWidget* activeChatroomWidget, bool forward) { int index = -1; QLayout* currentLayout = nullptr; FriendWidget* friendWidget = qobject_cast(activeChatroomWidget); if (friendWidget == nullptr) return false; currentLayout = listLayout->getLayoutOnline(); index = listLayout->indexOfFriendWidget(friendWidget, true); if (index == -1) { currentLayout = listLayout->getLayoutOffline(); index = listLayout->indexOfFriendWidget(friendWidget, false); } index += forward ? 1 : -1; for (;;) { // Bounds checking. if (index < 0) { if (currentLayout == listLayout->getLayoutOffline()) currentLayout = listLayout->getLayoutOnline(); else return false; index = currentLayout->count() - 1; continue; } else if (index >= currentLayout->count()) { if (currentLayout == listLayout->getLayoutOnline()) currentLayout = listLayout->getLayoutOffline(); else return false; index = 0; continue; } GenericChatroomWidget* chatWidget = qobject_cast(currentLayout->itemAt(index)->widget()); if (chatWidget != nullptr) emit chatWidget->chatroomWidgetClicked(chatWidget); return true; } return false; } void CategoryWidget::onCompactChanged(bool _compact) { delete topLayout; delete mainLayout; topLayout = new QHBoxLayout; topLayout->setSpacing(0); topLayout->setMargin(0); Q_UNUSED(_compact); setCompact(true); nameLabel->minimizeMaximumWidth(); mainLayout = nullptr; container->setFixedHeight(25); container->setLayout(topLayout); topLayout->addSpacing(18); topLayout->addWidget(&statusPic); topLayout->addSpacing(5); topLayout->addWidget(nameLabel, 100); topLayout->addWidget(lineFrame, 1); topLayout->addSpacing(5); topLayout->addWidget(statusLabel); topLayout->addSpacing(5); topLayout->activate(); Style::repolish(this); } void CategoryWidget::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) setExpanded(!expanded); } void CategoryWidget::setContainerAttribute(Qt::WidgetAttribute attribute, bool enabled) { container->setAttribute(attribute, enabled); Style::repolish(container); } QLayout* CategoryWidget::friendOfflineLayout() const { return listLayout->getLayoutOffline(); } QLayout* CategoryWidget::friendOnlineLayout() const { return listLayout->getLayoutOnline(); } void CategoryWidget::moveFriendWidgets(FriendListWidget* friendList) { listLayout->moveFriendWidgets(friendList); } qTox/src/widget/categorywidget.h000066400000000000000000000051751415623743500172770ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CATEGORYWIDGET_H #define CATEGORYWIDGET_H #include "genericchatitemwidget.h" #include "src/core/core.h" #include "src/model/status.h" class FriendListLayout; class FriendListWidget; class FriendWidget; class QVBoxLayout; class QHBoxLayout; class CategoryWidget : public GenericChatItemWidget { Q_OBJECT public: explicit CategoryWidget(bool compact, QWidget* parent = nullptr); bool isExpanded() const; void setExpanded(bool isExpanded, bool save = true); void setName(const QString& name, bool save = true); void addFriendWidget(FriendWidget* w, Status::Status s); void removeFriendWidget(FriendWidget* w, Status::Status s); void updateStatus(); bool hasChatrooms() const; bool cycleContacts(bool forward); bool cycleContacts(FriendWidget* activeChatroomWidget, bool forward); void search(const QString& searchString, bool updateAll = false, bool hideOnline = false, bool hideOffline = false); public slots: void onCompactChanged(bool compact); void moveFriendWidgets(FriendListWidget* friendList); protected: virtual void leaveEvent(QEvent* event) final override; virtual void mouseReleaseEvent(QMouseEvent* event) final override; void editName(); void setContainerAttribute(Qt::WidgetAttribute attribute, bool enabled); QLayout* friendOnlineLayout() const; QLayout* friendOfflineLayout() const; void emitChatroomWidget(QLayout* layout, int index); private: virtual void onSetName() { } virtual void onExpand() { } virtual void onAddFriendWidget(FriendWidget*) { } QWidget* listWidget; FriendListLayout* listLayout; QVBoxLayout* fullLayout; QVBoxLayout* mainLayout = nullptr; QHBoxLayout* topLayout = nullptr; QLabel* statusLabel; QWidget* container; QFrame* lineFrame; bool expanded = false; }; #endif // CATEGORYWIDGET_H qTox/src/widget/chatformheader.cpp000066400000000000000000000223451415623743500175630ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chatformheader.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/style.h" #include "src/widget/tool/callconfirmwidget.h" #include "src/widget/tool/croppinglabel.h" #include "src/widget/translator.h" #include #include #include #include #include #include static const QSize AVATAR_SIZE{40, 40}; static const short HEAD_LAYOUT_SPACING = 5; static const short MIC_BUTTONS_LAYOUT_SPACING = 4; static const short BUTTONS_LAYOUT_HOR_SPACING = 4; namespace { const QString STYLE_PATH = QStringLiteral("chatForm/buttons.css"); const QString STATE_NAME[] = { QString{}, QStringLiteral("green"), QStringLiteral("red"), QStringLiteral("yellow"), QStringLiteral("yellow"), }; const QString CALL_TOOL_TIP[] = { ChatFormHeader::tr("Can't start audio call"), ChatFormHeader::tr("Start audio call"), ChatFormHeader::tr("End audio call"), ChatFormHeader::tr("Cancel audio call"), ChatFormHeader::tr("Accept audio call"), }; const QString VIDEO_TOOL_TIP[] = { ChatFormHeader::tr("Can't start video call"), ChatFormHeader::tr("Start video call"), ChatFormHeader::tr("End video call"), ChatFormHeader::tr("Cancel video call"), ChatFormHeader::tr("Accept video call"), }; const QString VOL_TOOL_TIP[] = { ChatFormHeader::tr("Sound can be disabled only during a call"), ChatFormHeader::tr("Mute call"), ChatFormHeader::tr("Unmute call"), }; const QString MIC_TOOL_TIP[] = { ChatFormHeader::tr("Microphone can be muted only during a call"), ChatFormHeader::tr("Mute microphone"), ChatFormHeader::tr("Unmute microphone"), }; template QPushButton* createButton(const QString& name, T* self, Fun onClickSlot) { QPushButton* btn = new QPushButton(); btn->setAttribute(Qt::WA_LayoutUsesWidgetRect); btn->setObjectName(name); btn->setStyleSheet(Style::getStylesheet(STYLE_PATH)); QObject::connect(btn, &QPushButton::clicked, self, onClickSlot); return btn; } template void setStateToolTip(QAbstractButton* btn, State state, const QString toolTip[]) { const int index = static_cast(state); btn->setToolTip(toolTip[index]); } template void setStateName(QAbstractButton* btn, State state) { const int index = static_cast(state); btn->setProperty("state", STATE_NAME[index]); btn->setEnabled(index != 0); } } ChatFormHeader::ChatFormHeader(QWidget* parent) : QWidget(parent) , mode{Mode::AV} , callState{CallButtonState::Disabled} , videoState{CallButtonState::Disabled} , volState{ToolButtonState::Disabled} , micState{ToolButtonState::Disabled} { QHBoxLayout* headLayout = new QHBoxLayout(); avatar = new MaskablePixmapWidget(this, AVATAR_SIZE, ":/img/avatar_mask.svg"); avatar->setObjectName("avatar"); nameLabel = new CroppingLabel(); nameLabel->setObjectName("nameLabel"); nameLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize()); nameLabel->setEditable(true); nameLabel->setTextFormat(Qt::PlainText); connect(nameLabel, &CroppingLabel::editFinished, this, &ChatFormHeader::nameChanged); headTextLayout = new QVBoxLayout(); headTextLayout->addStretch(); headTextLayout->addWidget(nameLabel); headTextLayout->addStretch(); micButton = createButton("micButton", this, &ChatFormHeader::micMuteToggle); volButton = createButton("volButton", this, &ChatFormHeader::volMuteToggle); callButton = createButton("callButton", this, &ChatFormHeader::callTriggered); videoButton = createButton("videoButton", this, &ChatFormHeader::videoCallTriggered); QVBoxLayout* micButtonsLayout = new QVBoxLayout(); micButtonsLayout->setSpacing(MIC_BUTTONS_LAYOUT_SPACING); micButtonsLayout->addWidget(micButton, Qt::AlignTop | Qt::AlignRight); micButtonsLayout->addWidget(volButton, Qt::AlignTop | Qt::AlignRight); QGridLayout* buttonsLayout = new QGridLayout(); buttonsLayout->addLayout(micButtonsLayout, 0, 0, 2, 1, Qt::AlignTop | Qt::AlignRight); buttonsLayout->addWidget(callButton, 0, 1, 2, 1, Qt::AlignTop); buttonsLayout->addWidget(videoButton, 0, 2, 2, 1, Qt::AlignTop); buttonsLayout->setVerticalSpacing(0); buttonsLayout->setHorizontalSpacing(BUTTONS_LAYOUT_HOR_SPACING); headLayout->addWidget(avatar); headLayout->addSpacing(HEAD_LAYOUT_SPACING); headLayout->addLayout(headTextLayout); headLayout->addLayout(buttonsLayout); setLayout(headLayout); updateButtonsView(); Translator::registerHandler(std::bind(&ChatFormHeader::retranslateUi, this), this); } ChatFormHeader::~ChatFormHeader() = default; void ChatFormHeader::setName(const QString& newName) { nameLabel->setText(newName); // for overlength names nameLabel->setToolTip(Qt::convertFromPlainText(newName, Qt::WhiteSpaceNormal)); } void ChatFormHeader::setMode(ChatFormHeader::Mode mode) { this->mode = mode; if (mode == Mode::None) { callButton->hide(); videoButton->hide(); volButton->hide(); micButton->hide(); } } void ChatFormHeader::retranslateUi() { setStateToolTip(callButton, callState, CALL_TOOL_TIP); setStateToolTip(videoButton, videoState, VIDEO_TOOL_TIP); setStateToolTip(micButton, micState, MIC_TOOL_TIP); setStateToolTip(volButton, volState, VOL_TOOL_TIP); } void ChatFormHeader::updateButtonsView() { setStateName(callButton, callState); setStateName(videoButton, videoState); setStateName(micButton, micState); setStateName(volButton, volState); retranslateUi(); Style::repolish(this); } void ChatFormHeader::showOutgoingCall(bool video) { CallButtonState& state = video ? videoState : callState; state = CallButtonState::Outgoing; updateButtonsView(); } void ChatFormHeader::createCallConfirm(bool video) { QWidget* btn = video ? videoButton : callButton; callConfirm = std::unique_ptr(new CallConfirmWidget(btn)); connect(callConfirm.get(), &CallConfirmWidget::accepted, this, &ChatFormHeader::callAccepted); connect(callConfirm.get(), &CallConfirmWidget::rejected, this, &ChatFormHeader::callRejected); } void ChatFormHeader::showCallConfirm() { if (callConfirm && !callConfirm->isVisible()) callConfirm->show(); } void ChatFormHeader::removeCallConfirm() { callConfirm.reset(nullptr); } void ChatFormHeader::updateCallButtons(bool online, bool audio, bool video) { const bool audioAvaliable = online && (mode & Mode::Audio); const bool videoAvaliable = online && (mode & Mode::Video); if (!audioAvaliable) { callState = CallButtonState::Disabled; } else if (video) { callState = CallButtonState::Disabled; } else if (audio) { callState = CallButtonState::InCall; } else { callState = CallButtonState::Avaliable; } if (!videoAvaliable) { videoState = CallButtonState::Disabled; } else if (video) { videoState = CallButtonState::InCall; } else if (audio) { videoState = CallButtonState::Disabled; } else { videoState = CallButtonState::Avaliable; } updateButtonsView(); } void ChatFormHeader::updateMuteMicButton(bool active, bool inputMuted) { micButton->setEnabled(active); if (active) { micState = inputMuted ? ToolButtonState::On : ToolButtonState::Off; } else { micState = ToolButtonState::Disabled; } updateButtonsView(); } void ChatFormHeader::updateMuteVolButton(bool active, bool outputMuted) { volButton->setEnabled(active); if (active) { volState = outputMuted ? ToolButtonState::On : ToolButtonState::Off; } else { volState = ToolButtonState::Disabled; } updateButtonsView(); } void ChatFormHeader::setAvatar(const QPixmap &img) { avatar->setPixmap(img); } QSize ChatFormHeader::getAvatarSize() const { return QSize{avatar->width(), avatar->height()}; } void ChatFormHeader::reloadTheme() { callButton->setStyleSheet(Style::getStylesheet(STYLE_PATH)); videoButton->setStyleSheet(Style::getStylesheet(STYLE_PATH)); volButton->setStyleSheet(Style::getStylesheet(STYLE_PATH)); micButton->setStyleSheet(Style::getStylesheet(STYLE_PATH)); } void ChatFormHeader::addWidget(QWidget* widget, int stretch, Qt::Alignment alignment) { headTextLayout->addWidget(widget, stretch, alignment); } void ChatFormHeader::addLayout(QLayout* layout) { headTextLayout->addLayout(layout); } void ChatFormHeader::addStretch() { headTextLayout->addStretch(); } qTox/src/widget/chatformheader.h000066400000000000000000000056571415623743500172370ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHAT_FORM_HEADER #define CHAT_FORM_HEADER #include #include class MaskablePixmapWidget; class QVBoxLayout; class CroppingLabel; class QPushButton; class QToolButton; class CallConfirmWidget; class ChatFormHeader : public QWidget { Q_OBJECT public: enum class CallButtonState { Disabled = 0, // Grey Avaliable = 1, // Green InCall = 2, // Red Outgoing = 3, // Yellow Incoming = 4, // Yellow }; enum class ToolButtonState { Disabled = 0, // Grey Off = 1, // Green On = 2, // Red }; enum Mode { None = 0, Audio = 1, Video = 2, AV = Audio | Video }; ChatFormHeader(QWidget* parent = nullptr); ~ChatFormHeader(); void setName(const QString& newName); void setMode(Mode mode); void showOutgoingCall(bool video); void createCallConfirm(bool video); void showCallConfirm(); void removeCallConfirm(); void updateCallButtons(bool online, bool audio, bool video = false); void updateMuteMicButton(bool active, bool inputMuted); void updateMuteVolButton(bool active, bool outputMuted); void setAvatar(const QPixmap& img); QSize getAvatarSize() const; void reloadTheme(); // TODO: Remove void addWidget(QWidget* widget, int stretch = 0, Qt::Alignment alignment = Qt::Alignment()); void addLayout(QLayout* layout); void addStretch(); signals: void callTriggered(); void videoCallTriggered(); void micMuteToggle(); void volMuteToggle(); void nameChanged(const QString& name); void callAccepted(); void callRejected(); private slots: void retranslateUi(); void updateButtonsView(); private: Mode mode; MaskablePixmapWidget* avatar; QVBoxLayout* headTextLayout; CroppingLabel* nameLabel; QPushButton* callButton; QPushButton* videoButton; QPushButton* volButton; QPushButton* micButton; CallButtonState callState; CallButtonState videoState; ToolButtonState volState; ToolButtonState micState; std::unique_ptr callConfirm; }; #endif // CHAT_FORM_HEADER qTox/src/widget/circlewidget.cpp000066400000000000000000000163451415623743500172570ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #include #include #include #include #include #include "circlewidget.h" #include "contentdialog.h" #include "friendlistwidget.h" #include "friendwidget.h" #include "widget.h" #include "tool/croppinglabel.h" #include "src/friendlist.h" #include "src/model/friend.h" #include "src/persistence/settings.h" #include "src/widget/form/chatform.h" QHash CircleWidget::circleList; CircleWidget::CircleWidget(FriendListWidget* parent, int id) : CategoryWidget(parent) , id(id) { setName(Settings::getInstance().getCircleName(id), false); circleList[id] = this; connect(nameLabel, &CroppingLabel::editFinished, [this](const QString& newName) { if (!newName.isEmpty()) emit renameRequested(this, newName); }); connect(nameLabel, &CroppingLabel::editRemoved, [this]() { if (isCompact()) nameLabel->minimizeMaximumWidth(); }); setExpanded(Settings::getInstance().getCircleExpanded(id), false); updateStatus(); } CircleWidget::~CircleWidget() { if (circleList[id] == this) circleList.remove(id); } void CircleWidget::editName() { CategoryWidget::editName(); } CircleWidget* CircleWidget::getFromID(int id) { auto circleIt = circleList.find(id); if (circleIt != circleList.end()) return circleIt.value(); return nullptr; } void CircleWidget::contextMenuEvent(QContextMenuEvent* event) { QMenu menu; QAction* renameAction = menu.addAction(tr("Rename circle", "Menu for renaming a circle")); QAction* removeAction = menu.addAction(tr("Remove circle", "Menu for removing a circle")); QAction* openAction = nullptr; if (friendOfflineLayout()->count() + friendOnlineLayout()->count() > 0) openAction = menu.addAction(tr("Open all in new window")); QAction* selectedItem = menu.exec(mapToGlobal(event->pos())); if (selectedItem) { if (selectedItem == renameAction) { editName(); } else if (selectedItem == removeAction) { FriendListWidget* friendList = static_cast(parentWidget()); moveFriendWidgets(friendList); friendList->removeCircleWidget(this); int replacedCircle = Settings::getInstance().removeCircle(id); auto circleReplace = circleList.find(replacedCircle); if (circleReplace != circleList.end()) circleReplace.value()->updateID(id); else assert(true); // This should never happen. circleList.remove(replacedCircle); } else if (selectedItem == openAction) { ContentDialog* dialog = new ContentDialog(); emit newContentDialog(*dialog); for (int i = 0; i < friendOnlineLayout()->count(); ++i) { QWidget* const widget = friendOnlineLayout()->itemAt(i)->widget(); FriendWidget* const friendWidget = qobject_cast(widget); if (friendWidget != nullptr) { friendWidget->activate(); } } for (int i = 0; i < friendOfflineLayout()->count(); ++i) { QWidget* const widget = friendOfflineLayout()->itemAt(i)->widget(); FriendWidget* const friendWidget = qobject_cast(widget); if (friendWidget != nullptr) { friendWidget->activate(); } } dialog->show(); dialog->ensureSplitterVisible(); } } setContainerAttribute(Qt::WA_UnderMouse, false); } void CircleWidget::dragEnterEvent(QDragEnterEvent* event) { if (!event->mimeData()->hasFormat("toxPk")) { return; } ToxPk toxPk(event->mimeData()->data("toxPk")); Friend* f = FriendList::findFriend(toxPk); if (f != nullptr) event->acceptProposedAction(); setContainerAttribute(Qt::WA_UnderMouse, true); // Simulate hover. } void CircleWidget::dragLeaveEvent(QDragLeaveEvent*) { setContainerAttribute(Qt::WA_UnderMouse, false); } void CircleWidget::dropEvent(QDropEvent* event) { setExpanded(true, false); // Check, that the element is dropped from qTox QObject* o = event->source(); FriendWidget* widget = qobject_cast(o); if (!widget) return; if (!event->mimeData()->hasFormat("toxPk")) { return; } // Check, that the user has a friend with the same ToxId ToxPk toxPk{event->mimeData()->data("toxPk")}; Friend* f = FriendList::findFriend(toxPk); if (!f) return; // Save CircleWidget before changing the Id int circleId = Settings::getInstance().getFriendCircleID(toxPk); CircleWidget* circleWidget = getFromID(circleId); addFriendWidget(widget, f->getStatus()); Settings::getInstance().savePersonal(); if (circleWidget != nullptr) { circleWidget->updateStatus(); emit searchCircle(*circleWidget); } setContainerAttribute(Qt::WA_UnderMouse, false); } void CircleWidget::onSetName() { Settings::getInstance().setCircleName(id, getName()); } void CircleWidget::onExpand() { Settings::getInstance().setCircleExpanded(id, isExpanded()); Settings::getInstance().savePersonal(); } void CircleWidget::onAddFriendWidget(FriendWidget* w) { const Friend* f = w->getFriend(); ToxPk toxId = f->getPublicKey(); Settings::getInstance().setFriendCircleID(toxId, id); } void CircleWidget::updateID(int index) { // For when a circle gets destroyed, another takes its id. // This function updates all friends widgets for this new id. if (id == index) { return; } id = index; circleList[id] = this; for (int i = 0; i < friendOnlineLayout()->count(); ++i) { const QWidget* w = friendOnlineLayout()->itemAt(i)->widget(); const FriendWidget* friendWidget = qobject_cast(w); if (friendWidget) { const Friend* f = friendWidget->getFriend(); Settings::getInstance().setFriendCircleID(f->getPublicKey(), id); } } for (int i = 0; i < friendOfflineLayout()->count(); ++i) { const QWidget* w = friendOfflineLayout()->itemAt(i)->widget(); const FriendWidget* friendWidget = qobject_cast(w); if (friendWidget) { const Friend* f = friendWidget->getFriend(); Settings::getInstance().setFriendCircleID(f->getPublicKey(), id); } } } qTox/src/widget/circlewidget.h000066400000000000000000000034221415623743500167140ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CIRCLEWIDGET_H #define CIRCLEWIDGET_H #include "categorywidget.h" class ContentDialog; class CircleWidget final : public CategoryWidget { Q_OBJECT public: explicit CircleWidget(FriendListWidget* parent, int id); ~CircleWidget(); void editName(); static CircleWidget* getFromID(int id); signals: void renameRequested(CircleWidget* circleWidget, const QString& newName); void searchCircle(CircleWidget& circletWidget); void newContentDialog(ContentDialog& contentDialog); protected: void contextMenuEvent(QContextMenuEvent* event) final override; void dragEnterEvent(QDragEnterEvent* event) final override; void dragLeaveEvent(QDragLeaveEvent* event) final override; void dropEvent(QDropEvent* event) final override; private: void onSetName() final override; void onExpand() final override; void onAddFriendWidget(FriendWidget* w) final override; void updateID(int index); static QHash circleList; int id; }; #endif // CIRCLEWIDGET_H qTox/src/widget/contentdialog.cpp000066400000000000000000000504771415623743500174500ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "contentdialog.h" #include "splitterrestorer.h" #include #include #include #include #include #include #include #include "src/core/core.h" #include "src/friendlist.h" #include "src/grouplist.h" #include "src/model/chatroom/friendchatroom.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/model/status.h" #include "src/persistence/settings.h" #include "src/widget/contentlayout.h" #include "src/widget/form/chatform.h" #include "src/widget/friendlistlayout.h" #include "src/widget/friendwidget.h" #include "src/widget/groupwidget.h" #include "src/widget/style.h" #include "src/widget/tool/adjustingscrollarea.h" #include "src/widget/translator.h" #include "src/widget/widget.h" static const int minWidget = 220; static const int minHeight = 220; static const QSize minSize(minHeight, minWidget); static const QSize defaultSize(720, 400); ContentDialog::ContentDialog(QWidget* parent) : ActivateDialog(parent, Qt::Window) , splitter{new QSplitter(this)} , friendLayout{new FriendListLayout(this)} , activeChatroomWidget(nullptr) , videoSurfaceSize(QSize()) , videoCount(0) { const Settings& s = Settings::getInstance(); setStyleSheet(Style::getStylesheet("contentDialog/contentDialog.css")); friendLayout->setMargin(0); friendLayout->setSpacing(0); layouts = {friendLayout->getLayoutOnline(), groupLayout.getLayout(), friendLayout->getLayoutOffline()}; if (s.getGroupchatPosition()) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) layouts.swapItemsAt(0, 1); #else layouts.swap(0, 1); #endif } QWidget* friendWidget = new QWidget(); friendWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); friendWidget->setAutoFillBackground(true); friendWidget->setLayout(friendLayout); onGroupchatPositionChanged(s.getGroupchatPosition()); QScrollArea* friendScroll = new QScrollArea(this); friendScroll->setMinimumWidth(minWidget); friendScroll->setFrameStyle(QFrame::NoFrame); friendScroll->setLayoutDirection(Qt::RightToLeft); friendScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); friendScroll->setStyleSheet(Style::getStylesheet("friendList/friendList.css")); friendScroll->setWidgetResizable(true); friendScroll->setWidget(friendWidget); QWidget* contentWidget = new QWidget(this); contentWidget->setAutoFillBackground(true); contentLayout = new ContentLayout(contentWidget); contentLayout->setMargin(0); contentLayout->setSpacing(0); splitter->addWidget(friendScroll); splitter->addWidget(contentWidget); splitter->setStretchFactor(1, 1); splitter->setCollapsible(1, false); QVBoxLayout* boxLayout = new QVBoxLayout(this); boxLayout->setMargin(0); boxLayout->setSpacing(0); boxLayout->addWidget(splitter); setMinimumSize(minSize); setAttribute(Qt::WA_DeleteOnClose); setObjectName("detached"); QByteArray geometry = s.getDialogGeometry(); if (!geometry.isNull()) { restoreGeometry(geometry); } else { resize(defaultSize); } SplitterRestorer restorer(splitter); restorer.restore(s.getDialogSplitterState(), size()); username = Core::getInstance()->getUsername(); setAcceptDrops(true); new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close())); new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this, SLOT(previousContact())); new QShortcut(Qt::CTRL + Qt::Key_Tab, this, SLOT(nextContact())); new QShortcut(Qt::CTRL + Qt::Key_PageUp, this, SLOT(previousContact())); new QShortcut(Qt::CTRL + Qt::Key_PageDown, this, SLOT(nextContact())); connect(&s, &Settings::groupchatPositionChanged, this, &ContentDialog::onGroupchatPositionChanged); connect(splitter, &QSplitter::splitterMoved, this, &ContentDialog::saveSplitterState); Translator::registerHandler(std::bind(&ContentDialog::retranslateUi, this), this); } ContentDialog::~ContentDialog() { Translator::unregister(this); } void ContentDialog::closeEvent(QCloseEvent* event) { emit willClose(); event->accept(); } FriendWidget* ContentDialog::addFriend(std::shared_ptr chatroom, GenericChatForm* form) { const auto compact = Settings::getInstance().getCompactLayout(); auto frnd = chatroom->getFriend(); const auto& friendPk = frnd->getPublicKey(); auto friendWidget = new FriendWidget(chatroom, compact); emit connectFriendWidget(*friendWidget); contactWidgets[friendPk] = friendWidget; friendLayout->addFriendWidget(friendWidget, frnd->getStatus()); contactChatForms[friendPk] = form; // TODO(sudden6): move this connection to the Friend::displayedNameChanged signal connect(frnd, &Friend::aliasChanged, this, &ContentDialog::updateFriendWidget); connect(frnd, &Friend::statusMessageChanged, this, &ContentDialog::setStatusMessage); connect(friendWidget, &FriendWidget::chatroomWidgetClicked, this, &ContentDialog::activate); // FIXME: emit should be removed emit friendWidget->chatroomWidgetClicked(friendWidget); return friendWidget; } GroupWidget* ContentDialog::addGroup(std::shared_ptr chatroom, GenericChatForm* form) { const auto g = chatroom->getGroup(); const auto& groupId = g->getPersistentId(); const auto compact = Settings::getInstance().getCompactLayout(); auto groupWidget = new GroupWidget(chatroom, compact); contactWidgets[groupId] = groupWidget; groupLayout.addSortedWidget(groupWidget); contactChatForms[groupId] = form; connect(groupWidget, &GroupWidget::chatroomWidgetClicked, this, &ContentDialog::activate); // FIXME: emit should be removed emit groupWidget->chatroomWidgetClicked(groupWidget); return groupWidget; } void ContentDialog::removeFriend(const ToxPk& friendPk) { auto chatroomWidget = qobject_cast(contactWidgets[friendPk]); disconnect(chatroomWidget->getFriend(), &Friend::aliasChanged, this, &ContentDialog::updateFriendWidget); // Need to find replacement to show here instead. if (activeChatroomWidget == chatroomWidget) { cycleContacts(/* forward = */ true, /* inverse = */ false); } friendLayout->removeFriendWidget(chatroomWidget, Status::Status::Offline); friendLayout->removeFriendWidget(chatroomWidget, Status::Status::Online); chatroomWidget->deleteLater(); if (chatroomCount() == 0) { contentLayout->clear(); activeChatroomWidget = nullptr; deleteLater(); } else { update(); } contactWidgets.remove(friendPk); contactChatForms.remove(friendPk); closeIfEmpty(); } void ContentDialog::removeGroup(const GroupId& groupId) { auto chatroomWidget = qobject_cast(contactWidgets[groupId]); // Need to find replacement to show here instead. if (activeChatroomWidget == chatroomWidget) { cycleContacts(true, false); } groupLayout.removeSortedWidget(chatroomWidget); chatroomWidget->deleteLater(); if (chatroomCount() == 0) { contentLayout->clear(); activeChatroomWidget = nullptr; deleteLater(); } else { update(); } contactWidgets.remove(groupId); contactChatForms.remove(groupId); closeIfEmpty(); } void ContentDialog::closeIfEmpty() { if (contactWidgets.isEmpty()) { close(); } } int ContentDialog::chatroomCount() const { return friendLayout->friendTotalCount() + groupLayout.getLayout()->count(); } void ContentDialog::ensureSplitterVisible() { if (splitter->sizes().at(0) == 0) { splitter->setSizes({1, 1}); } update(); } /** * @brief Get current layout and index of current wiget in it. * Current layout -- layout contains activated widget. * * @param[out] layout Current layout * @return Index of current widget in current layout. */ int ContentDialog::getCurrentLayout(QLayout*& layout) { layout = friendLayout->getLayoutOnline(); int index = friendLayout->indexOfFriendWidget(activeChatroomWidget, true); if (index != -1) { return index; } layout = friendLayout->getLayoutOffline(); index = friendLayout->indexOfFriendWidget(activeChatroomWidget, false); if (index != -1) { return index; } layout = groupLayout.getLayout(); index = groupLayout.indexOfSortedWidget(activeChatroomWidget); if (index != -1) { return index; } layout = nullptr; return -1; } /** * @brief Activate next/previous contact. * @param forward If true, activate next contace, previous otherwise. * @param inverse ??? TODO: Add docs. */ void ContentDialog::cycleContacts(bool forward, bool inverse) { QLayout* currentLayout; int index = getCurrentLayout(currentLayout); if (!currentLayout || index == -1) { return; } if (!inverse && index == currentLayout->count() - 1) { bool groupsOnTop = Settings::getInstance().getGroupchatPosition(); bool offlineEmpty = friendLayout->getLayoutOffline()->isEmpty(); bool onlineEmpty = friendLayout->getLayoutOnline()->isEmpty(); bool groupsEmpty = groupLayout.getLayout()->isEmpty(); bool isCurOffline = currentLayout == friendLayout->getLayoutOffline(); bool isCurOnline = currentLayout == friendLayout->getLayoutOnline(); bool isCurGroup = currentLayout == groupLayout.getLayout(); bool nextIsEmpty = (isCurOnline && offlineEmpty && (groupsEmpty || groupsOnTop)) || (isCurGroup && offlineEmpty && (onlineEmpty || !groupsOnTop)) || (isCurOffline); if (nextIsEmpty) { forward = !forward; } } index += forward ? 1 : -1; // If goes out of the layout, then go to the next and skip empty. This loop goes more // then 1 time, because for empty layout index will be out of interval (0 < 0 || 0 >= 0) while (index < 0 || index >= currentLayout->count()) { int oldCount = currentLayout->count(); currentLayout = nextLayout(currentLayout, forward); int newCount = currentLayout->count(); if (index < 0) { index = newCount - 1; } else if (index >= oldCount) { index = 0; } } QWidget* widget = currentLayout->itemAt(index)->widget(); GenericChatroomWidget* chatWidget = qobject_cast(widget); if (chatWidget && chatWidget != activeChatroomWidget) { // FIXME: emit should be removed emit chatWidget->chatroomWidgetClicked(chatWidget); } } void ContentDialog::onVideoShow(QSize size) { ++videoCount; if (videoCount > 1) { return; } videoSurfaceSize = size; QSize minSize = minimumSize(); setMinimumSize(minSize + videoSurfaceSize); } void ContentDialog::onVideoHide() { videoCount--; if (videoCount > 0) { return; } QSize minSize = minimumSize(); setMinimumSize(minSize - videoSurfaceSize); videoSurfaceSize = QSize(); } /** * @brief Update window title and icon. */ void ContentDialog::updateTitleAndStatusIcon() { if (!activeChatroomWidget) { setWindowTitle(username); return; } setWindowTitle(activeChatroomWidget->getTitle() + QStringLiteral(" - ") + username); bool isGroupchat = activeChatroomWidget->getGroup() != nullptr; if (isGroupchat) { setWindowIcon(QIcon(":/img/group.svg")); return; } Status::Status currentStatus = activeChatroomWidget->getFriend()->getStatus(); setWindowIcon(QIcon{Status::getIconPath(currentStatus)}); } /** * @brief Update layouts order according to settings. * @param groupOnTop If true move groupchat layout on the top. Move under online otherwise. */ void ContentDialog::reorderLayouts(bool newGroupOnTop) { bool oldGroupOnTop = layouts.first() == groupLayout.getLayout(); if (newGroupOnTop != oldGroupOnTop) { // Kriby: Maintain backwards compatibility #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) layouts.swapItemsAt(0, 1); #else layouts.swap(0, 1); #endif } } void ContentDialog::previousContact() { cycleContacts(false); } /** * @brief Enable next contact. */ void ContentDialog::nextContact() { cycleContacts(true); } /** * @brief Update username to show in the title. * @param newName New name to display. */ void ContentDialog::setUsername(const QString& newName) { username = newName; updateTitleAndStatusIcon(); } bool ContentDialog::event(QEvent* event) { switch (event->type()) { case QEvent::WindowActivate: if (activeChatroomWidget) { activeChatroomWidget->resetEventFlags(); activeChatroomWidget->updateStatusLight(); updateTitleAndStatusIcon(); const Friend* frnd = activeChatroomWidget->getFriend(); Group* group = activeChatroomWidget->getGroup(); if (frnd) { emit friendDialogShown(frnd); } else if (group) { emit groupDialogShown(group); } } emit activated(); default: break; } return ActivateDialog::event(event); } void ContentDialog::dragEnterEvent(QDragEnterEvent* event) { QObject* o = event->source(); FriendWidget* frnd = qobject_cast(o); GroupWidget* group = qobject_cast(o); if (frnd) { assert(event->mimeData()->hasFormat("toxPk")); ToxPk toxPk{event->mimeData()->data("toxPk")}; Friend* contact = FriendList::findFriend(toxPk); if (!contact) { return; } ToxPk friendId = contact->getPublicKey(); // If friend is already in a dialog then you can't drop friend where it already is. if (!hasContact(friendId)) { event->acceptProposedAction(); } } else if (group) { assert(event->mimeData()->hasFormat("groupId")); GroupId groupId = GroupId{event->mimeData()->data("groupId")}; Group* contact = GroupList::findGroup(groupId); if (!contact) { return; } if (!hasContact(groupId)) { event->acceptProposedAction(); } } } void ContentDialog::dropEvent(QDropEvent* event) { QObject* o = event->source(); FriendWidget* frnd = qobject_cast(o); GroupWidget* group = qobject_cast(o); if (frnd) { assert(event->mimeData()->hasFormat("toxPk")); const ToxPk toxId(event->mimeData()->data("toxPk")); Friend* contact = FriendList::findFriend(toxId); if (!contact) { return; } emit addFriendDialog(contact, this); ensureSplitterVisible(); } else if (group) { assert(event->mimeData()->hasFormat("groupId")); const GroupId groupId(event->mimeData()->data("groupId")); Group* contact = GroupList::findGroup(groupId); if (!contact) { return; } emit addGroupDialog(contact, this); ensureSplitterVisible(); } } void ContentDialog::changeEvent(QEvent* event) { QWidget::changeEvent(event); if (event->type() == QEvent::ActivationChange) { if (isActiveWindow()) { emit activated(); } } } void ContentDialog::resizeEvent(QResizeEvent* event) { saveDialogGeometry(); QDialog::resizeEvent(event); } void ContentDialog::moveEvent(QMoveEvent* event) { saveDialogGeometry(); QDialog::moveEvent(event); } void ContentDialog::keyPressEvent(QKeyEvent* event) { // Ignore escape keyboard shortcut. if (event->key() != Qt::Key_Escape) { QDialog::keyPressEvent(event); } } void ContentDialog::focusContact(const ContactId& contactId) { focusCommon(contactId, contactWidgets); } void ContentDialog::focusCommon(const ContactId& id, QHash list) { auto it = list.find(id); if (it == list.end()) { return; } activate(*it); } /** * @brief Show ContentDialog, activate chatroom widget. * @param widget Widget which should be activated. */ void ContentDialog::activate(GenericChatroomWidget* widget) { // If we clicked on the currently active widget, don't reload and relayout everything if (activeChatroomWidget == widget) { return; } contentLayout->clear(); if (activeChatroomWidget) { activeChatroomWidget->setAsInactiveChatroom(); } activeChatroomWidget = widget; const Contact* contact = widget->getContact(); contactChatForms[contact->getPersistentId()]->show(contentLayout); widget->setAsActiveChatroom(); widget->resetEventFlags(); widget->updateStatusLight(); updateTitleAndStatusIcon(); } void ContentDialog::updateFriendStatus(const ToxPk& friendPk, Status::Status status) { auto widget = qobject_cast(contactWidgets.value(friendPk)); addFriendWidget(widget, status); } void ContentDialog::updateContactStatusLight(const ContactId& contactId) { auto widget = contactWidgets.value(contactId); if (widget != nullptr) { widget->updateStatusLight(); } } bool ContentDialog::isContactActive(const ContactId& contactId) const { auto widget = contactWidgets.value(contactId); if (widget == nullptr) { return false; } return widget->isActive(); } // TODO: Connect to widget directly void ContentDialog::setStatusMessage(const ToxPk& friendPk, const QString& message) { auto widget = contactWidgets.value(friendPk); if (widget != nullptr) { widget->setStatusMsg(message); } } /** * @brief Update friend widget name and position. * @param friendId Friend Id. * @param alias Alias to display on widget. */ void ContentDialog::updateFriendWidget(const ToxPk& friendPk, QString alias) { Friend* f = FriendList::findFriend(friendPk); FriendWidget* friendWidget = qobject_cast(contactWidgets[friendPk]); Status::Status status = f->getStatus(); friendLayout->addFriendWidget(friendWidget, status); } /** * @brief Handler of `groupchatPositionChanged` action. * Move group layout on the top or on the buttom. * * @param top If true, move group layout on the top, false otherwise. */ void ContentDialog::onGroupchatPositionChanged(bool top) { friendLayout->removeItem(groupLayout.getLayout()); friendLayout->insertLayout(top ? 0 : 1, groupLayout.getLayout()); } /** * @brief Retranslate all elements in the form. */ void ContentDialog::retranslateUi() { updateTitleAndStatusIcon(); } /** * @brief Save size of dialog window. */ void ContentDialog::saveDialogGeometry() { Settings::getInstance().setDialogGeometry(saveGeometry()); } /** * @brief Save state of splitter between dialog and dialog list. */ void ContentDialog::saveSplitterState() { Settings::getInstance().setDialogSplitterState(splitter->saveState()); } bool ContentDialog::hasContact(const ContactId& contactId) const { return contactWidgets.contains(contactId); } /** * @brief Find the next or previous layout in layout list. * @param layout Current layout. * @param forward If true, move forward, backward othwerwise. * @return Next/previous layout. */ QLayout* ContentDialog::nextLayout(QLayout* layout, bool forward) const { int index = layouts.indexOf(layout); if (index == -1) { return nullptr; } int next = forward ? index + 1 : index - 1; size_t size = layouts.size(); next = (next + size) % size; return layouts[next]; } void ContentDialog::addFriendWidget(FriendWidget* widget, Status::Status status) { friendLayout->addFriendWidget(widget, status); } bool ContentDialog::isActiveWidget(GenericChatroomWidget* widget) { return activeChatroomWidget == widget; } qTox/src/widget/contentdialog.h000066400000000000000000000105501415623743500171010ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CONTENTDIALOG_H #define CONTENTDIALOG_H #include "src/core/groupid.h" #include "src/core/toxpk.h" #include "src/model/dialogs/idialogs.h" #include "src/model/status.h" #include "src/widget/genericchatitemlayout.h" #include "src/widget/tool/activatedialog.h" #include template class QHash; class ContentLayout; class Friend; class FriendChatroom; class FriendListLayout; class FriendWidget; class GenericChatForm; class GenericChatroomWidget; class Group; class GroupChatroom; class GroupWidget; class QCloseEvent; class QSplitter; class ContentDialog : public ActivateDialog, public IDialogs { Q_OBJECT public: explicit ContentDialog(QWidget* parent = nullptr); ~ContentDialog() override; FriendWidget* addFriend(std::shared_ptr chatroom, GenericChatForm* form); GroupWidget* addGroup(std::shared_ptr chatroom, GenericChatForm* form); void removeFriend(const ToxPk& friendPk) override; void removeGroup(const GroupId& groupId) override; int chatroomCount() const override; void ensureSplitterVisible(); void updateTitleAndStatusIcon(); void cycleContacts(bool forward, bool loop = true); void onVideoShow(QSize size); void onVideoHide(); void addFriendWidget(FriendWidget* widget, Status::Status status); bool isActiveWidget(GenericChatroomWidget* widget); bool hasContact(const ContactId& contactId) const override; bool isContactActive(const ContactId& contactId) const override; void focusContact(const ContactId& friendPk); void updateFriendStatus(const ToxPk& friendPk, Status::Status status); void updateContactStatusLight(const ContactId& contactId); void setStatusMessage(const ToxPk& friendPk, const QString& message); signals: void friendDialogShown(const Friend* f); void groupDialogShown(Group* g); void addFriendDialog(Friend* frnd, ContentDialog* contentDialog); void addGroupDialog(Group* group, ContentDialog* contentDialog); void activated(); void willClose(); void connectFriendWidget(FriendWidget& friendWidget); public slots: void reorderLayouts(bool newGroupOnTop); void previousContact(); void nextContact(); void setUsername(const QString& newName); protected: bool event(QEvent* event) final override; void dragEnterEvent(QDragEnterEvent* event) final override; void dropEvent(QDropEvent* event) final override; void changeEvent(QEvent* event) override; void resizeEvent(QResizeEvent* event) override; void moveEvent(QMoveEvent* event) override; void keyPressEvent(QKeyEvent* event) override; public slots: void activate(GenericChatroomWidget* widget); private slots: void updateFriendWidget(const ToxPk& friendPk, QString alias); void onGroupchatPositionChanged(bool top); private: void closeIfEmpty(); void closeEvent(QCloseEvent* event) override; void retranslateUi(); void saveDialogGeometry(); void saveSplitterState(); QLayout* nextLayout(QLayout* layout, bool forward) const; int getCurrentLayout(QLayout*& layout); void focusCommon(const ContactId& id, QHash list); private: QList layouts; QSplitter* splitter; FriendListLayout* friendLayout; GenericChatItemLayout groupLayout; ContentLayout* contentLayout; GenericChatroomWidget* activeChatroomWidget; QSize videoSurfaceSize; int videoCount; QHash contactWidgets; QHash contactChatForms; QString username; }; #endif // CONTENTDIALOG_H qTox/src/widget/contentdialogmanager.cpp000066400000000000000000000135331415623743500207730ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "contentdialogmanager.h" #include "src/friendlist.h" #include "src/grouplist.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/widget/friendwidget.h" #include "src/widget/groupwidget.h" #include namespace { void removeDialog(ContentDialog* dialog, QHash& dialogs) { for (auto it = dialogs.begin(); it != dialogs.end();) { if (*it == dialog) { it = dialogs.erase(it); } else { ++it; } } } } // namespace ContentDialogManager* ContentDialogManager::instance; ContentDialog* ContentDialogManager::current() { return currentDialog; } bool ContentDialogManager::contactWidgetExists(const ContactId& contactId) { const auto dialog = contactDialogs.value(contactId, nullptr); if (dialog == nullptr) { return false; } return dialog->hasContact(contactId); } FriendWidget* ContentDialogManager::addFriendToDialog(ContentDialog* dialog, std::shared_ptr chatroom, GenericChatForm* form) { auto friendWidget = dialog->addFriend(chatroom, form); const auto& friendPk = friendWidget->getFriend()->getPublicKey(); ContentDialog* lastDialog = getFriendDialog(friendPk); if (lastDialog) { lastDialog->removeFriend(friendPk); } contactDialogs[friendPk] = dialog; return friendWidget; } GroupWidget* ContentDialogManager::addGroupToDialog(ContentDialog* dialog, std::shared_ptr chatroom, GenericChatForm* form) { auto groupWidget = dialog->addGroup(chatroom, form); const auto& groupId = groupWidget->getGroup()->getPersistentId(); ContentDialog* lastDialog = getGroupDialog(groupId); if (lastDialog) { lastDialog->removeGroup(groupId); } contactDialogs[groupId] = dialog; return groupWidget; } void ContentDialogManager::focusContact(const ContactId& contactId) { auto dialog = focusDialog(contactId, contactDialogs); if (dialog != nullptr) { dialog->focusContact(contactId); } } /** * @brief Focus the dialog if it exists. * @param id User Id. * @param list List with dialogs * @return ContentDialog if found, nullptr otherwise */ ContentDialog* ContentDialogManager::focusDialog(const ContactId& id, const QHash& list) { auto iter = list.find(id); if (iter == list.end()) { return nullptr; } ContentDialog* dialog = *iter; if (dialog->windowState() & Qt::WindowMinimized) { dialog->showNormal(); } dialog->raise(); dialog->activateWindow(); return dialog; } void ContentDialogManager::updateFriendStatus(const ToxPk& friendPk) { auto dialog = contactDialogs.value(friendPk); if (dialog == nullptr) { return; } dialog->updateContactStatusLight(friendPk); if (dialog->isContactActive(friendPk)) { dialog->updateTitleAndStatusIcon(); } Friend* f = FriendList::findFriend(friendPk); dialog->updateFriendStatus(friendPk, f->getStatus()); } void ContentDialogManager::updateGroupStatus(const GroupId& groupId) { auto dialog = contactDialogs.value(groupId); if (dialog == nullptr) { return; } dialog->updateContactStatusLight(groupId); if (dialog->isContactActive(groupId)) { dialog->updateTitleAndStatusIcon(); } } bool ContentDialogManager::isContactActive(const ContactId& contactId) { const auto dialog = contactDialogs.value(contactId); if (dialog == nullptr) { return false; } return dialog->isContactActive(contactId); } ContentDialog* ContentDialogManager::getFriendDialog(const ToxPk& friendPk) const { return contactDialogs.value(friendPk); } ContentDialog* ContentDialogManager::getGroupDialog(const GroupId& groupId) const { return contactDialogs.value(groupId); } ContentDialogManager* ContentDialogManager::getInstance() { if (instance == nullptr) { instance = new ContentDialogManager(); } return instance; } void ContentDialogManager::addContentDialog(ContentDialog& dialog) { currentDialog = &dialog; connect(&dialog, &ContentDialog::willClose, this, &ContentDialogManager::onDialogClose); connect(&dialog, &ContentDialog::activated, this, &ContentDialogManager::onDialogActivate); } void ContentDialogManager::onDialogActivate() { ContentDialog* dialog = qobject_cast(sender()); currentDialog = dialog; } void ContentDialogManager::onDialogClose() { ContentDialog* dialog = qobject_cast(sender()); if (currentDialog == dialog) { currentDialog = nullptr; } removeDialog(dialog, contactDialogs); } IDialogs* ContentDialogManager::getFriendDialogs(const ToxPk& friendPk) const { return getFriendDialog(friendPk); } IDialogs* ContentDialogManager::getGroupDialogs(const GroupId& groupId) const { return getGroupDialog(groupId); } qTox/src/widget/contentdialogmanager.h000066400000000000000000000047521415623743500204430ustar00rootroot00000000000000/* Copyright © 2018-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef _CONTENT_DIALOG_MANAGER_H_ #define _CONTENT_DIALOG_MANAGER_H_ #include "contentdialog.h" #include "src/core/contactid.h" #include "src/core/groupid.h" #include "src/core/toxpk.h" #include "src/model/dialogs/idialogsmanager.h" #include /** * @breaf Manage all content dialogs */ class ContentDialogManager : public QObject, public IDialogsManager { Q_OBJECT public: ContentDialog* current(); bool contactWidgetExists(const ContactId& groupId); void focusContact(const ContactId& contactId); void updateFriendStatus(const ToxPk& friendPk); void updateGroupStatus(const GroupId& friendPk); bool isContactActive(const ContactId& contactId); ContentDialog* getFriendDialog(const ToxPk& friendPk) const; ContentDialog* getGroupDialog(const GroupId& friendPk) const; IDialogs* getFriendDialogs(const ToxPk& friendPk) const; IDialogs* getGroupDialogs(const GroupId& groupId) const; FriendWidget* addFriendToDialog(ContentDialog* dialog, std::shared_ptr chatroom, GenericChatForm* form); GroupWidget* addGroupToDialog(ContentDialog* dialog, std::shared_ptr chatroom, GenericChatForm* form); void addContentDialog(ContentDialog& dialog); static ContentDialogManager* getInstance(); private slots: void onDialogClose(); void onDialogActivate(); private: ContentDialog* focusDialog(const ContactId& id, const QHash& list); ContentDialog* currentDialog = nullptr; QHash contactDialogs; static ContentDialogManager* instance; }; #endif // _CONTENT_DIALOG_MANAGER_H_ qTox/src/widget/contentlayout.cpp000066400000000000000000000103601415623743500175110ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "contentlayout.h" #include "style.h" #include "src/persistence/settings.h" #include #include ContentLayout::ContentLayout() : QVBoxLayout() { init(); } ContentLayout::ContentLayout(QWidget* parent) : QVBoxLayout(parent) { init(); QPalette palette = parent->palette(); palette.setBrush(QPalette::WindowText, QColor(0, 0, 0)); palette.setBrush(QPalette::Button, QColor(255, 255, 255)); palette.setBrush(QPalette::Light, QColor(255, 255, 255)); palette.setBrush(QPalette::Midlight, QColor(255, 255, 255)); palette.setBrush(QPalette::Dark, QColor(127, 127, 127)); palette.setBrush(QPalette::Mid, QColor(170, 170, 170)); palette.setBrush(QPalette::Text, QColor(0, 0, 0)); palette.setBrush(QPalette::BrightText, QColor(255, 255, 255)); palette.setBrush(QPalette::ButtonText, QColor(0, 0, 0)); palette.setBrush(QPalette::Base, QColor(255, 255, 255)); palette.setBrush(QPalette::Window, QColor(255, 255, 255)); palette.setBrush(QPalette::Shadow, QColor(0, 0, 0)); palette.setBrush(QPalette::AlternateBase, QColor(255, 255, 255)); palette.setBrush(QPalette::ToolTipBase, QColor(255, 255, 220)); palette.setBrush(QPalette::ToolTipText, QColor(0, 0, 0)); palette.setBrush(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127)); palette.setBrush(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); palette.setBrush(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127)); parent->setPalette(palette); } ContentLayout::~ContentLayout() { clear(); mainHead->deleteLater(); mainContent->deleteLater(); } void ContentLayout::reloadTheme() { #ifndef Q_OS_MAC mainHead->setStyleSheet(Style::getStylesheet("settings/mainHead.css")); mainContent->setStyleSheet(Style::getStylesheet("window/general.css")); #endif } void ContentLayout::clear() { QLayoutItem* item; while ((item = mainHead->layout()->takeAt(0)) != nullptr) { item->widget()->hide(); item->widget()->setParent(nullptr); delete item; } while ((item = mainContent->layout()->takeAt(0)) != nullptr) { item->widget()->hide(); item->widget()->setParent(nullptr); delete item; } } void ContentLayout::init() { setMargin(0); setSpacing(0); mainHead = new QWidget(); mainHead->setLayout(new QVBoxLayout); mainHead->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); mainHead->layout()->setMargin(0); mainHead->layout()->setSpacing(0); mainHead->setMouseTracking(true); mainHLine.setFrameShape(QFrame::HLine); mainHLine.setFrameShadow(QFrame::Plain); QPalette palette = mainHLine.palette(); palette.setBrush(QPalette::WindowText, QBrush(QColor(193, 193, 193))); mainHLine.setPalette(palette); mainContent = new QWidget(); mainContent->setLayout(new QVBoxLayout); mainContent->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); if (QStyleFactory::keys().contains(Settings::getInstance().getStyle()) && Settings::getInstance().getStyle() != "None") { mainHead->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); mainContent->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); } reloadTheme(); mainHLineLayout.addSpacing(4); mainHLineLayout.addWidget(&mainHLine); mainHLineLayout.addSpacing(5); addWidget(mainHead); addLayout(&mainHLineLayout); addWidget(mainContent); } qTox/src/widget/contentlayout.h000066400000000000000000000022631415623743500171610ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CONTENTLAYOUT_H #define CONTENTLAYOUT_H #include #include class ContentLayout : public QVBoxLayout { public: ContentLayout(); explicit ContentLayout(QWidget* parent); ~ContentLayout(); void reloadTheme(); void clear(); QFrame mainHLine; QHBoxLayout mainHLineLayout; QWidget* mainContent; QWidget* mainHead; private: void init(); }; #endif // CONTENTLAYOUT_H qTox/src/widget/emoticonswidget.cpp000066400000000000000000000134501415623743500200100ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "emoticonswidget.h" #include "src/persistence/settings.h" #include "src/persistence/smileypack.h" #include "src/widget/style.h" #include #include #include #include #include #include #include EmoticonsWidget::EmoticonsWidget(QWidget* parent) : QMenu(parent) { setStyleSheet(Style::getStylesheet("emoticonWidget/emoticonWidget.css")); setLayout(&layout); layout.addWidget(&stack); QWidget* pageButtonsContainer = new QWidget; QHBoxLayout* buttonLayout = new QHBoxLayout; pageButtonsContainer->setLayout(buttonLayout); layout.addWidget(pageButtonsContainer); const int maxCols = 8; const int maxRows = 8; const int itemsPerPage = maxRows * maxCols; const QList& emoticons = SmileyPack::getInstance().getEmoticons(); int itemCount = emoticons.size(); int pageCount = ceil(float(itemCount) / float(itemsPerPage)); int currPage = 0; int currItem = 0; int row = 0; int col = 0; // respect configured emoticon size const int px = Settings::getInstance().getEmojiFontPointSize(); const QSize size(px, px); // create pages buttonLayout->addStretch(); for (int i = 0; i < pageCount; ++i) { QGridLayout* pageLayout = new QGridLayout; pageLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), maxRows, 0); pageLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, maxCols); QWidget* page = new QWidget; page->setLayout(pageLayout); stack.addWidget(page); // page buttons are only needed if there is more than 1 page if (pageCount > 1) { QRadioButton* pageButton = new QRadioButton; pageButton->setProperty("pageIndex", i); pageButton->setCursor(Qt::PointingHandCursor); pageButton->setChecked(i == 0); buttonLayout->addWidget(pageButton); connect(pageButton, &QRadioButton::clicked, this, &EmoticonsWidget::onPageButtonClicked); } } buttonLayout->addStretch(); SmileyPack& smileyPack = SmileyPack::getInstance(); for (const QStringList& set : emoticons) { QPushButton* button = new QPushButton; std::shared_ptr icon = smileyPack.getAsIcon(set[0]); emoticonsIcons.append(icon); button->setIcon(icon->pixmap(size)); button->setToolTip(set.join(" ")); button->setProperty("sequence", set[0]); button->setCursor(Qt::PointingHandCursor); button->setFlat(true); button->setIconSize(size); button->setFixedSize(size); connect(button, &QPushButton::clicked, this, &EmoticonsWidget::onSmileyClicked); qobject_cast(stack.widget(currPage)->layout())->addWidget(button, row, col); ++col; ++currItem; // next row if (col >= maxCols) { col = 0; ++row; } // next page if (currItem >= itemsPerPage) { row = 0; currItem = 0; ++currPage; } } // calculates sizeHint layout.activate(); } void EmoticonsWidget::onSmileyClicked() { // emit insert emoticon QWidget* sender = qobject_cast(QObject::sender()); if (sender) { QString sequence = sender->property("sequence").toString().replace("<", "<").replace(">", ">"); emit insertEmoticon(sequence); } } void EmoticonsWidget::onPageButtonClicked() { QWidget* sender = qobject_cast(QObject::sender()); if (sender) { int page = sender->property("pageIndex").toInt(); stack.setCurrentIndex(page); } } QSize EmoticonsWidget::sizeHint() const { return layout.sizeHint(); } void EmoticonsWidget::mouseReleaseEvent(QMouseEvent* ev) { if (!rect().contains(ev->pos())) hide(); } void EmoticonsWidget::mousePressEvent(QMouseEvent*) { } void EmoticonsWidget::wheelEvent(QWheelEvent* e) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) const bool vertical = qAbs(e->angleDelta().y()) >= qAbs(e->angleDelta().x()); const int delta = vertical ? e->angleDelta().y() : e->angleDelta().x(); if (vertical) { if (delta < 0) { #else if (e->orientation() == Qt::Vertical) { if (e->delta() < 0) { #endif stack.setCurrentIndex(stack.currentIndex() + 1); } else { stack.setCurrentIndex(stack.currentIndex() - 1); } emit PageButtonsUpdate(); } } void EmoticonsWidget::PageButtonsUpdate() { QList pageButtons = this->findChildren(QString()); foreach (QRadioButton* t_pageButton, pageButtons) { if (t_pageButton->property("pageIndex").toInt() == stack.currentIndex()) t_pageButton->setChecked(true); else t_pageButton->setChecked(false); } } void EmoticonsWidget::keyPressEvent(QKeyEvent* e) { Q_UNUSED(e) hide(); } qTox/src/widget/emoticonswidget.h000066400000000000000000000031471415623743500174570ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef EMOTICONSWIDGET_H #define EMOTICONSWIDGET_H #include #include #include #include #include class QIcon; class EmoticonsWidget : public QMenu { Q_OBJECT public: explicit EmoticonsWidget(QWidget* parent = nullptr); signals: void insertEmoticon(QString str); private slots: void onSmileyClicked(); void onPageButtonClicked(); void PageButtonsUpdate(); protected: void mouseReleaseEvent(QMouseEvent* ev) final override; void mousePressEvent(QMouseEvent* ev) final override; void wheelEvent(QWheelEvent* event) final override; void keyPressEvent(QKeyEvent* e) final override; private: QStackedWidget stack; QVBoxLayout layout; QList> emoticonsIcons; public: QSize sizeHint() const override; }; #endif // EMOTICONSWIDGET_H qTox/src/widget/flowlayout.cpp000066400000000000000000000131071415623743500170100ustar00rootroot00000000000000/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "flowlayout.h" FlowLayout::FlowLayout(QWidget* parent, int margin, int hSpacing, int vSpacing) : QLayout(parent) , m_hSpace(hSpacing) , m_vSpace(vSpacing) { setContentsMargins(margin, margin, margin, margin); } FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) : m_hSpace(hSpacing) , m_vSpace(vSpacing) { setContentsMargins(margin, margin, margin, margin); } FlowLayout::~FlowLayout() { QLayoutItem* item; while ((item = takeAt(0))) delete item; } void FlowLayout::addItem(QLayoutItem* item) { itemList.append(item); } int FlowLayout::horizontalSpacing() const { if (m_hSpace >= 0) return m_hSpace; else return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); } int FlowLayout::verticalSpacing() const { if (m_vSpace >= 0) return m_vSpace; else return smartSpacing(QStyle::PM_LayoutVerticalSpacing); } int FlowLayout::count() const { return itemList.size(); } QLayoutItem* FlowLayout::itemAt(int index) const { return itemList.value(index); } QLayoutItem* FlowLayout::takeAt(int index) { if (index >= 0 && index < itemList.size()) return itemList.takeAt(index); else return nullptr; } Qt::Orientations FlowLayout::expandingDirections() const { return {}; } bool FlowLayout::hasHeightForWidth() const { return true; } int FlowLayout::heightForWidth(int width) const { int height = doLayout(QRect(0, 0, width, 0), true); return height; } void FlowLayout::setGeometry(const QRect& rect) { QLayout::setGeometry(rect); doLayout(rect, false); } QSize FlowLayout::sizeHint() const { return minimumSize(); } QSize FlowLayout::minimumSize() const { QSize size; foreach (QLayoutItem* item, itemList) size = size.expandedTo(item->minimumSize()); size += QSize(2 * margin(), 2 * margin()); return size; } int FlowLayout::doLayout(const QRect& rect, bool testOnly) const { int left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); int x = effectiveRect.x(); int y = effectiveRect.y(); int lineHeight = 0; QLayoutItem* item; foreach (item, itemList) { QWidget* wid = item->widget(); int spaceX = horizontalSpacing(); if (spaceX == -1) spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); int spaceY = verticalSpacing(); if (spaceY == -1) spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); int nextX = x + item->sizeHint().width() + spaceX; if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { x = effectiveRect.x(); y = y + lineHeight + spaceY; nextX = x + item->sizeHint().width() + spaceX; lineHeight = 0; } if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); x = nextX; lineHeight = qMax(lineHeight, item->sizeHint().height()); } return y + lineHeight - rect.y() + bottom; } int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const { QObject* parent = this->parent(); if (!parent) { return -1; } else if (parent->isWidgetType()) { QWidget* pw = static_cast(parent); return pw->style()->pixelMetric(pm, nullptr, pw); } else { return static_cast(parent)->spacing(); } } qTox/src/widget/flowlayout.h000066400000000000000000000056421415623743500164620ustar00rootroot00000000000000/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names ** of its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef FLOWLAYOUT_H #define FLOWLAYOUT_H #include #include #include class FlowLayout : public QLayout { public: explicit FlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); ~FlowLayout(); void addItem(QLayoutItem* item); int horizontalSpacing() const; int verticalSpacing() const; Qt::Orientations expandingDirections() const; bool hasHeightForWidth() const; int heightForWidth(int) const; int count() const; QLayoutItem* itemAt(int index) const; QSize minimumSize() const; void setGeometry(const QRect& rect); QSize sizeHint() const; QLayoutItem* takeAt(int index); private: int doLayout(const QRect& rect, bool testOnly) const; int smartSpacing(QStyle::PixelMetric pm) const; QList itemList; int m_hSpace; int m_vSpace; }; #endif // FLOWLAYOUT_H qTox/src/widget/form/000077500000000000000000000000001415623743500150405ustar00rootroot00000000000000qTox/src/widget/form/addfriendform.cpp000066400000000000000000000356601415623743500203620ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "addfriendform.h" #include "src/core/core.h" #include "src/nexus.h" #include "src/persistence/settings.h" #include "src/widget/contentlayout.h" #include "src/widget/gui.h" #include "src/widget/tool/croppinglabel.h" #include "src/widget/style.h" #include "src/widget/translator.h" #include #include #include #include #include #include #include #include #include #include #include namespace { QString getToxId(const QString& id) { const QString toxUriPrefix{"tox:"}; QString strippedId = id.trimmed(); if (strippedId.startsWith(toxUriPrefix)) { strippedId.remove(0, toxUriPrefix.length()); } return strippedId; } bool checkIsValidId(const QString& id) { return ToxId::isToxId(id); } } /** * @var QString AddFriendForm::lastUsername * @brief Cached username so we can retranslate the invite message */ AddFriendForm::AddFriendForm() { tabWidget = new QTabWidget(); main = new QWidget(tabWidget), head = new QWidget(); QFont bold; bold.setBold(true); headLabel.setFont(bold); toxIdLabel.setTextFormat(Qt::RichText); main->setLayout(&layout); layout.addWidget(&toxIdLabel); layout.addWidget(&toxId); layout.addWidget(&messageLabel); layout.addWidget(&message); layout.addWidget(&sendButton); tabWidget->addTab(main, QString()); importContacts = new QWidget(tabWidget); importContacts->setLayout(&importContactsLayout); importFileLine.addWidget(&importFileLabel); importFileLine.addStretch(); importFileLine.addWidget(&importFileButton); importContactsLayout.addLayout(&importFileLine); importContactsLayout.addWidget(&importMessageLabel); importContactsLayout.addWidget(&importMessage); importContactsLayout.addWidget(&importSendButton); tabWidget->addTab(importContacts, QString()); QScrollArea* scrollArea = new QScrollArea(tabWidget); QWidget* requestWidget = new QWidget(tabWidget); scrollArea->setWidget(requestWidget); scrollArea->setWidgetResizable(true); requestsLayout = new QVBoxLayout(requestWidget); requestsLayout->addStretch(1); tabWidget->addTab(scrollArea, QString()); head->setLayout(&headLayout); headLayout.addWidget(&headLabel); connect(&toxId, &QLineEdit::returnPressed, this, &AddFriendForm::onSendTriggered); connect(&toxId, &QLineEdit::textChanged, this, &AddFriendForm::onIdChanged); connect(tabWidget, &QTabWidget::currentChanged, this, &AddFriendForm::onCurrentChanged); connect(&sendButton, &QPushButton::clicked, this, &AddFriendForm::onSendTriggered); connect(&importSendButton, &QPushButton::clicked, this, &AddFriendForm::onImportSendClicked); connect(&importFileButton, &QPushButton::clicked, this, &AddFriendForm::onImportOpenClicked); connect(Nexus::getCore(), &Core::usernameSet, this, &AddFriendForm::onUsernameSet); // accessibility stuff toxIdLabel.setAccessibleDescription( tr("Tox ID, 76 hexadecimal characters")); toxId.setAccessibleDescription(tr("Type in Tox ID of your friend")); messageLabel.setAccessibleDescription(tr("Friend request message")); message.setAccessibleDescription(tr( "Type message to send with the friend request or leave empty to send a default message")); message.setTabChangesFocus(true); retranslateUi(); Translator::registerHandler(std::bind(&AddFriendForm::retranslateUi, this), this); const int size = Settings::getInstance().getFriendRequestSize(); for (int i = 0; i < size; ++i) { Settings::Request request = Settings::getInstance().getFriendRequest(i); addFriendRequestWidget(request.address, request.message); } } AddFriendForm::~AddFriendForm() { Translator::unregister(this); head->deleteLater(); tabWidget->deleteLater(); } bool AddFriendForm::isShown() const { if (head->isVisible()) { head->window()->windowHandle()->alert(0); return true; } return false; } void AddFriendForm::show(ContentLayout* contentLayout) { contentLayout->mainContent->layout()->addWidget(tabWidget); contentLayout->mainHead->layout()->addWidget(head); tabWidget->show(); head->show(); setIdFromClipboard(); toxId.setFocus(); // Fix #3421 // Needed to update tab after opening window const int index = tabWidget->currentIndex(); onCurrentChanged(index); } QString AddFriendForm::getMessage() const { const QString msg = message.toPlainText(); return !msg.isEmpty() ? msg : message.placeholderText(); } QString AddFriendForm::getImportMessage() const { const QString msg = importMessage.toPlainText(); return msg.isEmpty() ? importMessage.placeholderText() : msg; } void AddFriendForm::setMode(Mode mode) { tabWidget->setCurrentIndex(mode); } bool AddFriendForm::addFriendRequest(const QString& friendAddress, const QString& message) { if (Settings::getInstance().addFriendRequest(friendAddress, message)) { addFriendRequestWidget(friendAddress, message); if (isShown()) { onCurrentChanged(tabWidget->currentIndex()); } return true; } return false; } void AddFriendForm::onUsernameSet(const QString& username) { lastUsername = username; retranslateUi(); } void AddFriendForm::addFriend(const QString& idText) { ToxId friendId(idText); if (!friendId.isValid()) { GUI::showWarning(tr("Couldn't add friend"), tr("%1 Tox ID is invalid", "Tox address error").arg(idText)); return; } deleteFriendRequest(friendId); if (friendId == Core::getInstance()->getSelfId()) { GUI::showWarning(tr("Couldn't add friend"), //: When trying to add your own Tox ID as friend tr("You can't add yourself as a friend!")); } else { emit friendRequested(friendId, getMessage()); } } void AddFriendForm::onSendTriggered() { const QString id = getToxId(toxId.text()); addFriend(id); this->toxId.clear(); this->message.clear(); } void AddFriendForm::onImportSendClicked() { for (const QString& id : contactsToImport) { addFriend(id); } contactsToImport.clear(); importMessage.clear(); retranslateUi(); // Update the importFileLabel } void AddFriendForm::onImportOpenClicked() { const QString path = QFileDialog::getOpenFileName(Q_NULLPTR, tr("Open contact list")); if (path.isEmpty()) { return; } QFile contactFile(path); if (!contactFile.open(QIODevice::ReadOnly | QIODevice::Text)) { GUI::showWarning(tr("Couldn't open file"), //: Error message when trying to open a contact list file to import tr("Couldn't open the contact file")); return; } contactsToImport = QString::fromUtf8(contactFile.readAll()).split('\n'); qDebug() << "Import list:"; for (auto it = contactsToImport.begin(); it != contactsToImport.end();) { const QString id = getToxId(*it); if (checkIsValidId(id)) { *it = id; qDebug() << *it; ++it; } else { if (!id.isEmpty()) { qDebug() << "Invalid ID:" << *it; } it = contactsToImport.erase(it); } } if (contactsToImport.isEmpty()) { GUI::showWarning(tr("Invalid file"), tr("We couldn't find any contacts to import in this file!")); } retranslateUi(); // Update the importFileLabel to show how many contacts we have } void AddFriendForm::onIdChanged(const QString& id) { const QString strippedId = getToxId(id); const bool isValidId = checkIsValidId(strippedId); const bool isValidOrEmpty = strippedId.isEmpty() || isValidId; //: Tox ID of the person you're sending a friend request to const QString toxIdText(tr("Tox ID")); //: Tox ID format description const QString toxIdComment(tr("76 hexadecimal characters")); const QString labelText = isValidId ? QStringLiteral("%1 (%2)") : QStringLiteral("%1 (%2)"); toxIdLabel.setText(labelText.arg(toxIdText, toxIdComment)); toxId.setStyleSheet(isValidOrEmpty ? QStringLiteral("") : Style::getStylesheet("addFriendForm/toxId.css")); toxId.setToolTip(isValidOrEmpty ? QStringLiteral("") : tr("Invalid Tox ID format")); sendButton.setEnabled(isValidId); } void AddFriendForm::setIdFromClipboard() { const QClipboard* clipboard = QApplication::clipboard(); const QString trimmedId = clipboard->text().trimmed(); const QString strippedId = getToxId(trimmedId); const Core* core = Core::getInstance(); const bool isSelf = ToxId::isToxId(strippedId) && ToxId(strippedId) != core->getSelfId(); if (!strippedId.isEmpty() && ToxId::isToxId(strippedId) && isSelf) { toxId.setText(trimmedId); } } void AddFriendForm::deleteFriendRequest(const ToxId& toxId) { const int size = Settings::getInstance().getFriendRequestSize(); for (int i = 0; i < size; ++i) { Settings::Request request = Settings::getInstance().getFriendRequest(i); if (toxId == ToxId(request.address)) { Settings::getInstance().removeFriendRequest(i); return; } } } void AddFriendForm::onFriendRequestAccepted() { QPushButton* acceptButton = static_cast(sender()); QWidget* friendWidget = acceptButton->parentWidget(); const int index = requestsLayout->indexOf(friendWidget); removeFriendRequestWidget(friendWidget); const int indexFromEnd = requestsLayout->count() - index - 1; const Settings::Request request = Settings::getInstance().getFriendRequest(indexFromEnd); emit friendRequestAccepted(ToxId(request.address).getPublicKey()); Settings::getInstance().removeFriendRequest(indexFromEnd); Settings::getInstance().savePersonal(); } void AddFriendForm::onFriendRequestRejected() { QPushButton* rejectButton = static_cast(sender()); QWidget* friendWidget = rejectButton->parentWidget(); const int index = requestsLayout->indexOf(friendWidget); removeFriendRequestWidget(friendWidget); const int indexFromEnd = requestsLayout->count() - index - 1; Settings::getInstance().removeFriendRequest(indexFromEnd); Settings::getInstance().savePersonal(); } void AddFriendForm::onCurrentChanged(int index) { if (index == FriendRequest && Settings::getInstance().getUnreadFriendRequests() != 0) { Settings::getInstance().clearUnreadFriendRequests(); Settings::getInstance().savePersonal(); emit friendRequestsSeen(); } } void AddFriendForm::retranslateUi() { headLabel.setText(tr("Add Friends")); //: The message you send in friend requests static const QString messageLabelText = tr("Message"); messageLabel.setText(messageLabelText); importMessageLabel.setText(messageLabelText); //: Button to choose a file with a list of contacts to import importFileButton.setText(tr("Open")); importSendButton.setText(tr("Send friend requests")); sendButton.setText(tr("Send friend request")); //: Default message in friend requests if the field is left blank. Write something appropriate! message.setPlaceholderText(tr("%1 here! Tox me maybe?").arg(lastUsername)); importMessage.setPlaceholderText(message.placeholderText()); importFileLabel.setText( contactsToImport.isEmpty() ? tr("Import a list of contacts, one Tox ID per line") //: Shows the number of contacts we're about to import from a file (at least one) : tr("Ready to import %n contact(s), click send to confirm", "", contactsToImport.size())); onIdChanged(toxId.text()); tabWidget->setTabText(AddFriend, tr("Add a friend")); tabWidget->setTabText(ImportContacts, tr("Import contacts")); tabWidget->setTabText(FriendRequest, tr("Friend requests")); for (QPushButton* acceptButton : acceptButtons) { retranslateAcceptButton(acceptButton); } for (QPushButton* rejectButton : rejectButtons) { retranslateRejectButton(rejectButton); } } void AddFriendForm::addFriendRequestWidget(const QString& friendAddress, const QString& message) { QWidget* friendWidget = new QWidget(tabWidget); QHBoxLayout* friendLayout = new QHBoxLayout(friendWidget); QVBoxLayout* horLayout = new QVBoxLayout(); horLayout->setMargin(0); friendLayout->addLayout(horLayout); CroppingLabel* friendLabel = new CroppingLabel(friendWidget); friendLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); friendLabel->setText("" + friendAddress + ""); horLayout->addWidget(friendLabel); QLabel* messageLabel = new QLabel(message); // allow to select text, but treat links as plaintext to prevent phishing messageLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); messageLabel->setTextFormat(Qt::PlainText); messageLabel->setWordWrap(true); horLayout->addWidget(messageLabel, 1); QPushButton* acceptButton = new QPushButton(friendWidget); acceptButtons.append(acceptButton); connect(acceptButton, &QPushButton::released, this, &AddFriendForm::onFriendRequestAccepted); friendLayout->addWidget(acceptButton); retranslateAcceptButton(acceptButton); QPushButton* rejectButton = new QPushButton(friendWidget); rejectButtons.append(rejectButton); connect(rejectButton, &QPushButton::released, this, &AddFriendForm::onFriendRequestRejected); friendLayout->addWidget(rejectButton); retranslateRejectButton(rejectButton); requestsLayout->insertWidget(0, friendWidget); } void AddFriendForm::removeFriendRequestWidget(QWidget* friendWidget) { int index = requestsLayout->indexOf(friendWidget); requestsLayout->removeWidget(friendWidget); acceptButtons.removeAt(index); rejectButtons.removeAt(index); friendWidget->deleteLater(); } void AddFriendForm::retranslateAcceptButton(QPushButton* acceptButton) { acceptButton->setText(tr("Accept")); } void AddFriendForm::retranslateRejectButton(QPushButton* rejectButton) { rejectButton->setText(tr("Reject")); } qTox/src/widget/form/addfriendform.h000066400000000000000000000063301415623743500200170ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ADDFRIENDFORM_H #define ADDFRIENDFORM_H #include "src/core/toxid.h" #include #include #include #include #include #include #include #include class QTabWidget; class ContentLayout; class AddFriendForm : public QObject { Q_OBJECT public: enum Mode { AddFriend = 0, ImportContacts = 1, FriendRequest = 2 }; AddFriendForm(); AddFriendForm(const AddFriendForm&) = delete; AddFriendForm& operator=(const AddFriendForm&) = delete; ~AddFriendForm(); bool isShown() const; void show(ContentLayout* contentLayout); void setMode(Mode mode); bool addFriendRequest(const QString& friendAddress, const QString& message); signals: void friendRequested(const ToxId& friendAddress, const QString& message); void friendRequestAccepted(const ToxPk& friendAddress); void friendRequestsSeen(); public slots: void onUsernameSet(const QString& userName); private slots: void onSendTriggered(); void onIdChanged(const QString& id); void onImportSendClicked(); void onImportOpenClicked(); void onFriendRequestAccepted(); void onFriendRequestRejected(); void onCurrentChanged(int index); private: void addFriend(const QString& idText); void retranslateUi(); void addFriendRequestWidget(const QString& friendAddress, const QString& message); void removeFriendRequestWidget(QWidget* friendWidget); void retranslateAcceptButton(QPushButton* acceptButton); void retranslateRejectButton(QPushButton* rejectButton); void deleteFriendRequest(const ToxId& toxId); void setIdFromClipboard(); QString getMessage() const; QString getImportMessage() const; private: QLabel headLabel; QLabel toxIdLabel; QLabel messageLabel; QLabel importFileLabel; QLabel importMessageLabel; QPushButton sendButton; QPushButton importFileButton; QPushButton importSendButton; QLineEdit toxId; QTextEdit message; QTextEdit importMessage; QVBoxLayout layout; QVBoxLayout headLayout; QVBoxLayout importContactsLayout; QHBoxLayout importFileLine; QWidget* head; QWidget* main; QWidget* importContacts; QString lastUsername; QTabWidget* tabWidget; QVBoxLayout* requestsLayout; QList acceptButtons; QList rejectButtons; QList contactsToImport; }; #endif // ADDFRIENDFORM_H qTox/src/widget/form/chatform.cpp000066400000000000000000000537661415623743500173700ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chatform.h" #include "src/chatlog/chatlinecontentproxy.h" #include "src/chatlog/chatlog.h" #include "src/chatlog/chatmessage.h" #include "src/chatlog/content/filetransferwidget.h" #include "src/chatlog/content/text.h" #include "src/core/core.h" #include "src/core/coreav.h" #include "src/core/corefile.h" #include "src/model/friend.h" #include "src/model/status.h" #include "src/nexus.h" #include "src/persistence/history.h" #include "src/persistence/offlinemsgengine.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/video/netcamview.h" #include "src/widget/chatformheader.h" #include "src/widget/form/loadhistorydialog.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/searchform.h" #include "src/widget/style.h" #include "src/widget/tool/callconfirmwidget.h" #include "src/widget/tool/chattextedit.h" #include "src/widget/tool/croppinglabel.h" #include "src/widget/tool/screenshotgrabber.h" #include "src/widget/translator.h" #include "src/widget/widget.h" #include #include #include #include #include #include #include #include #include #include #include /** * @brief ChatForm::incomingNotification Notify that we are called by someone. * @param friendId Friend that is calling us. * * @brief ChatForm::outgoingNotification Notify that we are calling someone. * * @brief stopNotification Tell others to stop notification of a call. */ static constexpr int CHAT_WIDGET_MIN_HEIGHT = 50; static constexpr int SCREENSHOT_GRABBER_OPENING_DELAY = 500; static constexpr int TYPING_NOTIFICATION_DURATION = 3000; const QString ChatForm::ACTION_PREFIX = QStringLiteral("/me "); namespace { QString secondsToDHMS(quint32 duration) { QString res; QString cD = ChatForm::tr("Call duration: "); quint32 seconds = duration % 60; duration /= 60; quint32 minutes = duration % 60; duration /= 60; quint32 hours = duration % 24; quint32 days = duration / 24; // I assume no one will ever have call longer than a month if (days) { return cD + res.asprintf("%dd%02dh %02dm %02ds", days, hours, minutes, seconds); } if (hours) { return cD + res.asprintf("%02dh %02dm %02ds", hours, minutes, seconds); } if (minutes) { return cD + res.asprintf("%02dm %02ds", minutes, seconds); } return cD + res.asprintf("%02ds", seconds); } } // namespace ChatForm::ChatForm(Friend* chatFriend, IChatLog& chatLog, IMessageDispatcher& messageDispatcher) : GenericChatForm(chatFriend, chatLog, messageDispatcher) , f(chatFriend) , isTyping{false} , lastCallIsVideo{false} { setName(f->getDisplayedName()); headWidget->setAvatar(QPixmap(":/img/contact_dark.svg")); statusMessageLabel = new CroppingLabel(); statusMessageLabel->setObjectName("statusLabel"); statusMessageLabel->setFont(Style::getFont(Style::Medium)); statusMessageLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize()); statusMessageLabel->setTextFormat(Qt::PlainText); statusMessageLabel->setContextMenuPolicy(Qt::CustomContextMenu); typingTimer.setSingleShot(true); callDurationTimer = nullptr; chatWidget->setTypingNotification(ChatMessage::createTypingNotification()); chatWidget->setMinimumHeight(CHAT_WIDGET_MIN_HEIGHT); callDuration = new QLabel(); headWidget->addWidget(statusMessageLabel); headWidget->addStretch(); headWidget->addWidget(callDuration, 1, Qt::AlignCenter); callDuration->hide(); copyStatusAction = statusMessageMenu.addAction(QString(), this, SLOT(onCopyStatusMessage())); const Core* core = Core::getInstance(); const Profile* profile = Nexus::getProfile(); const CoreFile* coreFile = core->getCoreFile(); connect(profile, &Profile::friendAvatarChanged, this, &ChatForm::onAvatarChanged); connect(coreFile, &CoreFile::fileReceiveRequested, this, &ChatForm::updateFriendActivityForFile); connect(coreFile, &CoreFile::fileSendStarted, this, &ChatForm::updateFriendActivityForFile); connect(core, &Core::friendTypingChanged, this, &ChatForm::onFriendTypingChanged); connect(core, &Core::friendStatusChanged, this, &ChatForm::onFriendStatusChanged); connect(coreFile, &CoreFile::fileNameChanged, this, &ChatForm::onFileNameChanged); const CoreAV* av = core->getAv(); connect(av, &CoreAV::avInvite, this, &ChatForm::onAvInvite); connect(av, &CoreAV::avStart, this, &ChatForm::onAvStart); connect(av, &CoreAV::avEnd, this, &ChatForm::onAvEnd); connect(headWidget, &ChatFormHeader::callTriggered, this, &ChatForm::onCallTriggered); connect(headWidget, &ChatFormHeader::videoCallTriggered, this, &ChatForm::onVideoCallTriggered); connect(headWidget, &ChatFormHeader::micMuteToggle, this, &ChatForm::onMicMuteToggle); connect(headWidget, &ChatFormHeader::volMuteToggle, this, &ChatForm::onVolMuteToggle); connect(sendButton, &QPushButton::pressed, this, &ChatForm::callUpdateFriendActivity); connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::callUpdateFriendActivity); connect(msgEdit, &ChatTextEdit::textChanged, this, &ChatForm::onTextEditChanged); connect(msgEdit, &ChatTextEdit::pasteImage, this, &ChatForm::sendImage); connect(statusMessageLabel, &CroppingLabel::customContextMenuRequested, this, [&](const QPoint& pos) { if (!statusMessageLabel->text().isEmpty()) { QWidget* sender = static_cast(this->sender()); statusMessageMenu.exec(sender->mapToGlobal(pos)); } }); connect(&typingTimer, &QTimer::timeout, this, [=] { Core::getInstance()->sendTyping(f->getId(), false); isTyping = false; }); // reflect name changes in the header connect(headWidget, &ChatFormHeader::nameChanged, this, [=](const QString& newName) { f->setAlias(newName); }); connect(headWidget, &ChatFormHeader::callAccepted, this, [this] { onAnswerCallTriggered(lastCallIsVideo); }); connect(headWidget, &ChatFormHeader::callRejected, this, &ChatForm::onRejectCallTriggered); updateCallButtons(); setAcceptDrops(true); retranslateUi(); Translator::registerHandler(std::bind(&ChatForm::retranslateUi, this), this); } ChatForm::~ChatForm() { Translator::unregister(this); delete netcam; netcam = nullptr; } void ChatForm::setStatusMessage(const QString& newMessage) { statusMessageLabel->setText(newMessage); // for long messsages statusMessageLabel->setToolTip(Qt::convertFromPlainText(newMessage, Qt::WhiteSpaceNormal)); } void ChatForm::callUpdateFriendActivity() { emit updateFriendActivity(*f); } void ChatForm::updateFriendActivityForFile(const ToxFile& file) { if (file.friendId != f->getId()) { return; } emit updateFriendActivity(*f); } void ChatForm::onFileNameChanged(const ToxPk& friendPk) { if (friendPk != f->getPublicKey()) { return; } QMessageBox::warning(this, tr("Filename contained illegal characters"), tr("Illegal characters have been changed to _ \n" "so you can save the file on windows.")); } void ChatForm::onTextEditChanged() { if (!Settings::getInstance().getTypingNotification()) { if (isTyping) { isTyping = false; Core::getInstance()->sendTyping(f->getId(), false); } return; } bool isTypingNow = !msgEdit->toPlainText().isEmpty(); if (isTyping != isTypingNow) { Core::getInstance()->sendTyping(f->getId(), isTypingNow); if (isTypingNow) { typingTimer.start(TYPING_NOTIFICATION_DURATION); } isTyping = isTypingNow; } } void ChatForm::onAttachClicked() { QStringList paths = QFileDialog::getOpenFileNames(Q_NULLPTR, tr("Send a file"), QDir::homePath(), nullptr, nullptr); if (paths.isEmpty()) { return; } Core* core = Core::getInstance(); for (QString path : paths) { QFile file(path); QString fileName = QFileInfo(path).fileName(); if (!file.exists() || !file.open(QIODevice::ReadOnly)) { QMessageBox::warning(this, tr("Unable to open"), tr("qTox wasn't able to open %1").arg(fileName)); continue; } file.close(); if (file.isSequential()) { QMessageBox::critical(this, tr("Bad idea"), tr("You're trying to send a sequential file, " "which is not going to work!")); continue; } qint64 filesize = file.size(); core->getCoreFile()->sendFile(f->getId(), fileName, path, filesize); } } void ChatForm::onAvInvite(uint32_t friendId, bool video) { if (friendId != f->getId()) { return; } QString displayedName = f->getDisplayedName(); insertChatMessage(ChatMessage::createChatInfoMessage(tr("%1 calling").arg(displayedName), ChatMessage::INFO, QDateTime::currentDateTime())); auto testedFlag = video ? Settings::AutoAcceptCall::Video : Settings::AutoAcceptCall::Audio; // AutoAcceptCall is set for this friend if (Settings::getInstance().getAutoAcceptCall(f->getPublicKey()).testFlag(testedFlag)) { uint32_t friendId = f->getId(); qDebug() << "automatic call answer"; CoreAV* coreav = Core::getInstance()->getAv(); QMetaObject::invokeMethod(coreav, "answerCall", Qt::QueuedConnection, Q_ARG(uint32_t, friendId), Q_ARG(bool, video)); onAvStart(friendId, video); } else { headWidget->createCallConfirm(video); headWidget->showCallConfirm(); lastCallIsVideo = video; emit incomingNotification(friendId); } } void ChatForm::onAvStart(uint32_t friendId, bool video) { if (friendId != f->getId()) { return; } if (video) { showNetcam(); } else { hideNetcam(); } emit stopNotification(); updateCallButtons(); startCounter(); } void ChatForm::onAvEnd(uint32_t friendId, bool error) { if (friendId != f->getId()) { return; } headWidget->removeCallConfirm(); // Fixes an OS X bug with ending a call while in full screen if (netcam && netcam->isFullScreen()) { netcam->showNormal(); } emit stopNotification(); emit endCallNotification(); updateCallButtons(); stopCounter(error); hideNetcam(); } void ChatForm::showOutgoingCall(bool video) { headWidget->showOutgoingCall(video); addSystemInfoMessage(tr("Calling %1").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime()); emit outgoingNotification(); emit updateFriendActivity(*f); } void ChatForm::onAnswerCallTriggered(bool video) { headWidget->removeCallConfirm(); uint32_t friendId = f->getId(); emit stopNotification(); emit acceptCall(friendId); updateCallButtons(); CoreAV* av = Core::getInstance()->getAv(); if (!av->answerCall(friendId, video)) { updateCallButtons(); stopCounter(); hideNetcam(); return; } onAvStart(friendId, av->isCallVideoEnabled(f)); } void ChatForm::onRejectCallTriggered() { headWidget->removeCallConfirm(); emit rejectCall(f->getId()); } void ChatForm::onCallTriggered() { CoreAV* av = Core::getInstance()->getAv(); uint32_t friendId = f->getId(); if (av->isCallStarted(f)) { av->cancelCall(friendId); } else if (av->startCall(friendId, false)) { showOutgoingCall(false); } } void ChatForm::onVideoCallTriggered() { CoreAV* av = Core::getInstance()->getAv(); uint32_t friendId = f->getId(); if (av->isCallStarted(f)) { // TODO: We want to activate video on the active call. if (av->isCallVideoEnabled(f)) { av->cancelCall(friendId); } } else if (av->startCall(friendId, true)) { showOutgoingCall(true); } } void ChatForm::updateCallButtons() { CoreAV* av = Core::getInstance()->getAv(); const bool audio = av->isCallActive(f); const bool video = av->isCallVideoEnabled(f); const bool online = Status::isOnline(f->getStatus()); headWidget->updateCallButtons(online, audio, video); updateMuteMicButton(); updateMuteVolButton(); } void ChatForm::onMicMuteToggle() { CoreAV* av = Core::getInstance()->getAv(); av->toggleMuteCallInput(f); updateMuteMicButton(); } void ChatForm::onVolMuteToggle() { CoreAV* av = Core::getInstance()->getAv(); av->toggleMuteCallOutput(f); updateMuteVolButton(); } void ChatForm::onFriendStatusChanged(uint32_t friendId, Status::Status status) { // Disable call buttons if friend is offline if (friendId != f->getId()) { return; } if (!Status::isOnline(f->getStatus())) { // Hide the "is typing" message when a friend goes offline setFriendTyping(false); } updateCallButtons(); if (Settings::getInstance().getStatusChangeNotificationEnabled()) { QString fStatus = Status::getTitle(status); addSystemInfoMessage(tr("%1 is now %2", "e.g. \"Dubslow is now online\"") .arg(f->getDisplayedName()) .arg(fStatus), ChatMessage::INFO, QDateTime::currentDateTime()); } } void ChatForm::onFriendTypingChanged(quint32 friendId, bool isTyping) { if (friendId == f->getId()) { setFriendTyping(isTyping); } } void ChatForm::onFriendNameChanged(const QString& name) { if (sender() == f) { setName(name); } } void ChatForm::onStatusMessage(const QString& message) { if (sender() == f) { setStatusMessage(message); } } void ChatForm::onAvatarChanged(const ToxPk& friendPk, const QPixmap& pic) { if (friendPk != f->getPublicKey()) { return; } headWidget->setAvatar(pic); } GenericNetCamView* ChatForm::createNetcam() { qDebug() << "creating netcam"; uint32_t friendId = f->getId(); NetCamView* view = new NetCamView(f->getPublicKey(), this); CoreAV* av = Core::getInstance()->getAv(); VideoSource* source = av->getVideoSourceFromCall(friendId); view->show(source, f->getDisplayedName()); connect(view, &GenericNetCamView::videoCallEnd, this, &ChatForm::onVideoCallTriggered); connect(view, &GenericNetCamView::volMuteToggle, this, &ChatForm::onVolMuteToggle); connect(view, &GenericNetCamView::micMuteToggle, this, &ChatForm::onMicMuteToggle); connect(view, &GenericNetCamView::videoPreviewToggle, view, &NetCamView::toggleVideoPreview); return view; } void ChatForm::dragEnterEvent(QDragEnterEvent* ev) { if (ev->mimeData()->hasUrls()) { ev->acceptProposedAction(); } } void ChatForm::dropEvent(QDropEvent* ev) { if (!ev->mimeData()->hasUrls()) { return; } Core* core = Core::getInstance(); for (const QUrl& url : ev->mimeData()->urls()) { QFileInfo info(url.path()); QFile file(info.absoluteFilePath()); QString urlString = url.toString(); if (url.isValid() && !url.isLocalFile() && urlString.length() < static_cast(tox_max_message_length())) { messageDispatcher.sendMessage(false, urlString); continue; } QString fileName = info.fileName(); if (!file.exists() || !file.open(QIODevice::ReadOnly)) { info.setFile(url.toLocalFile()); file.setFileName(info.absoluteFilePath()); if (!file.exists() || !file.open(QIODevice::ReadOnly)) { QMessageBox::warning(this, tr("Unable to open"), tr("qTox wasn't able to open %1").arg(fileName)); continue; } } file.close(); if (file.isSequential()) { QMessageBox::critical(nullptr, tr("Bad idea"), tr("You're trying to send a sequential file, " "which is not going to work!")); continue; } if (info.exists()) { core->getCoreFile()->sendFile(f->getId(), fileName, info.absoluteFilePath(), info.size()); } } } void ChatForm::clearChatArea() { GenericChatForm::clearChatArea(/* confirm = */ false, /* inform = */ true); } void ChatForm::onScreenshotClicked() { doScreenshot(); // Give the window manager a moment to open the fullscreen grabber window QTimer::singleShot(SCREENSHOT_GRABBER_OPENING_DELAY, this, SLOT(hideFileMenu())); } void ChatForm::doScreenshot() { // note: grabber is self-managed and will destroy itself when done ScreenshotGrabber* grabber = new ScreenshotGrabber; connect(grabber, &ScreenshotGrabber::screenshotTaken, this, &ChatForm::sendImage); grabber->showGrabber(); } void ChatForm::sendImage(const QPixmap& pixmap) { QDir(Settings::getInstance().getAppDataDirPath()).mkpath("images"); // use ~ISO 8601 for screenshot timestamp, considering FS limitations // https://en.wikipedia.org/wiki/ISO_8601 // Windows has to be supported, thus filename can't have `:` in it :/ // Format should be: `qTox_Screenshot_yyyy-MM-dd HH-mm-ss.zzz.png` QString filepath = QString("%1images%2qTox_Image_%3.png") .arg(Settings::getInstance().getAppDataDirPath()) .arg(QDir::separator()) .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH-mm-ss.zzz")); QFile file(filepath); if (file.open(QFile::ReadWrite)) { pixmap.save(&file, "PNG"); qint64 filesize = file.size(); file.close(); QFileInfo fi(file); CoreFile* coreFile = Core::getInstance()->getCoreFile(); coreFile->sendFile(f->getId(), fi.fileName(), fi.filePath(), filesize); } else { QMessageBox::warning(this, tr("Failed to open temporary file", "Temporary file for screenshot"), tr("qTox wasn't able to save the screenshot")); } } void ChatForm::insertChatMessage(ChatMessage::Ptr msg) { GenericChatForm::insertChatMessage(msg); if (netcam && bodySplitter->sizes()[1] == 0) { netcam->setShowMessages(true, true); } } void ChatForm::onCopyStatusMessage() { // make sure to copy not truncated text directly from the friend QString text = f->getStatusMessage(); QClipboard* clipboard = QApplication::clipboard(); if (clipboard) { clipboard->setText(text, QClipboard::Clipboard); } } void ChatForm::updateMuteMicButton() { const CoreAV* av = Core::getInstance()->getAv(); bool active = av->isCallActive(f); bool inputMuted = av->isCallInputMuted(f); headWidget->updateMuteMicButton(active, inputMuted); if (netcam) { netcam->updateMuteMicButton(inputMuted); } } void ChatForm::updateMuteVolButton() { const CoreAV* av = Core::getInstance()->getAv(); bool active = av->isCallActive(f); bool outputMuted = av->isCallOutputMuted(f); headWidget->updateMuteVolButton(active, outputMuted); if (netcam) { netcam->updateMuteVolButton(outputMuted); } } void ChatForm::startCounter() { if (callDurationTimer) { return; } callDurationTimer = new QTimer(); connect(callDurationTimer, &QTimer::timeout, this, &ChatForm::onUpdateTime); callDurationTimer->start(1000); timeElapsed.start(); callDuration->show(); } void ChatForm::stopCounter(bool error) { if (!callDurationTimer) { return; } QString dhms = secondsToDHMS(timeElapsed.elapsed() / 1000); QString name = f->getDisplayedName(); QString mess = error ? tr("Call with %1 ended unexpectedly. %2") : tr("Call with %1 ended. %2"); // TODO: add notification once notifications are implemented addSystemInfoMessage(mess.arg(name, dhms), ChatMessage::INFO, QDateTime::currentDateTime()); callDurationTimer->stop(); callDuration->setText(""); callDuration->hide(); delete callDurationTimer; callDurationTimer = nullptr; } void ChatForm::onUpdateTime() { callDuration->setText(secondsToDHMS(timeElapsed.elapsed() / 1000)); } void ChatForm::setFriendTyping(bool isTyping) { chatWidget->setTypingNotificationVisible(isTyping); Text* text = static_cast(chatWidget->getTypingNotification()->getContent(1)); QString typingDiv = "
%1
"; QString name = f->getDisplayedName(); text->setText(typingDiv.arg(tr("%1 is typing").arg(name))); } void ChatForm::show(ContentLayout* contentLayout) { GenericChatForm::show(contentLayout); } void ChatForm::reloadTheme() { chatWidget->setTypingNotification(ChatMessage::createTypingNotification()); GenericChatForm::reloadTheme(); } void ChatForm::showEvent(QShowEvent* event) { GenericChatForm::showEvent(event); } void ChatForm::hideEvent(QHideEvent* event) { GenericChatForm::hideEvent(event); } void ChatForm::retranslateUi() { copyStatusAction->setText(tr("Copy")); updateMuteMicButton(); updateMuteVolButton(); if (netcam) { netcam->setShowMessages(chatWidget->isVisible()); } } qTox/src/widget/form/chatform.h000066400000000000000000000076161415623743500170260ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHATFORM_H #define CHATFORM_H #include #include #include #include #include "genericchatform.h" #include "src/core/core.h" #include "src/model/ichatlog.h" #include "src/model/imessagedispatcher.h" #include "src/model/status.h" #include "src/persistence/history.h" #include "src/widget/tool/screenshotgrabber.h" class CallConfirmWidget; class FileTransferInstance; class Friend; class History; class OfflineMsgEngine; class QPixmap; class QHideEvent; class QMoveEvent; class ChatForm : public GenericChatForm { Q_OBJECT public: ChatForm(Friend* chatFriend, IChatLog& chatLog, IMessageDispatcher& messageDispatcher); ~ChatForm(); void setStatusMessage(const QString& newMessage); void setFriendTyping(bool isTyping); virtual void show(ContentLayout* contentLayout) final override; virtual void reloadTheme() final override; static const QString ACTION_PREFIX; signals: void incomingNotification(uint32_t friendId); void outgoingNotification(); void stopNotification(); void endCallNotification(); void rejectCall(uint32_t friendId); void acceptCall(uint32_t friendId); void updateFriendActivity(Friend& frnd); public slots: void onAvInvite(uint32_t friendId, bool video); void onAvStart(uint32_t friendId, bool video); void onAvEnd(uint32_t friendId, bool error); void onAvatarChanged(const ToxPk& friendPk, const QPixmap& pic); void onFileNameChanged(const ToxPk& friendPk); void clearChatArea(); private slots: void updateFriendActivityForFile(const ToxFile& file); void onAttachClicked() override; void onScreenshotClicked() override; void onTextEditChanged(); void onCallTriggered(); void onVideoCallTriggered(); void onAnswerCallTriggered(bool video); void onRejectCallTriggered(); void onMicMuteToggle(); void onVolMuteToggle(); void onFriendStatusChanged(quint32 friendId, Status::Status status); void onFriendTypingChanged(quint32 friendId, bool isTyping); void onFriendNameChanged(const QString& name); void onStatusMessage(const QString& message); void onUpdateTime(); void sendImage(const QPixmap& pixmap); void doScreenshot(); void onCopyStatusMessage(); void callUpdateFriendActivity(); private: void updateMuteMicButton(); void updateMuteVolButton(); void retranslateUi(); void showOutgoingCall(bool video); void startCounter(); void stopCounter(bool error = false); void updateCallButtons(); protected: GenericNetCamView* createNetcam() final override; void insertChatMessage(ChatMessage::Ptr msg) final override; void dragEnterEvent(QDragEnterEvent* ev) final override; void dropEvent(QDropEvent* ev) final override; void hideEvent(QHideEvent* event) final override; void showEvent(QShowEvent* event) final override; private: Friend* f; CroppingLabel* statusMessageLabel; QMenu statusMessageMenu; QLabel* callDuration; QTimer* callDurationTimer; QTimer typingTimer; QElapsedTimer timeElapsed; QAction* copyStatusAction; bool isTyping; bool lastCallIsVideo; }; #endif // CHATFORM_H qTox/src/widget/form/filesform.cpp000066400000000000000000000062761415623743500175450ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "filesform.h" #include "src/widget/contentlayout.h" #include "src/widget/translator.h" #include "src/widget/style.h" #include "src/widget/widget.h" #include #include FilesForm::FilesForm() : QObject() , doneIcon(Style::getImagePath("fileTransferWidget/fileDone.svg")) { head = new QWidget(); QFont bold; bold.setBold(true); headLabel.setFont(bold); head->setLayout(&headLayout); headLayout.addWidget(&headLabel); recvd = new QListWidget; sent = new QListWidget; main.addTab(recvd, QString()); main.addTab(sent, QString()); connect(sent, &QListWidget::itemActivated, this, &FilesForm::onFileActivated); connect(recvd, &QListWidget::itemActivated, this, &FilesForm::onFileActivated); retranslateUi(); Translator::registerHandler(std::bind(&FilesForm::retranslateUi, this), this); } FilesForm::~FilesForm() { Translator::unregister(this); delete recvd; delete sent; head->deleteLater(); } bool FilesForm::isShown() const { if (main.isVisible()) { head->window()->windowHandle()->alert(0); return true; } return false; } void FilesForm::show(ContentLayout* contentLayout) { contentLayout->mainContent->layout()->addWidget(&main); contentLayout->mainHead->layout()->addWidget(head); main.show(); head->show(); } void FilesForm::onFileDownloadComplete(const QString& path) { QListWidgetItem* tmp = new QListWidgetItem(doneIcon, QFileInfo(path).fileName()); tmp->setData(Qt::UserRole, path); recvd->addItem(tmp); } void FilesForm::onFileUploadComplete(const QString& path) { QListWidgetItem* tmp = new QListWidgetItem(doneIcon, QFileInfo(path).fileName()); tmp->setData(Qt::UserRole, path); sent->addItem(tmp); } // sadly, the ToxFile struct in core only has the file name, not the file path... // so currently, these don't work as intended (though for now, downloads might work // whenever they're not saved anywhere custom, thanks to the hack) // I could do some digging around, but for now I'm tired and others already // might know it without me needing to dig, so... void FilesForm::onFileActivated(QListWidgetItem* item) { Widget::confirmExecutableOpen(QFileInfo(item->data(Qt::UserRole).toString())); } void FilesForm::retranslateUi() { headLabel.setText(tr("Transferred Files", "\"Headline\" of the window")); main.setTabText(0, tr("Downloads")); main.setTabText(1, tr("Uploads")); } qTox/src/widget/form/filesform.h000066400000000000000000000027641415623743500172100ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FILESFORM_H #define FILESFORM_H #include #include #include #include #include class ContentLayout; class QListWidget; class FilesForm : public QObject { Q_OBJECT public: FilesForm(); ~FilesForm(); bool isShown() const; void show(ContentLayout* contentLayout); public slots: void onFileDownloadComplete(const QString& path); void onFileUploadComplete(const QString& path); private slots: void onFileActivated(QListWidgetItem* item); private: void retranslateUi(); private: QWidget* head; QIcon doneIcon; QLabel headLabel; QVBoxLayout headLayout; QTabWidget main; QListWidget *sent, *recvd; }; #endif // FILESFORM_H qTox/src/widget/form/genericchatform.cpp000066400000000000000000001006561415623743500207140ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "genericchatform.h" #include "src/chatlog/chatlinecontentproxy.h" #include "src/chatlog/chatlog.h" #include "src/chatlog/content/filetransferwidget.h" #include "src/chatlog/content/timestamp.h" #include "src/core/core.h" #include "src/friendlist.h" #include "src/grouplist.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/persistence/settings.h" #include "src/persistence/smileypack.h" #include "src/video/genericnetcamview.h" #include "src/widget/chatformheader.h" #include "src/widget/contentdialog.h" #include "src/widget/contentdialogmanager.h" #include "src/widget/contentlayout.h" #include "src/widget/emoticonswidget.h" #include "src/widget/form/chatform.h" #include "src/widget/form/loadhistorydialog.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/searchform.h" #include "src/widget/style.h" #include "src/widget/tool/chattextedit.h" #include "src/widget/tool/flyoutoverlaywidget.h" #include "src/widget/translator.h" #include "src/widget/widget.h" #include #include #include #include #include #include #include #ifdef SPELL_CHECKING #include #endif /** * @class GenericChatForm * @brief Parent class for all chatforms. It's provide the minimum required UI * elements and methods to work with chat messages. */ static const QSize FILE_FLYOUT_SIZE{24, 24}; static const short FOOT_BUTTONS_SPACING = 2; static const short MESSAGE_EDIT_HEIGHT = 50; static const short MAIN_FOOT_LAYOUT_SPACING = 5; static const QString FONT_STYLE[]{"normal", "italic", "oblique"}; /** * @brief Creates CSS style string for needed class with specified font * @param font Font that needs to be represented for a class * @param name Class name * @return Style string */ static QString fontToCss(const QFont& font, const QString& name) { QString result{"%1{" "font-family: \"%2\"; " "font-size: %3px; " "font-style: \"%4\"; " "font-weight: normal;}"}; return result.arg(name).arg(font.family()).arg(font.pixelSize()).arg(FONT_STYLE[font.style()]); } /** * @brief Searches for name (possibly alias) of someone with specified public key among all of your * friends or groups you are participated * @param pk Searched public key * @return Name or alias of someone with such public key, or public key string representation if no * one was found */ QString GenericChatForm::resolveToxPk(const ToxPk& pk) { Friend* f = FriendList::findFriend(pk); if (f) { return f->getDisplayedName(); } for (Group* it : GroupList::getAllGroups()) { QString res = it->resolveToxId(pk); if (!res.isEmpty()) { return res; } } return pk.toString(); } namespace { const QString STYLE_PATH = QStringLiteral("chatForm/buttons.css"); } namespace { template QPushButton* createButton(const QString& name, T* self, Fun onClickSlot) { QPushButton* btn = new QPushButton(); // Fix for incorrect layouts on OS X as per // https://bugreports.qt-project.org/browse/QTBUG-14591 btn->setAttribute(Qt::WA_LayoutUsesWidgetRect); btn->setObjectName(name); btn->setProperty("state", "green"); btn->setStyleSheet(Style::getStylesheet(STYLE_PATH)); QObject::connect(btn, &QPushButton::clicked, self, onClickSlot); return btn; } ChatMessage::Ptr getChatMessageForIdx(ChatLogIdx idx, const std::map& messages) { auto existingMessageIt = messages.find(idx); if (existingMessageIt == messages.end()) { return ChatMessage::Ptr(); } return existingMessageIt->second; } bool shouldRenderDate(ChatLogIdx idxToRender, const IChatLog& chatLog) { if (idxToRender == chatLog.getFirstIdx()) return true; return chatLog.at(idxToRender - 1).getTimestamp().date() != chatLog.at(idxToRender).getTimestamp().date(); } ChatMessage::Ptr dateMessageForItem(const ChatLogItem& item) { const auto& s = Settings::getInstance(); const auto date = item.getTimestamp().date(); auto dateText = date.toString(s.getDateFormat()); return ChatMessage::createChatInfoMessage(dateText, ChatMessage::INFO, QDateTime()); } ChatMessage::Ptr createMessage(const QString& displayName, bool isSelf, bool colorizeNames, const ChatLogMessage& chatLogMessage) { auto messageType = chatLogMessage.message.isAction ? ChatMessage::MessageType::ACTION : ChatMessage::MessageType::NORMAL; const bool bSelfMentioned = std::any_of(chatLogMessage.message.metadata.begin(), chatLogMessage.message.metadata.end(), [](const MessageMetadata& metadata) { return metadata.type == MessageMetadataType::selfMention; }); if (bSelfMentioned) { messageType = ChatMessage::MessageType::ALERT; } const auto timestamp = chatLogMessage.message.timestamp; return ChatMessage::createChatMessage(displayName, chatLogMessage.message.content, messageType, isSelf, chatLogMessage.state, timestamp, colorizeNames); } void renderMessage(const QString& displayName, bool isSelf, bool colorizeNames, const ChatLogMessage& chatLogMessage, ChatMessage::Ptr& chatMessage) { if (chatMessage) { if (chatLogMessage.state == MessageState::complete) { chatMessage->markAsDelivered(chatLogMessage.message.timestamp); } } else { chatMessage = createMessage(displayName, isSelf, colorizeNames, chatLogMessage); } } void renderFile(QString displayName, ToxFile file, bool isSelf, QDateTime timestamp, ChatMessage::Ptr& chatMessage) { if (!chatMessage) { chatMessage = ChatMessage::createFileTransferMessage(displayName, file, isSelf, timestamp); } else { auto proxy = static_cast(chatMessage->getContent(1)); assert(proxy->getWidgetType() == ChatLineContentProxy::FileTransferWidgetType); auto ftWidget = static_cast(proxy->getWidget()); ftWidget->onFileTransferUpdate(file); } } void renderItem(const ChatLogItem& item, bool hideName, bool colorizeNames, ChatMessage::Ptr& chatMessage) { const auto& sender = item.getSender(); const Core* core = Core::getInstance(); bool isSelf = sender == core->getSelfId().getPublicKey(); switch (item.getContentType()) { case ChatLogItem::ContentType::message: { const auto& chatLogMessage = item.getContentAsMessage(); renderMessage(item.getDisplayName(), isSelf, colorizeNames, chatLogMessage, chatMessage); break; } case ChatLogItem::ContentType::fileTransfer: { const auto& file = item.getContentAsFile(); renderFile(item.getDisplayName(), file.file, isSelf, item.getTimestamp(), chatMessage); break; } } if (hideName) { chatMessage->hideSender(); } } ChatLogIdx firstItemAfterDate(QDate date, const IChatLog& chatLog) { auto idxs = chatLog.getDateIdxs(date, 1); if (idxs.size()) { return idxs[0].idx; } else { return chatLog.getNextIdx(); } } } // namespace GenericChatForm::GenericChatForm(const Contact* contact, IChatLog& chatLog, IMessageDispatcher& messageDispatcher, QWidget* parent) : QWidget(parent, Qt::Window) , audioInputFlag(false) , audioOutputFlag(false) , chatLog(chatLog) , messageDispatcher(messageDispatcher) { curRow = 0; headWidget = new ChatFormHeader(); searchForm = new SearchForm(); dateInfo = new QLabel(this); chatWidget = new ChatLog(this); chatWidget->setBusyNotification(ChatMessage::createBusyNotification()); searchForm->hide(); dateInfo->setAlignment(Qt::AlignHCenter); dateInfo->setVisible(false); // settings const Settings& s = Settings::getInstance(); connect(&s, &Settings::emojiFontPointSizeChanged, chatWidget, &ChatLog::forceRelayout); connect(&s, &Settings::chatMessageFontChanged, this, &GenericChatForm::onChatMessageFontChanged); msgEdit = new ChatTextEdit(); #ifdef SPELL_CHECKING if (s.getSpellCheckingEnabled()) { decorator = new Sonnet::SpellCheckDecorator(msgEdit); } #endif sendButton = createButton("sendButton", this, &GenericChatForm::onSendTriggered); emoteButton = createButton("emoteButton", this, &GenericChatForm::onEmoteButtonClicked); fileButton = createButton("fileButton", this, &GenericChatForm::onAttachClicked); screenshotButton = createButton("screenshotButton", this, &GenericChatForm::onScreenshotClicked); // TODO: Make updateCallButtons (see ChatForm) abstract // and call here to set tooltips. fileFlyout = new FlyoutOverlayWidget; QHBoxLayout* fileLayout = new QHBoxLayout(fileFlyout); fileLayout->addWidget(screenshotButton); fileLayout->setContentsMargins(0, 0, 0, 0); fileLayout->setSpacing(0); fileLayout->setMargin(0); msgEdit->setFixedHeight(MESSAGE_EDIT_HEIGHT); msgEdit->setFrameStyle(QFrame::NoFrame); bodySplitter = new QSplitter(Qt::Vertical, this); connect(bodySplitter, &QSplitter::splitterMoved, this, &GenericChatForm::onSplitterMoved); QWidget* contentWidget = new QWidget(this); bodySplitter->addWidget(contentWidget); QVBoxLayout* mainLayout = new QVBoxLayout(); mainLayout->addWidget(bodySplitter); mainLayout->setMargin(0); setLayout(mainLayout); QVBoxLayout* footButtonsSmall = new QVBoxLayout(); footButtonsSmall->setSpacing(FOOT_BUTTONS_SPACING); footButtonsSmall->addWidget(emoteButton); footButtonsSmall->addWidget(fileButton); QHBoxLayout* mainFootLayout = new QHBoxLayout(); mainFootLayout->addWidget(msgEdit); mainFootLayout->addLayout(footButtonsSmall); mainFootLayout->addSpacing(MAIN_FOOT_LAYOUT_SPACING); mainFootLayout->addWidget(sendButton); mainFootLayout->setSpacing(0); QVBoxLayout* contentLayout = new QVBoxLayout(contentWidget); contentLayout->addWidget(searchForm); contentLayout->addWidget(dateInfo); contentLayout->addWidget(chatWidget); contentLayout->addLayout(mainFootLayout); quoteAction = menu.addAction(QIcon(), QString(), this, SLOT(quoteSelectedText()), QKeySequence(Qt::ALT + Qt::Key_Q)); addAction(quoteAction); menu.addSeparator(); searchAction = menu.addAction(QIcon(), QString(), this, SLOT(searchFormShow()), QKeySequence(Qt::CTRL + Qt::Key_F)); addAction(searchAction); menu.addSeparator(); menu.addActions(chatWidget->actions()); menu.addSeparator(); clearAction = menu.addAction(QIcon::fromTheme("edit-clear"), QString(), this, SLOT(clearChatArea()), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_L)); addAction(clearAction); copyLinkAction = menu.addAction(QIcon(), QString(), this, SLOT(copyLink())); menu.addSeparator(); loadHistoryAction = menu.addAction(QIcon(), QString(), this, SLOT(onLoadHistory())); exportChatAction = menu.addAction(QIcon::fromTheme("document-save"), QString(), this, SLOT(onExportChat())); connect(chatWidget, &ChatLog::customContextMenuRequested, this, &GenericChatForm::onChatContextMenuRequested); connect(chatWidget, &ChatLog::firstVisibleLineChanged, this, &GenericChatForm::updateShowDateInfo); connect(chatWidget, &ChatLog::loadHistoryLower, this, &GenericChatForm::loadHistoryLower); connect(searchForm, &SearchForm::searchInBegin, this, &GenericChatForm::searchInBegin); connect(searchForm, &SearchForm::searchUp, this, &GenericChatForm::onSearchUp); connect(searchForm, &SearchForm::searchDown, this, &GenericChatForm::onSearchDown); connect(searchForm, &SearchForm::visibleChanged, this, &GenericChatForm::onSearchTriggered); connect(this, &GenericChatForm::messageNotFoundShow, searchForm, &SearchForm::showMessageNotFound); connect(&chatLog, &IChatLog::itemUpdated, this, &GenericChatForm::renderMessage); connect(msgEdit, &ChatTextEdit::enterPressed, this, &GenericChatForm::onSendTriggered); reloadTheme(); fileFlyout->setFixedSize(FILE_FLYOUT_SIZE); fileFlyout->setParent(this); fileButton->installEventFilter(this); fileFlyout->installEventFilter(this); retranslateUi(); Translator::registerHandler(std::bind(&GenericChatForm::retranslateUi, this), this); // update header on name/title change connect(contact, &Contact::displayedNameChanged, this, &GenericChatForm::setName); auto chatLogIdxRange = chatLog.getNextIdx() - chatLog.getFirstIdx(); auto firstChatLogIdx = (chatLogIdxRange < 100) ? chatLog.getFirstIdx() : chatLog.getNextIdx() - 100; renderMessages(firstChatLogIdx, chatLog.getNextIdx()); netcam = nullptr; } GenericChatForm::~GenericChatForm() { Translator::unregister(this); delete searchForm; } void GenericChatForm::adjustFileMenuPosition() { QPoint pos = fileButton->mapTo(bodySplitter, QPoint()); QSize size = fileFlyout->size(); fileFlyout->move(pos.x() - size.width(), pos.y()); } void GenericChatForm::showFileMenu() { if (!fileFlyout->isShown() && !fileFlyout->isBeingShown()) { adjustFileMenuPosition(); } fileFlyout->animateShow(); } void GenericChatForm::hideFileMenu() { if (fileFlyout->isShown() || fileFlyout->isBeingShown()) fileFlyout->animateHide(); } QDateTime GenericChatForm::getLatestTime() const { return getTime(chatWidget->getLatestLine()); } QDateTime GenericChatForm::getFirstTime() const { return getTime(chatWidget->getFirstLine()); } void GenericChatForm::reloadTheme() { const Settings& s = Settings::getInstance(); setStyleSheet(Style::getStylesheet("genericChatForm/genericChatForm.css")); msgEdit->setStyleSheet(Style::getStylesheet("msgEdit/msgEdit.css") + fontToCss(s.getChatMessageFont(), "QTextEdit")); chatWidget->setStyleSheet(Style::getStylesheet("chatArea/chatArea.css")); headWidget->setStyleSheet(Style::getStylesheet("chatArea/chatHead.css")); chatWidget->reloadTheme(); headWidget->reloadTheme(); searchForm->reloadTheme(); emoteButton->setStyleSheet(Style::getStylesheet(STYLE_PATH)); fileButton->setStyleSheet(Style::getStylesheet(STYLE_PATH)); screenshotButton->setStyleSheet(Style::getStylesheet(STYLE_PATH)); sendButton->setStyleSheet(Style::getStylesheet(STYLE_PATH)); } void GenericChatForm::setName(const QString& newName) { headWidget->setName(newName); } void GenericChatForm::show(ContentLayout* contentLayout) { contentLayout->mainHead->layout()->addWidget(headWidget); headWidget->show(); #if QT_VERSION < QT_VERSION_CHECK(5, 12, 4) && QT_VERSION > QT_VERSION_CHECK(5, 11, 0) // HACK: switching order happens to avoid a Qt bug causing segfault, present between these versions. // this could cause flickering if our form is shown before added to the layout // https://github.com/qTox/qTox/issues/5570 QWidget::show(); contentLayout->mainContent->layout()->addWidget(this); #else contentLayout->mainContent->layout()->addWidget(this); QWidget::show(); #endif } void GenericChatForm::showEvent(QShowEvent*) { msgEdit->setFocus(); headWidget->showCallConfirm(); } bool GenericChatForm::event(QEvent* e) { // If the user accidentally starts typing outside of the msgEdit, focus it automatically if (e->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(e); if ((ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier) && !ke->text().isEmpty()) { if (searchForm->isHidden()) { msgEdit->sendKeyEvent(ke); msgEdit->setFocus(); } else { searchForm->insertEditor(ke->text()); searchForm->setFocusEditor(); } } } return QWidget::event(e); } void GenericChatForm::onChatContextMenuRequested(QPoint pos) { QWidget* sender = static_cast(QObject::sender()); pos = sender->mapToGlobal(pos); // If we right-clicked on a link, give the option to copy it bool clickedOnLink = false; Text* clickedText = qobject_cast(chatWidget->getContentFromGlobalPos(pos)); if (clickedText) { QPointF scenePos = chatWidget->mapToScene(chatWidget->mapFromGlobal(pos)); QString linkTarget = clickedText->getLinkAt(scenePos); if (!linkTarget.isEmpty()) { clickedOnLink = true; copyLinkAction->setData(linkTarget); } } copyLinkAction->setVisible(clickedOnLink); menu.exec(pos); } void GenericChatForm::onSendTriggered() { auto msg = msgEdit->toPlainText(); bool isAction = msg.startsWith(ChatForm::ACTION_PREFIX, Qt::CaseInsensitive); if (isAction) { msg.remove(0, ChatForm::ACTION_PREFIX.length()); } if (msg.isEmpty()) { return; } msgEdit->setLastMessage(msg); msgEdit->clear(); messageDispatcher.sendMessage(isAction, msg); } /** * @brief Show, is it needed to hide message author name or not * @param idx ChatLogIdx of the message * @return True if the name should be hidden, false otherwise */ bool GenericChatForm::needsToHideName(ChatLogIdx idx) const { // If the previous message is not rendered we should show the name // regardless of other constraints auto itemBefore = messages.find(idx - 1); if (itemBefore == messages.end()) { return false; } const auto& prevItem = chatLog.at(idx - 1); const auto& currentItem = chatLog.at(idx); // Always show the * in the name field for action messages if (currentItem.getContentType() == ChatLogItem::ContentType::message && currentItem.getContentAsMessage().message.isAction) { return false; } qint64 messagesTimeDiff = prevItem.getTimestamp().secsTo(currentItem.getTimestamp()); return currentItem.getSender() == prevItem.getSender() && messagesTimeDiff < chatWidget->repNameAfter; } void GenericChatForm::onEmoteButtonClicked() { // don't show the smiley selection widget if there are no smileys available if (SmileyPack::getInstance().getEmoticons().empty()) return; EmoticonsWidget widget; connect(&widget, SIGNAL(insertEmoticon(QString)), this, SLOT(onEmoteInsertRequested(QString))); widget.installEventFilter(this); QWidget* sender = qobject_cast(QObject::sender()); if (sender) { QPoint pos = -QPoint(widget.sizeHint().width() / 2, widget.sizeHint().height()) - QPoint(0, 10); widget.exec(sender->mapToGlobal(pos)); } } void GenericChatForm::onEmoteInsertRequested(QString str) { // insert the emoticon QWidget* sender = qobject_cast(QObject::sender()); if (sender) msgEdit->insertPlainText(str); msgEdit->setFocus(); // refocus so that we can continue typing } void GenericChatForm::onCopyLogClicked() { chatWidget->copySelectedText(); } void GenericChatForm::focusInput() { msgEdit->setFocus(); } void GenericChatForm::onChatMessageFontChanged(const QFont& font) { // chat log chatWidget->fontChanged(font); chatWidget->forceRelayout(); // message editor msgEdit->setStyleSheet(Style::getStylesheet("msgEdit/msgEdit.css") + fontToCss(font, "QTextEdit")); } void GenericChatForm::setColorizedNames(bool enable) { colorizeNames = enable; } void GenericChatForm::addSystemInfoMessage(const QString& message, ChatMessage::SystemMessageType type, const QDateTime& datetime) { insertChatMessage(ChatMessage::createChatInfoMessage(message, type, datetime)); } void GenericChatForm::addSystemDateMessage(const QDate& date) { const Settings& s = Settings::getInstance(); QString dateText = date.toString(s.getDateFormat()); insertChatMessage(ChatMessage::createChatInfoMessage(dateText, ChatMessage::INFO, QDateTime())); } QDateTime GenericChatForm::getTime(const ChatLine::Ptr &chatLine) const { if (chatLine) { Timestamp* const timestamp = qobject_cast(chatLine->getContent(2)); if (timestamp) { return timestamp->getTime(); } else { return QDateTime(); } } return QDateTime(); } void GenericChatForm::disableSearchText() { auto msgIt = messages.find(searchPos.logIdx); if (msgIt != messages.end()) { auto text = qobject_cast(msgIt->second->getContent(1)); text->deselectText(); } } void GenericChatForm::clearChatArea() { clearChatArea(/* confirm = */ true, /* inform = */ true); } void GenericChatForm::clearChatArea(bool confirm, bool inform) { if (confirm) { QMessageBox::StandardButton mboxResult = QMessageBox::question(this, tr("Confirmation"), tr("You are sure that you want to clear all displayed messages?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (mboxResult == QMessageBox::No) { return; } } chatWidget->clear(); if (inform) addSystemInfoMessage(tr("Cleared"), ChatMessage::INFO, QDateTime::currentDateTime()); messages.clear(); } void GenericChatForm::onSelectAllClicked() { chatWidget->selectAll(); } void GenericChatForm::insertChatMessage(ChatMessage::Ptr msg) { chatWidget->insertChatlineAtBottom(std::static_pointer_cast(msg)); emit messageInserted(); } void GenericChatForm::hideEvent(QHideEvent* event) { hideFileMenu(); QWidget::hideEvent(event); } void GenericChatForm::resizeEvent(QResizeEvent* event) { adjustFileMenuPosition(); QWidget::resizeEvent(event); } bool GenericChatForm::eventFilter(QObject* object, QEvent* event) { EmoticonsWidget* ev = qobject_cast(object); if (ev && event->type() == QEvent::KeyPress) { QKeyEvent* key = static_cast(event); msgEdit->sendKeyEvent(key); msgEdit->setFocus(); return false; } if (object != this->fileButton && object != this->fileFlyout) return false; if (!qobject_cast(object)->isEnabled()) return false; switch (event->type()) { case QEvent::Enter: showFileMenu(); break; case QEvent::Leave: { QPoint flyPos = fileFlyout->mapToGlobal(QPoint()); QSize flySize = fileFlyout->size(); QPoint filePos = fileButton->mapToGlobal(QPoint()); QSize fileSize = fileButton->size(); QRect region = QRect(flyPos, flySize).united(QRect(filePos, fileSize)); if (!region.contains(QCursor::pos())) hideFileMenu(); break; } case QEvent::MouseButtonPress: hideFileMenu(); break; default: break; } return false; } void GenericChatForm::onSplitterMoved(int, int) { if (netcam) netcam->setShowMessages(bodySplitter->sizes()[1] == 0); } void GenericChatForm::onShowMessagesClicked() { if (netcam) { if (bodySplitter->sizes()[1] == 0) bodySplitter->setSizes({1, 1}); else bodySplitter->setSizes({1, 0}); onSplitterMoved(0, 0); } } void GenericChatForm::quoteSelectedText() { QString selectedText = chatWidget->getSelectedText(); if (selectedText.isEmpty()) return; // forming pretty quote text // 1. insert "> " to the begining of quote; // 2. replace all possible line terminators with "\n> "; // 3. append new line to the end of quote. QString quote = selectedText; quote.insert(0, "> "); quote.replace(QRegExp(QString("\r\n|[\r\n\u2028\u2029]")), QString("\n> ")); quote.append("\n"); msgEdit->append(quote); } /** * @brief Callback of GenericChatForm::copyLinkAction */ void GenericChatForm::copyLink() { QString linkText = copyLinkAction->data().toString(); QApplication::clipboard()->setText(linkText); } void GenericChatForm::searchFormShow() { if (searchForm->isHidden()) { searchForm->show(); searchForm->setFocusEditor(); } } void GenericChatForm::onLoadHistory() { LoadHistoryDialog dlg(&chatLog); if (dlg.exec()) { QDateTime time = dlg.getFromDate(); auto idx = firstItemAfterDate(dlg.getFromDate().date(), chatLog); renderMessages(idx, chatLog.getNextIdx()); } } void GenericChatForm::onExportChat() { QString path = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Save chat log")); if (path.isEmpty()) { return; } QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { return; } QString buffer; for (auto i = chatLog.getFirstIdx(); i < chatLog.getNextIdx(); ++i) { const auto& item = chatLog.at(i); if (item.getContentType() != ChatLogItem::ContentType::message) { continue; } QString timestamp = item.getTimestamp().time().toString("hh:mm:ss"); QString datestamp = item.getTimestamp().date().toString("yyyy-MM-dd"); QString author = item.getDisplayName(); buffer = buffer % QString{datestamp % '\t' % timestamp % '\t' % author % '\t' % item.getContentAsMessage().message.content % '\n'}; } file.write(buffer.toUtf8()); file.close(); } void GenericChatForm::onSearchTriggered() { if (searchForm->isHidden()) { searchForm->removeSearchPhrase(); } disableSearchText(); } void GenericChatForm::searchInBegin(const QString& phrase, const ParameterSearch& parameter) { disableSearchText(); bool bForwardSearch = false; switch (parameter.period) { case PeriodSearch::WithTheFirst: { bForwardSearch = true; searchPos.logIdx = chatLog.getFirstIdx(); searchPos.numMatches = 0; break; } case PeriodSearch::WithTheEnd: case PeriodSearch::None: { bForwardSearch = false; searchPos.logIdx = chatLog.getNextIdx(); searchPos.numMatches = 0; break; } case PeriodSearch::AfterDate: { bForwardSearch = true; searchPos.logIdx = firstItemAfterDate(parameter.date, chatLog); searchPos.numMatches = 0; break; } case PeriodSearch::BeforeDate: { bForwardSearch = false; searchPos.logIdx = firstItemAfterDate(parameter.date, chatLog); searchPos.numMatches = 0; break; } } if (bForwardSearch) { onSearchDown(phrase, parameter); } else { onSearchUp(phrase, parameter); } } void GenericChatForm::onSearchUp(const QString& phrase, const ParameterSearch& parameter) { auto result = chatLog.searchBackward(searchPos, phrase, parameter); handleSearchResult(result, SearchDirection::Up); } void GenericChatForm::onSearchDown(const QString& phrase, const ParameterSearch& parameter) { auto result = chatLog.searchForward(searchPos, phrase, parameter); handleSearchResult(result, SearchDirection::Down); } void GenericChatForm::handleSearchResult(SearchResult result, SearchDirection direction) { if (!result.found) { emit messageNotFoundShow(direction); return; } disableSearchText(); searchPos = result.pos; auto const firstRenderedIdx = (messages.empty()) ? chatLog.getNextIdx() : messages.begin()->first; renderMessages(searchPos.logIdx, firstRenderedIdx, [this, result] { auto msg = messages.at(searchPos.logIdx); chatWidget->scrollToLine(msg); auto text = qobject_cast(msg->getContent(1)); text->selectText(result.exp, std::make_pair(result.start, result.len)); }); } void GenericChatForm::renderMessage(ChatLogIdx idx) { renderMessages(idx, idx + 1); } void GenericChatForm::renderMessages(ChatLogIdx begin, ChatLogIdx end, std::function onCompletion) { QList beforeLines; QList afterLines; for (auto i = begin; i < end; ++i) { auto chatMessage = getChatMessageForIdx(i, messages); renderItem(chatLog.at(i), needsToHideName(i), colorizeNames, chatMessage); if (messages.find(i) == messages.end()) { QList* lines = (messages.empty() || i > messages.rbegin()->first) ? &afterLines : &beforeLines; messages.insert({i, chatMessage}); if (shouldRenderDate(i, chatLog)) { lines->push_back(dateMessageForItem(chatLog.at(i))); } lines->push_back(chatMessage); } } for (auto const& line : afterLines) { chatWidget->insertChatlineAtBottom(line); } if (!beforeLines.empty()) { // Rendering upwards is expensive and has async behavior for chatWidget. // Once rendering completes we call our completion callback once and // then disconnect the signal if (onCompletion) { auto connection = std::make_shared(); *connection = connect(chatWidget, &ChatLog::workerTimeoutFinished, [onCompletion, connection] { onCompletion(); disconnect(*connection); }); } chatWidget->insertChatlinesOnTop(beforeLines); } else if (onCompletion) { onCompletion(); } } void GenericChatForm::loadHistoryLower() { auto begin = messages.begin()->first; if (begin.get() > 100) { begin = ChatLogIdx(begin.get() - 100); } else { begin = ChatLogIdx(0); } renderMessages(begin, chatLog.getNextIdx()); } void GenericChatForm::updateShowDateInfo(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& topLine) { // If the dateInfo is visible we need to pretend the top line is the one // covered by the date to prevent oscillations const auto effectiveTopLine = (dateInfo->isVisible() && prevLine) ? prevLine : topLine; const auto date = getTime(effectiveTopLine); if (date.isValid() && date.date() != QDate::currentDate()) { const auto dateText = QStringLiteral("%1<\b>").arg(date.toString(Settings::getInstance().getDateFormat())); dateInfo->setText(dateText); dateInfo->setVisible(true); } else { dateInfo->setVisible(false); } } void GenericChatForm::retranslateUi() { sendButton->setToolTip(tr("Send message")); emoteButton->setToolTip(tr("Smileys")); fileButton->setToolTip(tr("Send file(s)")); screenshotButton->setToolTip(tr("Send a screenshot")); clearAction->setText(tr("Clear displayed messages")); quoteAction->setText(tr("Quote selected text")); copyLinkAction->setText(tr("Copy link address")); searchAction->setText(tr("Search in text")); loadHistoryAction->setText(tr("Load chat history...")); exportChatAction->setText(tr("Export to file")); } void GenericChatForm::showNetcam() { if (!netcam) netcam = createNetcam(); connect(netcam, &GenericNetCamView::showMessageClicked, this, &GenericChatForm::onShowMessagesClicked); bodySplitter->insertWidget(0, netcam); bodySplitter->setCollapsible(0, false); QSize minSize = netcam->getSurfaceMinSize(); ContentDialog* current = ContentDialogManager::getInstance()->current(); if (current) current->onVideoShow(minSize); } void GenericChatForm::hideNetcam() { if (!netcam) return; ContentDialog* current = ContentDialogManager::getInstance()->current(); if (current) current->onVideoHide(); netcam->close(); netcam->hide(); delete netcam; netcam = nullptr; } qTox/src/widget/form/genericchatform.h000066400000000000000000000136241415623743500203570ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GENERICCHATFORM_H #define GENERICCHATFORM_H #include "src/chatlog/chatmessage.h" #include "src/core/toxpk.h" #include "src/model/ichatlog.h" #include "src/widget/searchtypes.h" #include #include /** * Spacing in px inserted when the author of the last message changes * @note Why the hell is this a thing? surely the different font is enough? * - Even a different font is not enough – TODO #1307 ~~zetok */ class ChatFormHeader; class ChatLog; class ChatTextEdit; class Contact; class ContentLayout; class CroppingLabel; class FlyoutOverlayWidget; class GenericNetCamView; class MaskablePixmapWidget; class SearchForm; class Widget; class QLabel; class QPushButton; class QSplitter; class QToolButton; class QVBoxLayout; class IMessageDispatcher; class Message; namespace Ui { class MainWindow; } #ifdef SPELL_CHECKING namespace Sonnet { class SpellCheckDecorator; } #endif class GenericChatForm : public QWidget { Q_OBJECT public: GenericChatForm(const Contact* contact, IChatLog& chatLog, IMessageDispatcher& messageDispatcher, QWidget* parent = nullptr); ~GenericChatForm() override; void setName(const QString& newName); virtual void show() final { } virtual void show(ContentLayout* contentLayout); virtual void reloadTheme(); void addSystemInfoMessage(const QString& message, ChatMessage::SystemMessageType type, const QDateTime& datetime); static QString resolveToxPk(const ToxPk& pk); QDateTime getLatestTime() const; QDateTime getFirstTime() const; signals: void messageInserted(); void messageNotFoundShow(SearchDirection direction); public slots: void focusInput(); void onChatMessageFontChanged(const QFont& font); void setColorizedNames(bool enable); protected slots: void onChatContextMenuRequested(QPoint pos); virtual void onScreenshotClicked() = 0; void onSendTriggered(); virtual void onAttachClicked() = 0; void onEmoteButtonClicked(); void onEmoteInsertRequested(QString str); void onCopyLogClicked(); void clearChatArea(); void clearChatArea(bool confirm, bool inform); void onSelectAllClicked(); void showFileMenu(); void hideFileMenu(); void onShowMessagesClicked(); void onSplitterMoved(int pos, int index); void quoteSelectedText(); void copyLink(); void onLoadHistory(); void onExportChat(); void searchFormShow(); void onSearchTriggered(); void updateShowDateInfo(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& topLine); void searchInBegin(const QString& phrase, const ParameterSearch& parameter); void onSearchUp(const QString& phrase, const ParameterSearch& parameter); void onSearchDown(const QString& phrase, const ParameterSearch& parameter); void handleSearchResult(SearchResult result, SearchDirection direction); void renderMessage(ChatLogIdx idx); void renderMessages(ChatLogIdx begin, ChatLogIdx end, std::function onCompletion = std::function()); void loadHistoryLower(); private: void retranslateUi(); void addSystemDateMessage(const QDate& date); QDateTime getTime(const ChatLine::Ptr& chatLine) const; protected: ChatMessage::Ptr createMessage(const ToxPk& author, const QString& message, const QDateTime& datetime, bool isAction, bool isSent, bool colorizeName = false); bool needsToHideName(ChatLogIdx idx) const; void showNetcam(); void hideNetcam(); virtual GenericNetCamView* createNetcam() = 0; virtual void insertChatMessage(ChatMessage::Ptr msg); void adjustFileMenuPosition(); virtual void hideEvent(QHideEvent* event) override; virtual void showEvent(QShowEvent*) override; virtual bool event(QEvent*) final override; virtual void resizeEvent(QResizeEvent* event) final override; virtual bool eventFilter(QObject* object, QEvent* event) final override; void disableSearchText(); bool searchInText(const QString& phrase, const ParameterSearch& parameter, SearchDirection direction); std::pair indexForSearchInLine(const QString& txt, const QString& phrase, const ParameterSearch& parameter, SearchDirection direction); protected: bool audioInputFlag; bool audioOutputFlag; int curRow; QAction* clearAction; QAction* quoteAction; QAction* copyLinkAction; QAction* searchAction; QAction* loadHistoryAction; QAction* exportChatAction; QMenu menu; QPushButton* emoteButton; QPushButton* fileButton; QPushButton* screenshotButton; QPushButton* sendButton; QSplitter* bodySplitter; ChatFormHeader* headWidget; SearchForm *searchForm; QLabel *dateInfo; ChatLog* chatWidget; ChatTextEdit* msgEdit; #ifdef SPELL_CHECKING Sonnet::SpellCheckDecorator* decorator{nullptr}; #endif FlyoutOverlayWidget* fileFlyout; GenericNetCamView* netcam; Widget* parent; IChatLog& chatLog; IMessageDispatcher& messageDispatcher; SearchPos searchPos; std::map messages; bool colorizeNames = false; }; #endif // GENERICCHATFORM_H qTox/src/widget/form/groupchatform.cpp000066400000000000000000000370111415623743500204260ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupchatform.h" #include "tabcompleter.h" #include "src/core/core.h" #include "src/core/coreav.h" #include "src/core/groupid.h" #include "src/chatlog/chatlog.h" #include "src/chatlog/content/text.h" #include "src/model/friend.h" #include "src/friendlist.h" #include "src/model/group.h" #include "src/video/groupnetcamview.h" #include "src/widget/chatformheader.h" #include "src/widget/flowlayout.h" #include "src/widget/form/chatform.h" #include "src/widget/groupwidget.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/style.h" #include "src/widget/tool/croppinglabel.h" #include "src/widget/translator.h" #include "src/persistence/settings.h" #include #include #include #include #include namespace { const auto LABEL_PEER_TYPE_OUR = QVariant(QStringLiteral("our")); const auto LABEL_PEER_TYPE_MUTED = QVariant(QStringLiteral("muted")); const auto LABEL_PEER_PLAYING_AUDIO = QVariant(QStringLiteral("true")); const auto LABEL_PEER_NOT_PLAYING_AUDIO = QVariant(QStringLiteral("false")); const auto PEER_LABEL_STYLE_SHEET_PATH = QStringLiteral("chatArea/chatHead.css"); } /** * @brief Edit name for correct representation if it is needed * @param name Editing string * @return Source name if it does not contain any newline character, otherwise it chops characters * starting with first newline character and appends "..." */ QString editName(const QString& name) { const int pos = name.indexOf(QRegularExpression(QStringLiteral("[\n\r]"))); if (pos == -1) { return name; } QString result = name; const int len = result.length(); result.chop(len - pos); result.append(QStringLiteral("…")); // \u2026 Unicode symbol, not just three separate dots return result; } /** * @var QList GroupChatForm::peerLabels * @brief Maps peernumbers to the QLabels in namesListLayout. * * @var QMap GroupChatForm::peerAudioTimers * @brief Timeout = peer stopped sending audio. */ GroupChatForm::GroupChatForm(Group* chatGroup, IChatLog& chatLog, IMessageDispatcher& messageDispatcher) : GenericChatForm(chatGroup, chatLog, messageDispatcher) , group(chatGroup) , inCall(false) { nusersLabel = new QLabel(); tabber = new TabCompleter(msgEdit, group); fileButton->setEnabled(false); fileButton->setProperty("state", ""); ChatFormHeader::Mode mode = ChatFormHeader::Mode::None; if (group->isAvGroupchat()) { mode = ChatFormHeader::Mode::Audio; } headWidget->setMode(mode); setName(group->getName()); nusersLabel->setFont(Style::getFont(Style::Medium)); nusersLabel->setObjectName("statusLabel"); retranslateUi(); const QSize& size = headWidget->getAvatarSize(); headWidget->setAvatar(Style::scaleSvgImage(":/img/group_dark.svg", size.width(), size.height())); msgEdit->setObjectName("group"); namesListLayout = new FlowLayout(0, 5, 0); headWidget->addWidget(nusersLabel); headWidget->addLayout(namesListLayout); headWidget->addStretch(); //nameLabel->setMinimumHeight(12); nusersLabel->setMinimumHeight(12); connect(msgEdit, &ChatTextEdit::tabPressed, tabber, &TabCompleter::complete); connect(msgEdit, &ChatTextEdit::keyPressed, tabber, &TabCompleter::reset); connect(headWidget, &ChatFormHeader::callTriggered, this, &GroupChatForm::onCallClicked); connect(headWidget, &ChatFormHeader::micMuteToggle, this, &GroupChatForm::onMicMuteToggle); connect(headWidget, &ChatFormHeader::volMuteToggle, this, &GroupChatForm::onVolMuteToggle); connect(headWidget, &ChatFormHeader::nameChanged, chatGroup, &Group::setName); connect(group, &Group::titleChanged, this, &GroupChatForm::onTitleChanged); connect(group, &Group::userJoined, this, &GroupChatForm::onUserJoined); connect(group, &Group::userLeft, this, &GroupChatForm::onUserLeft); connect(group, &Group::peerNameChanged, this, &GroupChatForm::onPeerNameChanged); connect(group, &Group::numPeersChanged, this, &GroupChatForm::updateUserCount); connect(&Settings::getInstance(), &Settings::blackListChanged, this, &GroupChatForm::updateUserNames); updateUserNames(); setAcceptDrops(true); Translator::registerHandler(std::bind(&GroupChatForm::retranslateUi, this), this); } GroupChatForm::~GroupChatForm() { Translator::unregister(this); } void GroupChatForm::onTitleChanged(const QString& author, const QString& title) { if (author.isEmpty()) { return; } const QString message = tr("%1 has set the title to %2").arg(author, title); const QDateTime curTime = QDateTime::currentDateTime(); addSystemInfoMessage(message, ChatMessage::INFO, curTime); } void GroupChatForm::onScreenshotClicked() { // Unsupported } void GroupChatForm::onAttachClicked() { // Unsupported } /** * @brief Updates user names' labels at the top of group chat */ void GroupChatForm::updateUserNames() { QLayoutItem* child; while ((child = namesListLayout->takeAt(0))) { child->widget()->hide(); delete child->widget(); delete child; } peerLabels.clear(); const auto peers = group->getPeerList(); // no need to do anything without any peers if (peers.isEmpty()) { return; } /* we store the peer labels by their ToxPk, but the namelist layout * needs it in alphabetical order, so we first create and store the labels * and then sort them by their text and add them to the layout in that order */ const auto selfPk = Core::getInstance()->getSelfPublicKey(); for (const auto& peerPk : peers.keys()) { const QString peerName = peers.value(peerPk); const QString editedName = editName(peerName); QLabel* const label = new QLabel(editedName + QLatin1String(", ")); if (editedName != peerName) { label->setToolTip(peerName + " (" + peerPk.toString() + ")"); } else if (peerName != peerPk.toString()) { label->setToolTip(peerPk.toString()); } // else their name is just their Pk, no tooltip needed label->setTextFormat(Qt::PlainText); label->setContextMenuPolicy(Qt::CustomContextMenu); const Settings& s = Settings::getInstance(); connect(label, &QLabel::customContextMenuRequested, this, &GroupChatForm::onLabelContextMenuRequested); if (peerPk == selfPk) { label->setProperty("peerType", LABEL_PEER_TYPE_OUR); } else if (s.getBlackList().contains(peerPk.toString())) { label->setProperty("peerType", LABEL_PEER_TYPE_MUTED); } label->setStyleSheet(Style::getStylesheet(PEER_LABEL_STYLE_SHEET_PATH)); peerLabels.insert(peerPk, label); } if (netcam != nullptr) { static_cast(netcam)->clearPeers(); } // add the labels in alphabetical order into the layout auto nickLabelList = peerLabels.values(); std::sort(nickLabelList.begin(), nickLabelList.end(), [](const QLabel* a, const QLabel* b) { return a->text().toLower() < b->text().toLower(); }); // remove comma from last sorted label QLabel* const lastLabel = nickLabelList.last(); QString labelText = lastLabel->text(); labelText.chop(2); lastLabel->setText(labelText); for (QLabel* l : nickLabelList) { namesListLayout->addWidget(l); } } void GroupChatForm::onUserJoined(const ToxPk& user, const QString& name) { addSystemInfoMessage(tr("%1 has joined the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime()); updateUserNames(); } void GroupChatForm::onUserLeft(const ToxPk& user, const QString& name) { addSystemInfoMessage(tr("%1 has left the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime()); updateUserNames(); } void GroupChatForm::onPeerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName) { addSystemInfoMessage(tr("%1 is now known as %2").arg(oldName, newName), ChatMessage::INFO, QDateTime::currentDateTime()); updateUserNames(); } void GroupChatForm::peerAudioPlaying(ToxPk peerPk) { peerLabels[peerPk]->setProperty("playingAudio", LABEL_PEER_PLAYING_AUDIO); peerLabels[peerPk]->style()->unpolish(peerLabels[peerPk]); peerLabels[peerPk]->style()->polish(peerLabels[peerPk]); // TODO(sudden6): check if this can ever be false, cause [] default constructs if (!peerAudioTimers[peerPk]) { peerAudioTimers[peerPk] = new QTimer(this); peerAudioTimers[peerPk]->setSingleShot(true); connect(peerAudioTimers[peerPk], &QTimer::timeout, [this, peerPk] { if (netcam) { static_cast(netcam)->removePeer(peerPk); } auto it = peerLabels.find(peerPk); if (it != peerLabels.end()) { peerLabels[peerPk]->setProperty("playingAudio", LABEL_PEER_NOT_PLAYING_AUDIO); peerLabels[peerPk]->style()->unpolish(peerLabels[peerPk]); peerLabels[peerPk]->style()->polish(peerLabels[peerPk]); } delete peerAudioTimers[peerPk]; peerAudioTimers[peerPk] = nullptr; }); if (netcam) { static_cast(netcam)->removePeer(peerPk); const auto nameIt = group->getPeerList().find(peerPk); static_cast(netcam)->addPeer(peerPk, nameIt.value()); } } peerLabels[peerPk]->setStyleSheet(Style::getStylesheet(PEER_LABEL_STYLE_SHEET_PATH)); peerAudioTimers[peerPk]->start(500); } void GroupChatForm::dragEnterEvent(QDragEnterEvent* ev) { if (!ev->mimeData()->hasFormat("toxPk")) { return; } ToxPk toxPk{ev->mimeData()->data("toxPk")}; Friend* frnd = FriendList::findFriend(toxPk); if (frnd) ev->acceptProposedAction(); } void GroupChatForm::dropEvent(QDropEvent* ev) { if (!ev->mimeData()->hasFormat("toxPk")) { return; } ToxPk toxPk{ev->mimeData()->data("toxPk")}; Friend* frnd = FriendList::findFriend(toxPk); if (!frnd) return; int friendId = frnd->getId(); int groupId = group->getId(); if (Status::isOnline(frnd->getStatus())) { Core::getInstance()->groupInviteFriend(friendId, groupId); } } void GroupChatForm::onMicMuteToggle() { if (audioInputFlag) { CoreAV* av = Core::getInstance()->getAv(); const bool oldMuteState = av->isGroupCallInputMuted(group); const bool newMute = !oldMuteState; av->muteCallInput(group, newMute); headWidget->updateMuteMicButton(inCall, newMute); } } void GroupChatForm::onVolMuteToggle() { if (audioOutputFlag) { CoreAV* av = Core::getInstance()->getAv(); const bool oldMuteState = av->isGroupCallOutputMuted(group); const bool newMute = !oldMuteState; av->muteCallOutput(group, newMute); headWidget->updateMuteVolButton(inCall, newMute); } } void GroupChatForm::onCallClicked() { CoreAV* av = Core::getInstance()->getAv(); if (!inCall) { joinGroupCall(); } else { leaveGroupCall(); } headWidget->updateCallButtons(true, inCall); const bool inMute = av->isGroupCallInputMuted(group); headWidget->updateMuteMicButton(inCall, inMute); const bool outMute = av->isGroupCallOutputMuted(group); headWidget->updateMuteVolButton(inCall, outMute); } GenericNetCamView* GroupChatForm::createNetcam() { auto view = new GroupNetCamView(group->getId(), this); const auto& names = group->getPeerList(); const auto ownPk = Core::getInstance()->getSelfPublicKey(); for (const auto& peerPk : names.keys()) { auto timerIt = peerAudioTimers.find(peerPk); if (peerPk != ownPk && timerIt != peerAudioTimers.end()) { static_cast(view)->addPeer(peerPk, names.find(peerPk).value()); } } return view; } void GroupChatForm::keyPressEvent(QKeyEvent* ev) { // Push to talk (CTRL+P) if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall) { onMicMuteToggle(); } if (msgEdit->hasFocus()) return; } void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) { // Push to talk (CTRL+P) if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall) { onMicMuteToggle(); } if (msgEdit->hasFocus()) return; } /** * @brief Updates users' count label text */ void GroupChatForm::updateUserCount(int numPeers) { nusersLabel->setText(tr("%n user(s) in chat", "Number of users in chat", numPeers)); headWidget->updateCallButtons(true, inCall); } void GroupChatForm::retranslateUi() { updateUserCount(group->getPeersCount()); } void GroupChatForm::onLabelContextMenuRequested(const QPoint& localPos) { QLabel* label = static_cast(QObject::sender()); if (label == nullptr) { return; } const QPoint pos = label->mapToGlobal(localPos); const QString muteString = tr("mute"); const QString unmuteString = tr("unmute"); Settings& s = Settings::getInstance(); QStringList blackList = s.getBlackList(); QMenu* const contextMenu = new QMenu(this); const ToxPk selfPk = Core::getInstance()->getSelfPublicKey(); ToxPk peerPk; // delete menu after it stops being used connect(contextMenu, &QMenu::aboutToHide, contextMenu, &QObject::deleteLater); peerPk = peerLabels.key(label); if (peerPk.isEmpty() || peerPk == selfPk) { return; } const bool isPeerBlocked = blackList.contains(peerPk.toString()); QString menuTitle = label->text(); if (menuTitle.endsWith(QLatin1String(", "))) { menuTitle.chop(2); } QAction* menuTitleAction = contextMenu->addAction(menuTitle); menuTitleAction->setEnabled(false); // make sure the title is not clickable contextMenu->addSeparator(); const QAction* toggleMuteAction; if (isPeerBlocked) { toggleMuteAction = contextMenu->addAction(unmuteString); } else { toggleMuteAction = contextMenu->addAction(muteString); } contextMenu->setStyleSheet(Style::getStylesheet(PEER_LABEL_STYLE_SHEET_PATH)); const QAction* selectedItem = contextMenu->exec(pos); if (selectedItem == toggleMuteAction) { if (isPeerBlocked) { const int index = blackList.indexOf(peerPk.toString()); if (index != -1) { blackList.removeAt(index); } } else { blackList << peerPk.toString(); } s.setBlackList(blackList); } } void GroupChatForm::joinGroupCall() { CoreAV* av = Core::getInstance()->getAv(); av->joinGroupCall(*group); audioInputFlag = true; audioOutputFlag = true; inCall = true; } void GroupChatForm::leaveGroupCall() { CoreAV* av = Core::getInstance()->getAv(); av->leaveGroupCall(group->getId()); audioInputFlag = false; audioOutputFlag = false; inCall = false; } qTox/src/widget/form/groupchatform.h000066400000000000000000000047721415623743500201030ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUPCHATFORM_H #define GROUPCHATFORM_H #include "genericchatform.h" #include "src/core/toxpk.h" #include namespace Ui { class MainWindow; } class Group; class TabCompleter; class FlowLayout; class QTimer; class GroupId; class IMessageDispatcher; class Message; class GroupChatForm : public GenericChatForm { Q_OBJECT public: explicit GroupChatForm(Group* chatGroup, IChatLog& chatLog, IMessageDispatcher& messageDispatcher); ~GroupChatForm(); void peerAudioPlaying(ToxPk peerPk); private slots: void onScreenshotClicked() override; void onAttachClicked() override; void onMicMuteToggle(); void onVolMuteToggle(); void onCallClicked(); void onUserJoined(const ToxPk& user, const QString& name); void onUserLeft(const ToxPk& user, const QString& name); void onPeerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName); void onTitleChanged(const QString& author, const QString& title); void onLabelContextMenuRequested(const QPoint& localPos); protected: virtual GenericNetCamView* createNetcam() final override; virtual void keyPressEvent(QKeyEvent* ev) final override; virtual void keyReleaseEvent(QKeyEvent* ev) final override; // drag & drop virtual void dragEnterEvent(QDragEnterEvent* ev) final override; virtual void dropEvent(QDropEvent* ev) final override; private: void retranslateUi(); void updateUserCount(int numPeers); void updateUserNames(); void joinGroupCall(); void leaveGroupCall(); private: Group* group; QMap peerLabels; QMap peerAudioTimers; FlowLayout* namesListLayout; QLabel* nusersLabel; TabCompleter* tabber; bool inCall; }; #endif // GROUPCHATFORM_H qTox/src/widget/form/groupinviteform.cpp000066400000000000000000000117131415623743500210060ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupinviteform.h" #include "ui_mainwindow.h" #include "src/core/core.h" #include "src/model/groupinvite.h" #include "src/persistence/settings.h" #include "src/widget/contentlayout.h" #include "src/widget/form/groupinvitewidget.h" #include "src/widget/translator.h" #include #include #include #include #include #include #include #include #include #include /** * @class GroupInviteForm * * @brief This form contains all group invites you received */ GroupInviteForm::GroupInviteForm() : headWidget(new QWidget(this)) , headLabel(new QLabel(this)) , createButton(new QPushButton(this)) , inviteBox(new QGroupBox(this)) , scroll(new QScrollArea(this)) { QVBoxLayout* layout = new QVBoxLayout(this); connect(createButton, &QPushButton::clicked, [this]() { emit groupCreate(TOX_CONFERENCE_TYPE_AV); }); QWidget* innerWidget = new QWidget(scroll); innerWidget->setLayout(new QVBoxLayout()); innerWidget->layout()->setAlignment(Qt::AlignTop); scroll->setWidget(innerWidget); scroll->setWidgetResizable(true); QVBoxLayout* inviteLayout = new QVBoxLayout(inviteBox); inviteLayout->addWidget(scroll); layout->addWidget(createButton); layout->addWidget(inviteBox); QFont bold; bold.setBold(true); headLabel->setFont(bold); QHBoxLayout* headLayout = new QHBoxLayout(headWidget); headLayout->addWidget(headLabel); retranslateUi(); Translator::registerHandler(std::bind(&GroupInviteForm::retranslateUi, this), this); } GroupInviteForm::~GroupInviteForm() { Translator::unregister(this); } /** * @brief Detects that form is shown * @return True if form is visible */ bool GroupInviteForm::isShown() const { bool result = isVisible(); if (result) { headWidget->window()->windowHandle()->alert(0); } return result; } /** * @brief Shows the form * @param contentLayout Main layout that contains all components of the form */ void GroupInviteForm::show(ContentLayout* contentLayout) { contentLayout->mainContent->layout()->addWidget(this); contentLayout->mainHead->layout()->addWidget(headWidget); QWidget::show(); headWidget->show(); } /** * @brief Adds group invite * @param inviteInfo Object which contains info about group invitation * @return true if notification is needed, false otherwise */ bool GroupInviteForm::addGroupInvite(const GroupInvite& inviteInfo) { // supress duplicate invite messages for (GroupInviteWidget* existing : invites) { if (existing->getInviteInfo().getInvite() == inviteInfo.getInvite()) { return false; } } GroupInviteWidget* widget = new GroupInviteWidget(this, inviteInfo); scroll->widget()->layout()->addWidget(widget); invites.append(widget); connect(widget, &GroupInviteWidget::accepted, [this] (const GroupInvite& inviteInfo) { deleteInviteWidget(inviteInfo); emit groupInviteAccepted(inviteInfo); }); connect(widget, &GroupInviteWidget::rejected, [this] (const GroupInvite& inviteInfo) { deleteInviteWidget(inviteInfo); }); if (isVisible()) { emit groupInvitesSeen(); return false; } return true; } void GroupInviteForm::showEvent(QShowEvent* event) { QWidget::showEvent(event); emit groupInvitesSeen(); } /** * @brief Deletes accepted/declined group invite widget * @param inviteInfo Invite information of accepted/declined widget */ void GroupInviteForm::deleteInviteWidget(const GroupInvite& inviteInfo) { auto deletingWidget = std::find_if(invites.begin(), invites.end(), [=](const GroupInviteWidget* widget) { return inviteInfo == widget->getInviteInfo(); }); (*deletingWidget)->deleteLater(); scroll->widget()->layout()->removeWidget(*deletingWidget); invites.erase(deletingWidget); } void GroupInviteForm::retranslateUi() { headLabel->setText(tr("Groups")); if (createButton) { createButton->setText(tr("Create new group")); } inviteBox->setTitle(tr("Group invites")); for (GroupInviteWidget* invite : invites) { invite->retranslateUi(); } } qTox/src/widget/form/groupinviteform.h000066400000000000000000000034221415623743500204510ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUPINVITEFORM_H #define GROUPINVITEFORM_H #include "src/widget/gui.h" #include class ContentLayout; class GroupInvite; class GroupInviteWidget; class QGroupBox; class QLabel; class QPushButton; class QScrollArea; class QSignalMapper; namespace Ui { class MainWindow; } class GroupInviteForm : public QWidget { Q_OBJECT public: GroupInviteForm(); ~GroupInviteForm(); void show(ContentLayout* contentLayout); bool addGroupInvite(const GroupInvite& inviteInfo); bool isShown() const; signals: void groupCreate(uint8_t type); void groupInviteAccepted(const GroupInvite& inviteInfo); void groupInvitesSeen(); protected: void showEvent(QShowEvent* event) final override; private: void retranslateUi(); void deleteInviteWidget(const GroupInvite& inviteInfo); private: QWidget* headWidget; QLabel* headLabel; QPushButton* createButton; QGroupBox* inviteBox; QList invites; QScrollArea* scroll; }; #endif // GROUPINVITEFORM_H qTox/src/widget/form/groupinvitewidget.cpp000066400000000000000000000051251415623743500213260ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupinvitewidget.h" #include "src/core/core.h" #include "src/nexus.h" #include "src/persistence/settings.h" #include "src/widget/tool/croppinglabel.h" #include #include #include /** * @class GroupInviteWidget * * @brief This class shows information about single group invite * and provides buttons to accept/reject it */ GroupInviteWidget::GroupInviteWidget(QWidget* parent, const GroupInvite& invite) : QWidget(parent) , acceptButton(new QPushButton(this)) , rejectButton(new QPushButton(this)) , inviteMessageLabel(new CroppingLabel(this)) , widgetLayout(new QHBoxLayout(this)) , inviteInfo(invite) { connect(acceptButton, &QPushButton::clicked, [=]() { emit accepted(inviteInfo); }); connect(rejectButton, &QPushButton::clicked, [=]() { emit rejected(inviteInfo); }); widgetLayout->addWidget(inviteMessageLabel); widgetLayout->addWidget(acceptButton); widgetLayout->addWidget(rejectButton); setLayout(widgetLayout); retranslateUi(); } /** * @brief Retranslate all elements in the form. */ void GroupInviteWidget::retranslateUi() { QString name = Nexus::getCore()->getFriendUsername(inviteInfo.getFriendId()); QDateTime inviteDate = inviteInfo.getInviteDate(); QString date = inviteDate.toString(Settings::getInstance().getDateFormat()); QString time = inviteDate.toString(Settings::getInstance().getTimestampFormat()); inviteMessageLabel->setText( tr("Invited by %1 on %2 at %3.").arg("%1").arg(name.toHtmlEscaped(), date, time)); acceptButton->setText(tr("Join")); rejectButton->setText(tr("Decline")); } /** * @brief Returns infomation about invitation - e.g., who and when sent * @return Invite information object */ const GroupInvite GroupInviteWidget::getInviteInfo() const { return inviteInfo; } qTox/src/widget/form/groupinvitewidget.h000066400000000000000000000026531415623743500207760ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUPINVITEWIDGET_H #define GROUPINVITEWIDGET_H #include "src/model/groupinvite.h" #include class CroppingLabel; class QHBoxLayout; class QPushButton; class GroupInviteWidget : public QWidget { Q_OBJECT public: GroupInviteWidget(QWidget* parent, const GroupInvite& invite); void retranslateUi(); const GroupInvite getInviteInfo() const; signals: void accepted(const GroupInvite& invite); void rejected(const GroupInvite& invite); private: QPushButton* acceptButton; QPushButton* rejectButton; CroppingLabel* inviteMessageLabel; QHBoxLayout* widgetLayout; GroupInvite inviteInfo; }; #endif // GROUPINVITEWIDGET_H qTox/src/widget/form/loadhistorydialog.cpp000066400000000000000000000054201415623743500212660ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "loadhistorydialog.h" #include "ui_loadhistorydialog.h" #include "src/model/ichatlog.h" #include "src/nexus.h" #include "src/persistence/history.h" #include "src/persistence/profile.h" #include #include #include LoadHistoryDialog::LoadHistoryDialog(const IChatLog* chatLog, QWidget* parent) : QDialog(parent) , ui(new Ui::LoadHistoryDialog) , chatLog(chatLog) { ui->setupUi(this); highlightDates(QDate::currentDate().year(), QDate::currentDate().month()); connect(ui->fromDate, &QCalendarWidget::currentPageChanged, this, &LoadHistoryDialog::highlightDates); } LoadHistoryDialog::LoadHistoryDialog(QWidget* parent) : QDialog(parent) , ui(new Ui::LoadHistoryDialog) { ui->setupUi(this); } LoadHistoryDialog::~LoadHistoryDialog() { delete ui; } QDateTime LoadHistoryDialog::getFromDate() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) QDateTime res(ui->fromDate->selectedDate().startOfDay()); #else QDateTime res(ui->fromDate->selectedDate()); #endif if (res.date().month() != ui->fromDate->monthShown() || res.date().year() != ui->fromDate->yearShown()) { QDate newDate(ui->fromDate->yearShown(), ui->fromDate->monthShown(), 1); res.setDate(newDate); } return res; } void LoadHistoryDialog::setTitle(const QString& title) { setWindowTitle(title); } void LoadHistoryDialog::setInfoLabel(const QString& info) { ui->fromLabel->setText(info); } void LoadHistoryDialog::highlightDates(int year, int month) { History* history = Nexus::getProfile()->getHistory(); QDate monthStart(year, month, 1); QDate monthEnd(year, month + 1, 1); // Max 31 days in a month auto dateIdxs = chatLog->getDateIdxs(monthStart, 31); QTextCharFormat format; format.setFontWeight(QFont::Bold); QCalendarWidget* calendar = ui->fromDate; for (const auto& item : dateIdxs) { if (item.date < monthEnd) { calendar->setDateTextFormat(item.date, format); } } } qTox/src/widget/form/loadhistorydialog.h000066400000000000000000000026741415623743500207430ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef LOADHISTORYDIALOG_H #define LOADHISTORYDIALOG_H #include "src/core/toxpk.h" #include #include namespace Ui { class LoadHistoryDialog; } class IChatLog; class LoadHistoryDialog : public QDialog { Q_OBJECT public: explicit LoadHistoryDialog(const IChatLog* chatLog, QWidget* parent = nullptr); explicit LoadHistoryDialog(QWidget* parent = nullptr); ~LoadHistoryDialog(); QDateTime getFromDate(); void setTitle(const QString& title); void setInfoLabel(const QString& info); public slots: void highlightDates(int year, int month); private: Ui::LoadHistoryDialog* ui; const IChatLog* chatLog; }; #endif // LOADHISTORYDIALOG_H qTox/src/widget/form/loadhistorydialog.ui000066400000000000000000000034761415623743500211320ustar00rootroot00000000000000 LoadHistoryDialog 0 0 347 264 Load History Dialog true Load history from: false Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() LoadHistoryDialog accept() 248 254 157 274 buttonBox rejected() LoadHistoryDialog reject() 316 260 286 274 qTox/src/widget/form/profileform.cpp000066400000000000000000000406731415623743500201020ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "profileform.h" #include "ui_profileform.h" #include "src/core/core.h" #include "src/model/profile/iprofileinfo.h" #include "src/persistence/profile.h" #include "src/persistence/profilelocker.h" #include "src/persistence/settings.h" #include "src/widget/contentlayout.h" #include "src/widget/form/setpassworddialog.h" #include "src/widget/form/settingswidget.h" #include "src/widget/gui.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/style.h" #include "src/widget/tool/croppinglabel.h" #include "src/widget/translator.h" #include "src/widget/widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const QMap SET_AVATAR_ERROR = { { IProfileInfo::SetAvatarResult::CanNotOpen, ProfileForm::tr("Unable to open this file.") }, { IProfileInfo::SetAvatarResult::CanNotRead, ProfileForm::tr("Unable to read this image.") }, { IProfileInfo::SetAvatarResult::TooLarge, ProfileForm::tr("The supplied image is too large.\nPlease use another image.") }, { IProfileInfo::SetAvatarResult::EmptyPath, ProfileForm::tr("Empty path is unavaliable") }, }; static const QMap> RENAME_ERROR = { { IProfileInfo::RenameResult::Error, { ProfileForm::tr("Failed to rename"), ProfileForm::tr("Couldn't rename the profile to \"%1\"") } } , { IProfileInfo::RenameResult::ProfileAlreadyExists, { ProfileForm::tr("Profile already exists"), ProfileForm::tr("A profile named \"%1\" already exists.") } }, { IProfileInfo::RenameResult::EmptyName, { ProfileForm::tr("Empty name"), ProfileForm::tr("Empty name is unavaliable") } }, }; static const QMap> SAVE_ERROR = { { IProfileInfo::SaveResult::NoWritePermission, { ProfileForm::tr("Location not writable", "Title of permissions popup"), ProfileForm::tr("You do not have permission to write that location. Choose " "another, or cancel the save dialog.", "text of permissions popup") }, }, { IProfileInfo::SaveResult::Error, { ProfileForm::tr("Failed to copy file"), ProfileForm::tr("The file you chose could not be written to.") } }, { IProfileInfo::SaveResult::EmptyPath, { ProfileForm::tr("Empty path"), ProfileForm::tr("Empty path is unavaliable") } }, }; static const QPair CAN_NOT_CHANGE_PASSWORD = { ProfileForm::tr("Couldn't change password"), ProfileForm::tr("Couldn't change password on the database, " "it might be corrupted or use the old password.") }; ProfileForm::ProfileForm(IProfileInfo* profileInfo, QWidget* parent) : QWidget{parent} , qr{nullptr} , profileInfo{profileInfo} { bodyUI = new Ui::IdentitySettings; bodyUI->setupUi(this); const uint32_t maxNameLength = tox_max_name_length(); const QString toolTip = tr("Tox user names cannot exceed %1 characters.").arg(maxNameLength); bodyUI->userNameLabel->setToolTip(toolTip); bodyUI->userName->setMaxLength(static_cast(maxNameLength)); // tox toxId = new ClickableTE(); toxId->setFont(Style::getFont(Style::Small)); toxId->setToolTip(bodyUI->toxId->toolTip()); QVBoxLayout* toxIdGroup = qobject_cast(bodyUI->toxGroup->layout()); delete toxIdGroup->replaceWidget(bodyUI->toxId, toxId); // Original toxId is in heap, delete it bodyUI->toxId->hide(); bodyUI->qrLabel->setWordWrap(true); profilePicture = new MaskablePixmapWidget(this, QSize(64, 64), ":/img/avatar_mask.svg"); profilePicture->setPixmap(QPixmap(":/img/contact_dark.svg")); profilePicture->setContextMenuPolicy(Qt::CustomContextMenu); profilePicture->setClickable(true); profilePicture->setObjectName("selfAvatar"); profilePicture->installEventFilter(this); profilePicture->setAccessibleName("Profile avatar"); profilePicture->setAccessibleDescription("Set a profile avatar shown to all contacts"); profilePicture->setStyleSheet(Style::getStylesheet("window/profile.css")); connect(profilePicture, &MaskablePixmapWidget::clicked, this, &ProfileForm::onAvatarClicked); connect(profilePicture, &MaskablePixmapWidget::customContextMenuRequested, this, &ProfileForm::showProfilePictureContextMenu); QHBoxLayout* publicGrouplayout = qobject_cast(bodyUI->publicGroup->layout()); publicGrouplayout->insertWidget(0, profilePicture); publicGrouplayout->insertSpacing(1, 7); timer.setInterval(750); timer.setSingleShot(true); connect(&timer, &QTimer::timeout, this, [=]() { bodyUI->toxIdLabel->setText(bodyUI->toxIdLabel->text().replace(" ✔", "")); hasCheck = false; }); connect(bodyUI->toxIdLabel, &CroppingLabel::clicked, this, &ProfileForm::copyIdClicked); connect(toxId, &ClickableTE::clicked, this, &ProfileForm::copyIdClicked); profileInfo->connectTo_idChanged(this, [=](const ToxId& id) { setToxId(id); }); connect(bodyUI->userName, &QLineEdit::editingFinished, this, &ProfileForm::onUserNameEdited); connect(bodyUI->statusMessage, &QLineEdit::editingFinished, this, &ProfileForm::onStatusMessageEdited); connect(bodyUI->renameButton, &QPushButton::clicked, this, &ProfileForm::onRenameClicked); connect(bodyUI->exportButton, &QPushButton::clicked, this, &ProfileForm::onExportClicked); connect(bodyUI->deleteButton, &QPushButton::clicked, this, &ProfileForm::onDeleteClicked); connect(bodyUI->logoutButton, &QPushButton::clicked, this, &ProfileForm::onLogoutClicked); connect(bodyUI->deletePassButton, &QPushButton::clicked, this, &ProfileForm::onDeletePassClicked); connect(bodyUI->changePassButton, &QPushButton::clicked, this, &ProfileForm::onChangePassClicked); connect(bodyUI->deletePassButton, &QPushButton::clicked, this, &ProfileForm::setPasswordButtonsText); connect(bodyUI->changePassButton, &QPushButton::clicked, this, &ProfileForm::setPasswordButtonsText); connect(bodyUI->saveQr, &QPushButton::clicked, this, &ProfileForm::onSaveQrClicked); connect(bodyUI->copyQr, &QPushButton::clicked, this, &ProfileForm::onCopyQrClicked); profileInfo->connectTo_usernameChanged( this, [=](const QString& val) { bodyUI->userName->setText(val); }); profileInfo->connectTo_statusMessageChanged( this, [=](const QString& val) { bodyUI->statusMessage->setText(val); }); for (QComboBox* cb : findChildren()) { cb->installEventFilter(this); cb->setFocusPolicy(Qt::StrongFocus); } retranslateUi(); Translator::registerHandler(std::bind(&ProfileForm::retranslateUi, this), this); } void ProfileForm::prFileLabelUpdate() { const QString name = profileInfo->getProfileName(); bodyUI->prFileLabel->setText(tr("Current profile: ") + name + ".tox"); } ProfileForm::~ProfileForm() { Translator::unregister(this); delete qr; delete bodyUI; } bool ProfileForm::isShown() const { if (profilePicture->isVisible()) { window()->windowHandle()->alert(0); return true; } return false; } void ProfileForm::show(ContentLayout* contentLayout) { contentLayout->mainContent->layout()->addWidget(this); QWidget::show(); prFileLabelUpdate(); bool portable = Settings::getInstance().getMakeToxPortable(); QString defaultPath = QDir(Settings::getInstance().getSettingsDirPath()).path().trimmed(); QString appPath = QApplication::applicationDirPath(); QString dirPath = portable ? appPath : defaultPath; QString dirPrLink = tr("Current profile location: %1").arg(QString("
%1").arg(dirPath)); bodyUI->dirPrLink->setText(dirPrLink); bodyUI->dirPrLink->setOpenExternalLinks(true); bodyUI->dirPrLink->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse); bodyUI->dirPrLink->setMaximumSize(bodyUI->dirPrLink->sizeHint()); bodyUI->userName->setFocus(); bodyUI->userName->selectAll(); } bool ProfileForm::eventFilter(QObject* object, QEvent* event) { if (object == static_cast(profilePicture) && event->type() == QEvent::MouseButtonPress) { QMouseEvent* mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::RightButton) return true; } return false; } void ProfileForm::showProfilePictureContextMenu(const QPoint& point) { const QPoint pos = profilePicture->mapToGlobal(point); QMenu contextMenu; const QIcon icon = style()->standardIcon(QStyle::SP_DialogCancelButton); const QAction* removeAction = contextMenu.addAction(icon, tr("Remove")); const QAction* selectedItem = contextMenu.exec(pos); if (selectedItem == removeAction) { profileInfo->removeAvatar(); } } void ProfileForm::copyIdClicked() { profileInfo->copyId(); if (!hasCheck) { bodyUI->toxIdLabel->setText(bodyUI->toxIdLabel->text() + " ✔"); hasCheck = true; } timer.start(); } void ProfileForm::onUserNameEdited() { profileInfo->setUsername(bodyUI->userName->text()); } void ProfileForm::onStatusMessageEdited() { profileInfo->setStatusMessage(bodyUI->statusMessage->text()); } void ProfileForm::onSelfAvatarLoaded(const QPixmap& pic) { profilePicture->setPixmap(pic); } void ProfileForm::setToxId(const ToxId& id) { QString idString = id.toString(); static const QString ToxIdColor = QStringLiteral("%1" "%2" "%3"); toxId->setText(ToxIdColor .arg(idString.mid(0, 64)) .arg(idString.mid(64, 8)) .arg(idString.mid(72, 4))); delete qr; qr = new QRWidget(); qr->setQRData("tox:" + idString); bodyUI->qrCode->setPixmap(QPixmap::fromImage(qr->getImage()->scaledToWidth(150))); } QString ProfileForm::getSupportedImageFilter() { QString res; for (auto type : QImageReader::supportedImageFormats()) { res += QString("*.%1 ").arg(QString(type)); } return tr("Images (%1)", "filetype filter").arg(res.left(res.size() - 1)); } void ProfileForm::onAvatarClicked() { const QString filter = getSupportedImageFilter(); const QString path = QFileDialog::getOpenFileName(Q_NULLPTR, tr("Choose a profile picture"), QDir::homePath(), filter, nullptr); if (path.isEmpty()) { return; } const IProfileInfo::SetAvatarResult result = profileInfo->setAvatar(path); if (result == IProfileInfo::SetAvatarResult::OK) { return; } GUI::showError(tr("Error"), SET_AVATAR_ERROR[result]); } void ProfileForm::onRenameClicked() { const QString cur = profileInfo->getProfileName(); const QString title = tr("Rename \"%1\"", "renaming a profile").arg(cur); const QString name = QInputDialog::getText(this, title, title + ":"); if (name.isEmpty()) { return; } const IProfileInfo::RenameResult result = profileInfo->renameProfile(name); if (result == IProfileInfo::RenameResult::OK) { return; } const QPair error = RENAME_ERROR[result]; GUI::showError(error.first, error.second.arg(name)); prFileLabelUpdate(); } void ProfileForm::onExportClicked() { const QString current = profileInfo->getProfileName() + Core::TOX_EXT; //:save dialog title const QString path = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Export profile"), current, //: save dialog filter tr("Tox save file (*.tox)")); if (path.isEmpty()) { return; } const IProfileInfo::SaveResult result = profileInfo->exportProfile(path); if (result == IProfileInfo::SaveResult::OK) { return; } const QPair error = SAVE_ERROR[result]; GUI::showWarning(error.first, error.second); } void ProfileForm::onDeleteClicked() { const QString title = tr("Really delete profile?", "deletion confirmation title"); const QString question = tr("Are you sure you want to delete this profile?", "deletion confirmation text"); if (!GUI::askQuestion(title, question)) { return; } const QStringList manualDeleteFiles = profileInfo->removeProfile(); if (manualDeleteFiles.empty()) { return; } //: deletion failed text part 1 QString message = tr("The following files could not be deleted:") + "\n\n"; for (const QString& file : manualDeleteFiles) { message += file + "\n"; } //: deletion failed text part 2 message += "\n" + tr("Please manually remove them."); GUI::showError(tr("Files could not be deleted!", "deletion failed title"), message); } void ProfileForm::onLogoutClicked() { profileInfo->logout(); } void ProfileForm::setPasswordButtonsText() { if (profileInfo->isEncrypted()) { bodyUI->changePassButton->setText(tr("Change password", "button text")); bodyUI->deletePassButton->setVisible(true); } else { bodyUI->changePassButton->setText(tr("Set profile password", "button text")); bodyUI->deletePassButton->setVisible(false); } } void ProfileForm::onCopyQrClicked() { profileInfo->copyQr(*qr->getImage()); } void ProfileForm::onSaveQrClicked() { const QString current = profileInfo->getProfileName() + ".png"; const QString path = QFileDialog::getSaveFileName( Q_NULLPTR, tr("Save", "save qr image"), current, tr("Save QrCode (*.png)", "save dialog filter")); if (path.isEmpty()) { return; } const IProfileInfo::SaveResult result = profileInfo->saveQr(*qr->getImage(), path); if (result == IProfileInfo::SaveResult::OK) { return; } const QPair error = SAVE_ERROR[result]; GUI::showWarning(error.first, error.second); } void ProfileForm::onDeletePassClicked() { if (!profileInfo->isEncrypted()) { GUI::showInfo(tr("Nothing to remove"), tr("Your profile does not have a password!")); return; } const QString title = tr("Really delete password?", "deletion confirmation title"); //: deletion confirmation text const QString body = tr("Are you sure you want to delete your password?"); if (!GUI::askQuestion(title, body)) { return; } if (!profileInfo->deletePassword()) { GUI::showInfo(CAN_NOT_CHANGE_PASSWORD.first, CAN_NOT_CHANGE_PASSWORD.second); } } void ProfileForm::onChangePassClicked() { const QString title = tr("Please enter a new password."); SetPasswordDialog* dialog = new SetPasswordDialog(title, QString{}, nullptr); if (dialog->exec() == QDialog::Rejected) { return; } QString newPass = dialog->getPassword(); if (!profileInfo->setPassword(newPass)) { GUI::showInfo(CAN_NOT_CHANGE_PASSWORD.first, CAN_NOT_CHANGE_PASSWORD.second); } } void ProfileForm::retranslateUi() { bodyUI->retranslateUi(this); setPasswordButtonsText(); // We have to add the toxId tooltip here and not in the .ui or Qt won't know how to translate it // dynamically toxId->setToolTip(tr("This bunch of characters tells other Tox clients how to contact " "you.\nShare it with your friends to communicate.\n\n" "This ID includes the NoSpam code (in blue), and the checksum (in gray).")); } qTox/src/widget/form/profileform.h000066400000000000000000000047151415623743500175440ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IDENTITYFORM_H #define IDENTITYFORM_H #include "src/widget/qrwidget.h" #include #include #include #include class ContentLayout; class CroppingLabel; class IProfileInfo; class MaskablePixmapWidget; namespace Ui { class IdentitySettings; } class ToxId; class ClickableTE : public QLabel { Q_OBJECT public: signals: void clicked(); protected: virtual void mouseReleaseEvent(QMouseEvent*) final override { emit clicked(); } }; class ProfileForm : public QWidget { Q_OBJECT public: ProfileForm(IProfileInfo* profileInfo, QWidget* parent = nullptr); ~ProfileForm(); virtual void show() final { } void show(ContentLayout* contentLayout); bool isShown() const; public slots: void onSelfAvatarLoaded(const QPixmap& pic); void onLogoutClicked(); private slots: void setPasswordButtonsText(); void setToxId(const ToxId& id); void copyIdClicked(); void onUserNameEdited(); void onStatusMessageEdited(); void onRenameClicked(); void onExportClicked(); void onDeleteClicked(); void onCopyQrClicked(); void onSaveQrClicked(); void onDeletePassClicked(); void onChangePassClicked(); void onAvatarClicked(); void showProfilePictureContextMenu(const QPoint& point); private: void retranslateUi(); void prFileLabelUpdate(); bool eventFilter(QObject* object, QEvent* event); void refreshProfiles(); static QString getSupportedImageFilter(); private: Ui::IdentitySettings* bodyUI; MaskablePixmapWidget* profilePicture; QTimer timer; bool hasCheck = false; QRWidget* qr; ClickableTE* toxId; IProfileInfo* profileInfo; }; #endif qTox/src/widget/form/profileform.ui000066400000000000000000000400411415623743500177220ustar00rootroot00000000000000 IdentitySettings 0 0 574 659 Form 0 0 0 0 0 true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 0 0 604 853 9 0 0 4 0 6 0 0 0 60 40 :/img/settings/identity.png true 0 0 75 true My profile 0 0 0 5 Qt::Horizontal Public Information 6 4 My name: Name input Name visible to contacts My status: Status message input Status message visible to contacts Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Your Tox ID (click to copy) Your Tox ID Qt::RichText Qt::AlignCenter This QR code contains your Tox ID. You may share this with your friends as well. Save QR image as file Save image Copy QR image to clipboard Copy image Profile PointingHandCursor Qt::ClickFocus {Current profile location} Qt::RichText false false Qt::TextSelectableByMouse Qt::Horizontal 40 20 Rename profile. Rename profile. Rename Delete profile. Delete profile. Delete Allows you to export your Tox profile to a file. Profile does not contain your history. Export profile Export Go back to the login screen Logout Qt::Horizontal 40 20 Qt::Horizontal 40 20 Remove your password and encryption from your profile. Remove password from profile Remove password Change profile password Change password Qt::Horizontal 40 20 Qt::Vertical 20 40 CroppingLabel QLabel
src/widget/tool/croppinglabel.h
scrollArea userName statusMessage toxId saveQr copyQr renameButton deleteButton exportButton logoutButton deletePassButton changePassButton
qTox/src/widget/form/removefrienddialog.ui000066400000000000000000000042771415623743500212560ustar00rootroot00000000000000 RemoveFriendDialog 0 0 300 110 300 110 Remove friend {Text message} Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Remove all chat history with the friend if set Also remove chat history Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() RemoveFriendDialog accept() 248 254 157 274 buttonBox rejected() RemoveFriendDialog reject() 316 260 286 274 qTox/src/widget/form/searchsettingsform.cpp000066400000000000000000000125051415623743500214610ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "searchsettingsform.h" #include "ui_searchsettingsform.h" #include "src/persistence/settings.h" #include "src/widget/style.h" #include "src/widget/form/loadhistorydialog.h" SearchSettingsForm::SearchSettingsForm(QWidget *parent) : QWidget(parent), ui(new Ui::SearchSettingsForm) { ui->setupUi(this); ui->choiceDateButton->setEnabled(false); ui->startDateLabel->setEnabled(false); ui->choiceDateButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->choiceDateButton->setObjectName(QStringLiteral("choiceDateButton")); reloadTheme(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) connect(ui->startSearchComboBox, QOverload::of(&QComboBox::currentIndexChanged), #else connect(ui->startSearchComboBox, static_cast(&QComboBox::currentIndexChanged), #endif this, &SearchSettingsForm::onStartSearchSelected); connect(ui->registerCheckBox, &QCheckBox::clicked, this, &SearchSettingsForm::onRegisterClicked); connect(ui->wordsOnlyRadioButton, &QCheckBox::clicked, this, &SearchSettingsForm::onWordsOnlyClicked); connect(ui->regularRadioButton, &QCheckBox::clicked, this, &SearchSettingsForm::onRegularClicked); connect(ui->choiceDateButton, &QPushButton::clicked, this, &SearchSettingsForm::onChoiceDate); } SearchSettingsForm::~SearchSettingsForm() { delete ui; } ParameterSearch SearchSettingsForm::getParameterSearch() { ParameterSearch ps; if (ui->regularRadioButton->isChecked()) { ps.filter = FilterSearch::Regular; } else if (ui->registerCheckBox->isChecked() && ui->wordsOnlyRadioButton->isChecked()) { ps.filter = FilterSearch::RegisterAndWordsOnly; } else if (ui->registerCheckBox->isChecked() && ui->regularRadioButton->isChecked()) { ps.filter = FilterSearch::RegisterAndRegular; } else if (ui->registerCheckBox->isChecked()) { ps.filter = FilterSearch::Register; } else if (ui->wordsOnlyRadioButton->isChecked()) { ps.filter = FilterSearch::WordsOnly; } else { ps.filter = FilterSearch::None; } switch (ui->startSearchComboBox->currentIndex()) { case 0: ps.period = PeriodSearch::WithTheEnd; break; case 1: ps.period = PeriodSearch::WithTheFirst; break; case 2: ps.period = PeriodSearch::AfterDate; break; case 3: ps.period = PeriodSearch::BeforeDate; break; default: ps.period = PeriodSearch::WithTheEnd; break; } ps.date = startDate; ps.isUpdate = isUpdate; isUpdate = false; return ps; } void SearchSettingsForm::reloadTheme() { ui->choiceDateButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); ui->startDateLabel->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/labels.css"))); } void SearchSettingsForm::updateStartDateLabel() { ui->startDateLabel->setText(startDate.toString(Settings::getInstance().getDateFormat())); } void SearchSettingsForm::setUpdate(const bool isUpdate) { this->isUpdate = isUpdate; emit updateSettings(isUpdate); } void SearchSettingsForm::onStartSearchSelected(const int index) { if (index > 1) { ui->choiceDateButton->setEnabled(true); ui->startDateLabel->setEnabled(true); ui->choiceDateButton->setProperty("state", QStringLiteral("green")); ui->choiceDateButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); if (startDate.isNull()) { startDate = QDate::currentDate(); updateStartDateLabel(); } } else { ui->choiceDateButton->setEnabled(false); ui->startDateLabel->setEnabled(false); ui->choiceDateButton->setProperty("state", QString()); ui->choiceDateButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); } setUpdate(true); } void SearchSettingsForm::onRegisterClicked(const bool checked) { Q_UNUSED(checked) setUpdate(true); } void SearchSettingsForm::onWordsOnlyClicked(const bool checked) { if (checked) { ui->regularRadioButton->setChecked(false); } setUpdate(true); } void SearchSettingsForm::onRegularClicked(const bool checked) { if (checked) { ui->wordsOnlyRadioButton->setChecked(false); } setUpdate(true); } void SearchSettingsForm::onChoiceDate() { LoadHistoryDialog dlg; dlg.setTitle(tr("Select Date Dialog")); dlg.setInfoLabel(tr("Select a date")); if (dlg.exec()) { startDate = dlg.getFromDate().date(); updateStartDateLabel(); } setUpdate(true); } qTox/src/widget/form/searchsettingsform.h000066400000000000000000000031401415623743500211210ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SEARCHSETTINGSFORM_H #define SEARCHSETTINGSFORM_H #include #include "src/widget/searchtypes.h" namespace Ui { class SearchSettingsForm; } class SearchSettingsForm : public QWidget { Q_OBJECT public: explicit SearchSettingsForm(QWidget *parent = nullptr); ~SearchSettingsForm(); ParameterSearch getParameterSearch(); void reloadTheme(); private: Ui::SearchSettingsForm *ui; QDate startDate; bool isUpdate{false}; void updateStartDateLabel(); void setUpdate(const bool isUpdate); private slots: void onStartSearchSelected(const int index); void onRegisterClicked(const bool checked); void onWordsOnlyClicked(const bool checked); void onRegularClicked(const bool checked); void onChoiceDate(); signals: void updateSettings(const bool isUpdate); }; #endif // SEARCHSETTINGSFORM_H qTox/src/widget/form/searchsettingsform.ui000066400000000000000000000105621415623743500213150ustar00rootroot00000000000000 SearchSettingsForm 0 0 473 84 0 0 Form 0 0 0 0 QFrame::Plain Qt::Horizontal Start search: 1 0 from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only true false false false Use regular expressions true false false Qt::Horizontal 40 20 QFrame::Plain Qt::Horizontal qTox/src/widget/form/setpassworddialog.cpp000066400000000000000000000071011415623743500213010ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "setpassworddialog.h" #include "ui_setpassworddialog.h" #include #include const double SetPasswordDialog::reasonablePasswordLength = 8.; SetPasswordDialog::SetPasswordDialog(QString body, QString extraButton, QWidget* parent) : QDialog(parent) , ui(new Ui::SetPasswordDialog) , body(body + "\n\n") { ui->setupUi(this); connect(ui->passwordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit())); connect(ui->repasswordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit())); ui->body->setText(body + "\n\n"); QPushButton* ok = ui->buttonBox->button(QDialogButtonBox::Ok); ok->setEnabled(false); ok->setText(QApplication::tr("Ok")); QPushButton* cancel = ui->buttonBox->button(QDialogButtonBox::Cancel); cancel->setText(QApplication::tr("Cancel")); if (!extraButton.isEmpty()) { QPushButton* third = new QPushButton(extraButton); ui->buttonBox->addButton(third, QDialogButtonBox::YesRole); connect(third, &QPushButton::clicked, this, [&]() { this->done(Tertiary); }); } } SetPasswordDialog::~SetPasswordDialog() { delete ui; } void SetPasswordDialog::onPasswordEdit() { QString pswd = ui->passwordlineEdit->text(); if (pswd.isEmpty()) { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->body->setText(body); } else if (pswd.length() < 6) { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->body->setText(body + tr("The password is too short")); } else if (pswd != ui->repasswordlineEdit->text()) { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->body->setText(body + tr("The password doesn't match.")); } else { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); ui->body->setText(body); } ui->passStrengthMeter->setValue(getPasswordStrength(pswd)); } int SetPasswordDialog::getPasswordStrength(QString pass) { if (pass.size() < 6) return 0; double fscore = 0; QHash charCounts; for (QChar c : pass) { charCounts[c]++; fscore += 5. / charCounts[c]; } int variations = -1; variations += pass.contains(QRegExp("[0-9]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0; variations += pass.contains(QRegExp("[a-z]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0; variations += pass.contains(QRegExp("[A-Z]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0; variations += pass.contains(QRegExp("[\\W]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0; int score = fscore; score += variations * 10; score -= 20; score = std::min(score, 100); score = std::max(score, 0); return score; } QString SetPasswordDialog::getPassword() { return ui->passwordlineEdit->text(); } qTox/src/widget/form/setpassworddialog.h000066400000000000000000000026671415623743500207620ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SETPASSWORDDIALOG_H #define SETPASSWORDDIALOG_H #include namespace Ui { class SetPasswordDialog; } class SetPasswordDialog : public QDialog { Q_OBJECT public: enum ReturnCode { Rejected = QDialog::Rejected, Accepted = QDialog::Accepted, Tertiary }; explicit SetPasswordDialog(QString body, QString extraButton, QWidget* parent = nullptr); ~SetPasswordDialog(); QString getPassword(); static int getPasswordStrength(QString pass); private slots: void onPasswordEdit(); private: Ui::SetPasswordDialog* ui; QString body; static const double reasonablePasswordLength; }; #endif // SETPASSWORDDIALOG_H qTox/src/widget/form/setpassworddialog.ui000066400000000000000000000074501415623743500211430ustar00rootroot00000000000000 SetPasswordDialog 0 0 255 176 Set your password true Confirm: Qt::AlignLeading Password: Qt::AlignLeading Confirm password Confirm password input Password input Password input field, minimum 6 characters long 0 Password strength: %p% Qt::Vertical 20 40 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok false PasswordEdit QLineEdit
src/widget/passwordedit.h
passwordlineEdit repasswordlineEdit buttonBox accepted() SetPasswordDialog accept() 248 254 157 274 buttonBox rejected() SetPasswordDialog reject() 316 260 286 274
qTox/src/widget/form/settings/000077500000000000000000000000001415623743500167005ustar00rootroot00000000000000qTox/src/widget/form/settings/aboutform.cpp000066400000000000000000000173461415623743500214150ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "aboutform.h" #include "ui_aboutsettings.h" #include "src/net/updatecheck.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/widget/style.h" #include "src/widget/tool/recursivesignalblocker.h" #include "src/widget/translator.h" #include #include #include #include #include #include // index of UI in the QStackedWidget enum class updateIndex { available = 0, upToDate = 1, failed = 2 }; /** * @class AboutForm * * This form contains information about qTox and libraries versions, external * links and licence text. Shows progress during an update. */ /** * @brief Constructor of AboutForm. */ AboutForm::AboutForm(UpdateCheck* updateCheck) : GenericForm(QPixmap(":/img/settings/general.png")) , bodyUI(new Ui::AboutSettings) , progressTimer(new QTimer(this)) , updateCheck(updateCheck) { bodyUI->setupUi(this); // block all child signals during initialization const RecursiveSignalBlocker signalBlocker(this); replaceVersions(); if (QString(GIT_VERSION).indexOf(" ") > -1) bodyUI->gitVersion->setOpenExternalLinks(false); eventsInit(); Translator::registerHandler(std::bind(&AboutForm::retranslateUi, this), this); } /** * @brief Update versions and links. * * Update commit hash if built with git, show author and known issues info * It also updates qTox, toxcore and Qt versions. */ void AboutForm::replaceVersions() { // TODO: When we finally have stable releases: build-in a way to tell // nightly builds from stable releases. QString TOXCORE_VERSION = QString::number(tox_version_major()) + "." + QString::number(tox_version_minor()) + "." + QString::number(tox_version_patch()); bodyUI->youAreUsing->setText(tr("You are using qTox version %1.").arg(QString(GIT_DESCRIBE))); #if UPDATE_CHECK_ENABLED if (updateCheck != nullptr) { connect(updateCheck, &UpdateCheck::updateAvailable, this, &AboutForm::onUpdateAvailable); connect(updateCheck, &UpdateCheck::upToDate, this, &AboutForm::onUpToDate); connect(updateCheck, &UpdateCheck::updateCheckFailed, this, &AboutForm::onUpdateCheckFailed); } else { qWarning() << "AboutForm passed null UpdateCheck!"; } #else qDebug() << "AboutForm not showing updates, qTox built without UPDATE_CHECK"; #endif QString commitLink = "https://github.com/qTox/qTox/commit/" + QString(GIT_VERSION); bodyUI->gitVersion->setText( tr("Commit hash: %1").arg(createLink(commitLink, QString(GIT_VERSION)))); bodyUI->toxCoreVersion->setText(tr("toxcore version: %1").arg(TOXCORE_VERSION)); bodyUI->qtVersion->setText(tr("Qt version: %1").arg(QT_VERSION_STR)); QString issueBody = QString("##### Brief Description\n\n" "OS: %1\n" "qTox version: %2\n" "Commit hash: %3\n" "toxcore: %4\n" "Qt: %5\n" "Hardware: \n…\n\n" "Reproducible: Always / Almost Always / Sometimes" " / Rarely / Couldn't Reproduce\n\n" "##### Steps to reproduce\n\n" "1. \n2. \n3. …\n\n" "##### Observed Behavior\n\n\n" "##### Expected Behavior\n\n\n" "##### Additional Info\n" "(links, images, etc go here)\n\n" "----\n\n" "More information on how to write good bug reports in the wiki: " "https://github.com/qTox/qTox/wiki/Writing-Useful-Bug-Reports.\n\n" "Please remove any unnecessary template section before submitting.") .arg(QSysInfo::prettyProductName(), GIT_DESCRIBE, GIT_VERSION, TOXCORE_VERSION, QT_VERSION_STR); issueBody.replace("#", "%23").replace(":", "%3A"); bodyUI->knownIssues->setText( tr("A list of all known issues may be found at our %1 at Github." " If you discover a bug or security vulnerability within" " qTox, please report it according to the guidelines in our" " %2 wiki article.", "`%1` is replaced by translation of `bug tracker`" "\n`%2` is replaced by translation of `Writing Useful Bug Reports`") .arg(createLink("https://github.com/qTox/qTox/issues", tr("bug-tracker", "Replaces `%1` in the `A list of all known…`"))) .arg(createLink("https://github.com/qTox/qTox/wiki/Writing-Useful-Bug-Reports", tr("Writing Useful Bug Reports", "Replaces `%2` in the `A list of all known…`")))); bodyUI->clickToReport->setText( createLink("https://github.com/qTox/qTox/issues/new?body=" + QUrl(issueBody).toEncoded(), QString("%1").arg(tr("Click here to report a bug.")))); QString authorInfo = QString("

%1

%2

") .arg(tr("Original author: %1").arg(createLink("https://github.com/tux3", "tux3"))) .arg( tr("See a full list of %1 at Github", "`%1` is replaced with translation of word `contributors`") .arg(createLink("https://qtox.github.io/gitstats/authors.html", tr("contributors", "Replaces `%1` in `See a full list of…`")))); bodyUI->authorInfo->setText(authorInfo); } void AboutForm::onUpdateAvailable(QString latestVersion, QUrl link) { QObject::disconnect(linkConnection); linkConnection = connect(bodyUI->updateAvailableButton, &QPushButton::clicked, [link]() { QDesktopServices::openUrl(link); }); bodyUI->updateStack->setCurrentIndex(static_cast(updateIndex::available)); } void AboutForm::onUpToDate() { bodyUI->updateStack->setCurrentIndex(static_cast(updateIndex::upToDate)); } void AboutForm::onUpdateCheckFailed() { bodyUI->updateStack->setCurrentIndex(static_cast(updateIndex::failed)); } /** * @brief Creates hyperlink with specific style. * @param path The URL of the page the link goes to. * @param text Text, which will be clickable. * @return Hyperlink to paste. */ QString AboutForm::createLink(QString path, QString text) const { return QString::fromUtf8( "%3") .arg(path, Style::getColor(Style::Link).name(), text); } AboutForm::~AboutForm() { Translator::unregister(this); delete bodyUI; } /** * @brief Retranslate all elements in the form. */ void AboutForm::retranslateUi() { bodyUI->retranslateUi(this); replaceVersions(); } qTox/src/widget/form/settings/aboutform.h000066400000000000000000000031161415623743500210500ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ABOUTFORM_H #define ABOUTFORM_H #include "genericsettings.h" #include class Core; class QTimer; class QString; class UpdateCheck; class QLayoutItem; namespace Ui { class AboutSettings; } class AboutForm : public GenericForm { Q_OBJECT public: AboutForm(UpdateCheck* updateCheck); ~AboutForm(); virtual QString getFormName() final override { return tr("About"); } public slots: void onUpdateAvailable(QString latestVersion, QUrl link); void onUpToDate(); void onUpdateCheckFailed(); private: void retranslateUi(); void replaceVersions(); inline QString createLink(QString path, QString text) const; private: Ui::AboutSettings* bodyUI; QTimer* progressTimer; UpdateCheck* updateCheck; QMetaObject::Connection linkConnection; }; #endif // ABOUTFORM_H qTox/src/widget/form/settings/aboutsettings.ui000066400000000000000000000406661415623743500221460ustar00rootroot00000000000000 AboutSettings 0 0 530 553 0 0 Form true 0 0 494 551 0 0 0 0 Version 0 0 Sans Serif true {TOXCOREVERSION} Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse Noto Sans {QTVERSION} Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse 0 0 Sans Serif true {GIT_VERSION} true Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse 0 0 Sans Serif QFrame::NoFrame {GIT_DESCRIBE} -1 true Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse 2 Qt::Horizontal 0 0 Open update download link Update available Qt::Horizontal 40 20 0 0 qTox is up to date ✓ Qt::Horizontal 0 1 License 0 0 0 0 Noto Sans Qt::NoFocus Qt::NoContextMenu Qt::LeftToRight <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">Copyright © 2014-2020 by The qTox Project Contributors</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> true true 0 0 0 0 Authors 0 0 Sans Serif {AUTHOR INFO} true Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse 0 0 0 0 Known Issues 0 0 Sans Serif {KNOWN ISSUES TEXT} true true Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse {CLICK TO REPORT TEXT} true true Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse VerticalOnlyScroller QScrollArea
src/widget/form/settings/verticalonlyscroller.h
1
qTox/src/widget/form/settings/advancedform.cpp000066400000000000000000000162561415623743500220470ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "advancedform.h" #include "ui_advancedsettings.h" #include #include #include #include #include #include #include "src/model/status.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/widget/gui.h" #include "src/widget/tool/recursivesignalblocker.h" #include "src/widget/translator.h" /** * @class AdvancedForm * * This form contains all connection settings. * Is also contains "Reset settings" button and "Make portable" checkbox. */ AdvancedForm::AdvancedForm() : GenericForm(QPixmap(":/img/settings/general.png")) , bodyUI(new Ui::AdvancedSettings) { bodyUI->setupUi(this); // block all child signals during initialization const RecursiveSignalBlocker signalBlocker(this); Settings& s = Settings::getInstance(); bodyUI->cbEnableIPv6->setChecked(s.getEnableIPv6()); bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable()); bodyUI->proxyAddr->setText(s.getProxyAddr()); quint16 port = s.getProxyPort(); if (port > 0) { bodyUI->proxyPort->setValue(port); } int index = static_cast(s.getProxyType()); bodyUI->proxyType->setCurrentIndex(index); on_proxyType_currentIndexChanged(index); const bool udpEnabled = !s.getForceTCP() && (s.getProxyType() == Settings::ProxyType::ptNone); bodyUI->cbEnableUDP->setChecked(udpEnabled); bodyUI->cbEnableLanDiscovery->setChecked(s.getEnableLanDiscovery() && udpEnabled); bodyUI->cbEnableLanDiscovery->setEnabled(udpEnabled); QString warningBody = tr("Unless you %1 know what you are doing, " "please do %2 change anything here. Changes " "made here may lead to problems with qTox, and even " "to loss of your data, e.g. history." "%3") .arg(QString("%1").arg(tr("really"))) .arg(QString("%1").arg(tr("not"))) .arg(QString("

%1

").arg(tr("Changes here are applied only after restarting qTox."))); QString warning = QString("
" "

%1

%2

") .arg(tr("IMPORTANT NOTE")) .arg(warningBody); bodyUI->warningLabel->setText(warning); eventsInit(); Translator::registerHandler(std::bind(&AdvancedForm::retranslateUi, this), this); } AdvancedForm::~AdvancedForm() { Translator::unregister(this); delete bodyUI; } void AdvancedForm::on_cbMakeToxPortable_stateChanged() { Settings::getInstance().setMakeToxPortable(bodyUI->cbMakeToxPortable->isChecked()); } void AdvancedForm::on_btnExportLog_clicked() { QString savefile = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Save File"), QString{}, tr("Logs (*.log)")); if (savefile.isNull() || savefile.isEmpty()) { qDebug() << "Debug log save file was not properly chosen"; return; } QString logFileDir = Settings::getInstance().getAppCacheDirPath(); QString logfile = logFileDir + "qtox.log"; QFile file(logfile); if (file.exists()) { qDebug() << "Found debug log for copying"; } else { qDebug() << "No debug file found"; return; } if (QFile::copy(logfile, savefile)) qDebug() << "Successfully copied to: " << savefile; else qDebug() << "File was not copied"; } void AdvancedForm::on_btnCopyDebug_clicked() { QString logFileDir = Settings::getInstance().getAppCacheDirPath(); QString logfile = logFileDir + "qtox.log"; QFile file(logfile); if (!file.exists()) { qDebug() << "No debug file found"; return; } QClipboard* clipboard = QApplication::clipboard(); if (clipboard) { QString debugtext; if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&file); debugtext = in.readAll(); file.close(); } else { qDebug() << "Unable to open file for copying to clipboard"; return; } clipboard->setText(debugtext, QClipboard::Clipboard); qDebug() << "Debug log copied to clipboard"; } else { qDebug() << "Unable to access clipboard"; } } void AdvancedForm::on_resetButton_clicked() { const QString titile = tr("Reset settings"); bool result = GUI::askQuestion(titile, tr("All settings will be reset to default. Are you sure?"), tr("Yes"), tr("No")); if (!result) return; Settings::getInstance().resetToDefault(); GUI::showInfo(titile, "Changes will take effect after restart"); } void AdvancedForm::on_cbEnableIPv6_stateChanged() { Settings::getInstance().setEnableIPv6(bodyUI->cbEnableIPv6->isChecked()); } void AdvancedForm::on_cbEnableUDP_stateChanged() { const bool enableUdp = bodyUI->cbEnableUDP->isChecked(); Settings::getInstance().setForceTCP(!enableUdp); const bool enableLanDiscovery = Settings::getInstance().getEnableLanDiscovery(); bodyUI->cbEnableLanDiscovery->setEnabled(enableUdp); bodyUI->cbEnableLanDiscovery->setChecked(enableUdp && enableLanDiscovery); } void AdvancedForm::on_cbEnableLanDiscovery_stateChanged() { Settings::getInstance().setEnableLanDiscovery(bodyUI->cbEnableLanDiscovery->isChecked()); } void AdvancedForm::on_proxyAddr_editingFinished() { Settings::getInstance().setProxyAddr(bodyUI->proxyAddr->text()); } void AdvancedForm::on_proxyPort_valueChanged(int port) { if (port <= 0) { port = 0; } Settings::getInstance().setProxyPort(port); } void AdvancedForm::on_proxyType_currentIndexChanged(int index) { Settings::ProxyType proxytype = static_cast(index); const bool proxyEnabled = proxytype != Settings::ProxyType::ptNone; bodyUI->proxyAddr->setEnabled(proxyEnabled); bodyUI->proxyPort->setEnabled(proxyEnabled); // enabling UDP and proxy can be a privacy issue bodyUI->cbEnableUDP->setEnabled(!proxyEnabled); bodyUI->cbEnableUDP->setChecked(!proxyEnabled); Settings::getInstance().setProxyType(proxytype); } /** * @brief Retranslate all elements in the form. */ void AdvancedForm::retranslateUi() { int proxyType = bodyUI->proxyType->currentIndex(); bodyUI->retranslateUi(this); bodyUI->proxyType->setCurrentIndex(proxyType); } qTox/src/widget/form/settings/advancedform.h000066400000000000000000000032351415623743500215050ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ADVANCEDFORM_H #define ADVANCEDFORM_H #include "genericsettings.h" class Core; namespace Ui { class AdvancedSettings; } class AdvancedForm : public GenericForm { Q_OBJECT public: AdvancedForm(); ~AdvancedForm(); virtual QString getFormName() final override { return tr("Advanced"); } private slots: // Portable void on_cbMakeToxPortable_stateChanged(); void on_resetButton_clicked(); // Debug void on_btnCopyDebug_clicked(); void on_btnExportLog_clicked(); // Connection void on_cbEnableIPv6_stateChanged(); void on_cbEnableUDP_stateChanged(); void on_cbEnableLanDiscovery_stateChanged(); void on_proxyAddr_editingFinished(); void on_proxyPort_valueChanged(int port); void on_proxyType_currentIndexChanged(int index); private: void retranslateUi(); private: Ui::AdvancedSettings* bodyUI; }; #endif // ADVANCEDFORM_H qTox/src/widget/form/settings/advancedsettings.ui000066400000000000000000000204111415623743500225630ustar00rootroot00000000000000 AdvancedSettings 0 0 505 565 Form true 0 0 489 549 {IMPORTANT NOTE HERE} Qt::RichText Qt::AlignCenter true Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse Portable Save settings to the working directory instead of the usual conf dir Make Tox portable Debug Export Debug Log Copy Debug Log Connection Settings Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 9 Enable IPv6 (recommended) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. Enable UDP (recommended) 40 Enable LAN discovery Proxy type: 0 65535 Address: Port: 0 0 None SOCKS5 HTTP Reset to default settings Qt::Vertical 20 40 VerticalOnlyScroller QScrollArea
src/widget/form/settings/verticalonlyscroller.h
1
scrollArea cbMakeToxPortable btnExportLog btnCopyDebug cbEnableIPv6 cbEnableUDP proxyType proxyAddr proxyPort resetButton
qTox/src/widget/form/settings/avform.cpp000066400000000000000000000517211415623743500207040ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "avform.h" #include #include #include #include #include #include #include "src/audio/audio.h" #include "src/audio/iaudiosettings.h" #include "src/audio/iaudiosource.h" #include "src/core/core.h" #include "src/core/coreav.h" #include "src/video/cameradevice.h" #include "src/video/camerasource.h" #include "src/video/ivideosettings.h" #include "src/video/videosurface.h" #include "src/widget/tool/recursivesignalblocker.h" #include "src/widget/tool/screenshotgrabber.h" #include "src/widget/translator.h" #ifndef ALC_ALL_DEVICES_SPECIFIER #define ALC_ALL_DEVICES_SPECIFIER ALC_DEVICE_SPECIFIER #endif AVForm::AVForm(IAudioControl& audio, CoreAV* coreAV, CameraSource& camera, IAudioSettings* audioSettings, IVideoSettings* videoSettings) : GenericForm(QPixmap(":/img/settings/av.png")) , audio(audio) , coreAV{coreAV} , audioSettings{audioSettings} , videoSettings{videoSettings} , camVideoSurface(nullptr) , camera(camera) { setupUi(this); // block all child signals during initialization const RecursiveSignalBlocker signalBlocker(this); cbEnableTestSound->setChecked(audioSettings->getEnableTestSound()); cbEnableTestSound->setToolTip(tr("Play a test sound while changing the output volume.")); connect(rescanButton, &QPushButton::clicked, this, &AVForm::rescanDevices); playbackSlider->setTracking(false); playbackSlider->setMaximum(totalSliderSteps); playbackSlider->setValue(getStepsFromValue(audioSettings->getOutVolume(), audioSettings->getOutVolumeMin(), audioSettings->getOutVolumeMax())); playbackSlider->installEventFilter(this); microphoneSlider->setToolTip(tr("Use slider to set the gain of your input device ranging" " from %1dB to %2dB.") .arg(audio.minInputGain()) .arg(audio.maxInputGain())); microphoneSlider->setMaximum(totalSliderSteps); microphoneSlider->setTickPosition(QSlider::TicksBothSides); static const int numTicks = 4; microphoneSlider->setTickInterval(totalSliderSteps / numTicks); microphoneSlider->setTracking(false); microphoneSlider->installEventFilter(this); microphoneSlider->setValue( getStepsFromValue(audio.inputGain(), audio.minInputGain(), audio.maxInputGain())); audioThresholdSlider->setToolTip(tr("Use slider to set the activation volume for your" " input device.")); audioThresholdSlider->setMaximum(totalSliderSteps); audioThresholdSlider->setValue(getStepsFromValue(audioSettings->getAudioThreshold(), audio.minInputThreshold(), audio.maxInputThreshold())); audioThresholdSlider->setTracking(false); audioThresholdSlider->installEventFilter(this); volumeDisplay->setMaximum(totalSliderSteps); fillAudioQualityComboBox(); eventsInit(); QDesktopWidget* desktop = QApplication::desktop(); for (QScreen* qScreen : QGuiApplication::screens()) { connect(qScreen, &QScreen::geometryChanged, this, &AVForm::rescanDevices); } auto* qGUIApp = qobject_cast(qApp); assert (qGUIApp); connect(qGUIApp, &QGuiApplication::screenAdded, this, &AVForm::trackNewScreenGeometry); connect(qGUIApp, &QGuiApplication::screenAdded, this, &AVForm::rescanDevices); connect(qGUIApp, &QGuiApplication::screenRemoved, this, &AVForm::rescanDevices); Translator::registerHandler(std::bind(&AVForm::retranslateUi, this), this); } AVForm::~AVForm() { killVideoSurface(); Translator::unregister(this); } void AVForm::hideEvent(QHideEvent* event) { audioSink.reset(); audioSrc.reset(); if (camVideoSurface) { camVideoSurface->setSource(nullptr); killVideoSurface(); } videoDeviceList.clear(); GenericForm::hideEvent(event); } void AVForm::showEvent(QShowEvent* event) { getAudioOutDevices(); getAudioInDevices(); createVideoSurface(); getVideoDevices(); if (audioSrc == nullptr) { audioSrc = audio.makeSource(); connect(audioSrc.get(), &IAudioSource::volumeAvailable, this, &AVForm::setVolume); } if (audioSink == nullptr) { audioSink = audio.makeSink(); } GenericForm::showEvent(event); } void AVForm::open(const QString& devName, const VideoMode& mode) { QRect rect = mode.toRect(); videoSettings->setCamVideoRes(rect); videoSettings->setCamVideoFPS(static_cast(mode.FPS)); camera.setupDevice(devName, mode); } void AVForm::trackNewScreenGeometry(QScreen* qScreen) { connect(qScreen, &QScreen::geometryChanged, this, &AVForm::rescanDevices); } void AVForm::rescanDevices() { getAudioInDevices(); getAudioOutDevices(); getVideoDevices(); } void AVForm::setVolume(float value) { volumeDisplay->setValue(getStepsFromValue(value, audio.minOutputVolume(), audio.maxOutputVolume())); } void AVForm::on_videoModescomboBox_currentIndexChanged(int index) { assert(0 <= index && index < videoModes.size()); int devIndex = videoDevCombobox->currentIndex(); assert(0 <= devIndex && devIndex < videoDeviceList.size()); QString devName = videoDeviceList[devIndex].first; VideoMode mode = videoModes[index]; if (CameraDevice::isScreen(devName) && mode == VideoMode()) { if (videoSettings->getScreenGrabbed()) { VideoMode mode(videoSettings->getScreenRegion()); open(devName, mode); return; } auto onGrabbed = [devName, this](QRect region) { VideoMode mode(region); mode.width = mode.width / 2 * 2; mode.height = mode.height / 2 * 2; // Needed, if the virtual screen origin is the top left corner of the primary screen QRect screen = QApplication::primaryScreen()->virtualGeometry(); mode.x += screen.x(); mode.y += screen.y(); videoSettings->setScreenRegion(mode.toRect()); videoSettings->setScreenGrabbed(true); open(devName, mode); }; // note: grabber is self-managed and will destroy itself when done ScreenshotGrabber* screenshotGrabber = new ScreenshotGrabber; connect(screenshotGrabber, &ScreenshotGrabber::regionChosen, this, onGrabbed, Qt::QueuedConnection); screenshotGrabber->showGrabber(); return; } videoSettings->setScreenGrabbed(false); open(devName, mode); } void AVForm::selectBestModes(QVector& allVideoModes) { if (allVideoModes.isEmpty()) { qCritical() << "Trying to select best mode from empty modes list"; return; } // Identify the best resolutions available for the supposed XXXXp resolutions. std::map idealModes; idealModes[120] = VideoMode(160, 120); idealModes[240] = VideoMode(430, 240); idealModes[360] = VideoMode(640, 360); idealModes[480] = VideoMode(854, 480); idealModes[720] = VideoMode(1280, 720); idealModes[1080] = VideoMode(1920, 1080); idealModes[1440] = VideoMode(2560, 1440); idealModes[2160] = VideoMode(3840, 2160); std::map bestModeInds; for (int i = 0; i < allVideoModes.size(); ++i) { VideoMode mode = allVideoModes[i]; // PS3-Cam protection, everything above 60fps makes no sense if (mode.FPS > 60) continue; for (auto iter = idealModes.begin(); iter != idealModes.end(); ++iter) { int res = iter->first; VideoMode idealMode = iter->second; // don't take approximately correct resolutions unless they really // are close if (mode.norm(idealMode) > idealMode.tolerance()) continue; if (bestModeInds.find(res) == bestModeInds.end()) { bestModeInds[res] = i; continue; } int index = bestModeInds[res]; VideoMode best = allVideoModes[index]; if (mode.norm(idealMode) < best.norm(idealMode)) { bestModeInds[res] = i; continue; } if (mode.norm(idealMode) == best.norm(idealMode)) { // prefer higher FPS and "better" pixel formats if (mode.FPS > best.FPS) { bestModeInds[res] = i; continue; } bool better = CameraDevice::betterPixelFormat(mode.pixel_format, best.pixel_format); if (mode.FPS >= best.FPS && better) bestModeInds[res] = i; } } } QVector newVideoModes; for (auto it = bestModeInds.rbegin(); it != bestModeInds.rend(); ++it) { VideoMode mode = allVideoModes[it->second]; if (newVideoModes.empty()) { newVideoModes.push_back(mode); } else { int size = getModeSize(mode); auto result = std::find_if(newVideoModes.cbegin(), newVideoModes.cend(), [size](VideoMode mode) { return getModeSize(mode) == size; }); if (result == newVideoModes.end()) newVideoModes.push_back(mode); } } allVideoModes = newVideoModes; } void AVForm::fillCameraModesComboBox() { qDebug() << "selected Modes:"; bool previouslyBlocked = videoModescomboBox->blockSignals(true); videoModescomboBox->clear(); for (int i = 0; i < videoModes.size(); ++i) { VideoMode mode = videoModes[i]; QString str; std::string pixelFormat = CameraDevice::getPixelFormatString(mode.pixel_format).toStdString(); qDebug("width: %d, height: %d, FPS: %f, pixel format: %s\n", mode.width, mode.height, mode.FPS, pixelFormat.c_str()); if (mode.height && mode.width) { str += QString("%1p").arg(mode.height); } else { str += tr("Default resolution"); } videoModescomboBox->addItem(str); } if (videoModes.isEmpty()) videoModescomboBox->addItem(tr("Default resolution")); videoModescomboBox->blockSignals(previouslyBlocked); } int AVForm::searchPreferredIndex() { QRect prefRes = videoSettings->getCamVideoRes(); float prefFPS = videoSettings->getCamVideoFPS(); for (int i = 0; i < videoModes.size(); ++i) { VideoMode mode = videoModes[i]; if (mode.width == prefRes.width() && mode.height == prefRes.height() && (qAbs(mode.FPS - prefFPS) < 0.0001f)) { return i; } } return -1; } void AVForm::fillScreenModesComboBox() { bool previouslyBlocked = videoModescomboBox->blockSignals(true); videoModescomboBox->clear(); for (int i = 0; i < videoModes.size(); ++i) { VideoMode mode = videoModes[i]; std::string pixelFormat = CameraDevice::getPixelFormatString(mode.pixel_format).toStdString(); qDebug("%dx%d+%d,%d FPS: %f, pixel format: %s\n", mode.width, mode.height, mode.x, mode.y, mode.FPS, pixelFormat.c_str()); QString name; if (mode.width && mode.height) name = tr("Screen %1").arg(i + 1); else name = tr("Select region"); videoModescomboBox->addItem(name); } videoModescomboBox->blockSignals(previouslyBlocked); } void AVForm::fillAudioQualityComboBox() { const bool previouslyBlocked = audioQualityComboBox->blockSignals(true); audioQualityComboBox->addItem(tr("High (64 kbps)"), 64); audioQualityComboBox->addItem(tr("Medium (32 kbps)"), 32); audioQualityComboBox->addItem(tr("Low (16 kbps)"), 16); audioQualityComboBox->addItem(tr("Very low (8 kbps)"), 8); const int currentBitrate = audioSettings->getAudioBitrate(); const int index = audioQualityComboBox->findData(currentBitrate); audioQualityComboBox->setCurrentIndex(index); audioQualityComboBox->blockSignals(previouslyBlocked); } void AVForm::updateVideoModes(int curIndex) { if (curIndex < 0 || curIndex >= videoDeviceList.size()) { qWarning() << "Invalid index:" << curIndex; return; } QString devName = videoDeviceList[curIndex].first; QVector allVideoModes = CameraDevice::getVideoModes(devName); qDebug("available Modes:"); bool isScreen = CameraDevice::isScreen(devName); if (isScreen) { // Add extra video mode to region selection allVideoModes.push_back(VideoMode()); videoModes = allVideoModes; fillScreenModesComboBox(); } else { selectBestModes(allVideoModes); videoModes = allVideoModes; fillCameraModesComboBox(); } int preferedIndex = searchPreferredIndex(); if (preferedIndex != -1) { videoSettings->setScreenGrabbed(false); videoModescomboBox->blockSignals(true); videoModescomboBox->setCurrentIndex(preferedIndex); videoModescomboBox->blockSignals(false); open(devName, videoModes[preferedIndex]); return; } if (isScreen) { QRect rect = videoSettings->getScreenRegion(); VideoMode mode(rect); videoSettings->setScreenGrabbed(true); videoModescomboBox->setCurrentIndex(videoModes.size() - 1); open(devName, mode); return; } // If the user hasn't set a preferred resolution yet, // we'll pick the resolution in the middle of the list, // and the best FPS for that resolution. // If we picked the lowest resolution, the quality would be awful // but if we picked the largest, FPS would be bad and thus quality bad too. int mid = (videoModes.size() - 1) / 2; videoModescomboBox->setCurrentIndex(mid); } void AVForm::on_videoDevCombobox_currentIndexChanged(int index) { assert(0 <= index && index < videoDeviceList.size()); videoSettings->setScreenGrabbed(false); QString dev = videoDeviceList[index].first; videoSettings->setVideoDev(dev); bool previouslyBlocked = videoModescomboBox->blockSignals(true); updateVideoModes(index); videoModescomboBox->blockSignals(previouslyBlocked); if (videoSettings->getScreenGrabbed()) { return; } int modeIndex = videoModescomboBox->currentIndex(); VideoMode mode = VideoMode(); if (0 <= modeIndex && modeIndex < videoModes.size()) { mode = videoModes[modeIndex]; } camera.setupDevice(dev, mode); if (dev == "none") { // TODO: Use injected `coreAv` currently injected `nullptr` Core::getInstance()->getAv()->sendNoVideo(); } } void AVForm::on_audioQualityComboBox_currentIndexChanged(int index) { audioSettings->setAudioBitrate(audioQualityComboBox->currentData().toInt()); } void AVForm::getVideoDevices() { QString settingsInDev = videoSettings->getVideoDev(); int videoDevIndex = 0; videoDeviceList = CameraDevice::getDeviceList(); // prevent currentIndexChanged to be fired while adding items videoDevCombobox->blockSignals(true); videoDevCombobox->clear(); for (QPair device : videoDeviceList) { videoDevCombobox->addItem(device.second); if (device.first == settingsInDev) videoDevIndex = videoDevCombobox->count() - 1; } videoDevCombobox->setCurrentIndex(videoDevIndex); videoDevCombobox->blockSignals(false); updateVideoModes(videoDevIndex); } int AVForm::getModeSize(VideoMode mode) { return qRound(mode.height / 120.0) * 120; } void AVForm::getAudioInDevices() { QStringList deviceNames; deviceNames << tr("Disabled") << audio.inDeviceNames(); inDevCombobox->blockSignals(true); inDevCombobox->clear(); inDevCombobox->addItems(deviceNames); inDevCombobox->blockSignals(false); int idx = 0; bool enabled = audioSettings->getAudioInDevEnabled(); if (enabled && deviceNames.size() > 1) { QString dev = audioSettings->getInDev(); idx = qMax(deviceNames.indexOf(dev), 1); } inDevCombobox->setCurrentIndex(idx); } void AVForm::getAudioOutDevices() { QStringList deviceNames; deviceNames << tr("Disabled") << audio.outDeviceNames(); outDevCombobox->blockSignals(true); outDevCombobox->clear(); outDevCombobox->addItems(deviceNames); outDevCombobox->blockSignals(false); int idx = 0; bool enabled = audioSettings->getAudioOutDevEnabled(); if (enabled && deviceNames.size() > 1) { QString dev = audioSettings->getOutDev(); idx = qMax(deviceNames.indexOf(dev), 1); } outDevCombobox->setCurrentIndex(idx); } void AVForm::on_inDevCombobox_currentIndexChanged(int deviceIndex) { const bool inputEnabled = deviceIndex > 0; audioSettings->setAudioInDevEnabled(inputEnabled); QString deviceName{}; if (inputEnabled) { deviceName = inDevCombobox->itemText(deviceIndex); } const QString oldName = audioSettings->getInDev(); if (oldName != deviceName) { audioSettings->setInDev(deviceName); audio.reinitInput(deviceName); audioSrc = audio.makeSource(); connect(audioSrc.get(), &IAudioSource::volumeAvailable, this, &AVForm::setVolume); } microphoneSlider->setEnabled(inputEnabled); if (!inputEnabled) { volumeDisplay->setValue(volumeDisplay->minimum()); } } void AVForm::on_outDevCombobox_currentIndexChanged(int deviceIndex) { const bool outputEnabled = deviceIndex > 0; audioSettings->setAudioOutDevEnabled(outputEnabled); QString deviceName{}; if (outputEnabled) { deviceName = outDevCombobox->itemText(deviceIndex); } const QString oldName = audioSettings->getOutDev(); if (oldName != deviceName) { audioSettings->setOutDev(deviceName); audio.reinitOutput(deviceName); audioSink = audio.makeSink(); } playbackSlider->setEnabled(outputEnabled); } void AVForm::on_playbackSlider_valueChanged(int sliderSteps) { const int settingsVolume = getValueFromSteps(sliderSteps, audioSettings->getOutVolumeMin(), audioSettings->getOutVolumeMax()); audioSettings->setOutVolume(settingsVolume); if (audio.isOutputReady()) { const qreal volume = getValueFromSteps(sliderSteps, audio.minOutputVolume(), audio.maxOutputVolume()); audio.setOutputVolume(volume); if (cbEnableTestSound->isChecked() && audioSink) { audioSink->playMono16Sound(IAudioSink::Sound::Test); } } } void AVForm::on_cbEnableTestSound_stateChanged() { audioSettings->setEnableTestSound(cbEnableTestSound->isChecked()); if (cbEnableTestSound->isChecked() && audio.isOutputReady() && audioSink) { audioSink->playMono16Sound(IAudioSink::Sound::Test); } } void AVForm::on_microphoneSlider_valueChanged(int sliderSteps) { const qreal dB = getValueFromSteps(sliderSteps, audio.minInputGain(), audio.maxInputGain()); audioSettings->setAudioInGainDecibel(dB); audio.setInputGain(dB); } void AVForm::on_audioThresholdSlider_valueChanged(int sliderSteps) { const qreal normThreshold = getValueFromSteps(sliderSteps, audio.minInputThreshold(), audio.maxInputThreshold()); audioSettings->setAudioThreshold(normThreshold); audio.setInputThreshold(normThreshold); } void AVForm::createVideoSurface() { if (camVideoSurface) return; camVideoSurface = new VideoSurface(QPixmap(), CamFrame); camVideoSurface->setObjectName(QStringLiteral("CamVideoSurface")); camVideoSurface->setMinimumSize(QSize(160, 120)); camVideoSurface->setSource(&camera); gridLayout->addWidget(camVideoSurface, 0, 0, 1, 1); } void AVForm::killVideoSurface() { if (!camVideoSurface) return; QLayoutItem* child; while ((child = gridLayout->takeAt(0)) != nullptr) delete child; camVideoSurface->close(); delete camVideoSurface; camVideoSurface = nullptr; } void AVForm::retranslateUi() { Ui::AVForm::retranslateUi(this); } int AVForm::getStepsFromValue(qreal val, qreal valMin, qreal valMax) { const float norm = (val - valMin) / (valMax - valMin); return norm * totalSliderSteps; } qreal AVForm::getValueFromSteps(int steps, qreal valMin, qreal valMax) { return (static_cast(steps) / totalSliderSteps) * (valMax - valMin) + valMin; } qTox/src/widget/form/settings/avform.h000066400000000000000000000066521415623743500203540ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef AVFORM_H #define AVFORM_H #include #include #include #include "genericsettings.h" #include "ui_avform.h" #include "src/video/videomode.h" #include class IAudioControl; class IAudioSettings; class IAudioSink; class IAudioSource; class CameraSource; class CoreAV; class IVideoSettings; class VideoSurface; class AVForm : public GenericForm, private Ui::AVForm { Q_OBJECT public: AVForm(IAudioControl& audio, CoreAV* coreAV, CameraSource& camera, IAudioSettings* audioSettings, IVideoSettings* videoSettings); ~AVForm() override; QString getFormName() final override { return tr("Audio/Video"); } private: void getAudioInDevices(); void getAudioOutDevices(); void getVideoDevices(); static int getModeSize(VideoMode mode); void selectBestModes(QVector& allVideoModes); void fillCameraModesComboBox(); void fillScreenModesComboBox(); void fillAudioQualityComboBox(); int searchPreferredIndex(); void createVideoSurface(); void killVideoSurface(); void retranslateUi(); private slots: // audio void on_inDevCombobox_currentIndexChanged(int deviceIndex); void on_outDevCombobox_currentIndexChanged(int deviceIndex); void on_playbackSlider_valueChanged(int sliderSteps); void on_cbEnableTestSound_stateChanged(); void on_microphoneSlider_valueChanged(int sliderSteps); void on_audioThresholdSlider_valueChanged(int sliderSteps); void on_audioQualityComboBox_currentIndexChanged(int index); // camera void on_videoDevCombobox_currentIndexChanged(int index); void on_videoModescomboBox_currentIndexChanged(int index); void rescanDevices(); void setVolume(float value); protected: void updateVideoModes(int curIndex); private: void hideEvent(QHideEvent* event) final override; void showEvent(QShowEvent* event) final override; void open(const QString& devName, const VideoMode& mode); int getStepsFromValue(qreal val, qreal valMin, qreal valMax); qreal getValueFromSteps(int steps, qreal valMin, qreal valMax); void trackNewScreenGeometry(QScreen* qScreen); private: IAudioControl& audio; CoreAV* coreAV; IAudioSettings* audioSettings; IVideoSettings* videoSettings; bool subscribedToAudioIn; std::unique_ptr audioSink = nullptr; std::unique_ptr audioSrc = nullptr; VideoSurface* camVideoSurface; CameraSource& camera; QVector> videoDeviceList; QVector videoModes; uint alSource; const uint totalSliderSteps = 100; // arbitrary number of steps to give slider a good "feel" }; #endif qTox/src/widget/form/settings/avform.ui000066400000000000000000000221261415623743500205340ustar00rootroot00000000000000 AVForm 0 0 842 507 Form true QFrame::NoFrame true 0 0 830 495 Audio Settings 6 Capture device Test Sound true Use slider to set volume of your speakers. 100 Qt::Horizontal Volume Gain Qt::Horizontal Threshold Qt::Horizontal Volume false Qt::Horizontal Playback device Audio quality 0 0 Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Video Settings Video device Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Resolution 0 0 Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. 1 150 QFrame::NoFrame QFrame::Raised Rescan devices VerticalOnlyScroller QScrollArea
src/widget/form/settings/verticalonlyscroller.h
1
scrollArea outDevCombobox playbackSlider cbEnableTestSound inDevCombobox microphoneSlider videoDevCombobox videoModescomboBox rescanButton
qTox/src/widget/form/settings/generalform.cpp000066400000000000000000000163371415623743500217170ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "generalform.h" #include "ui_generalsettings.h" #include #include #include "src/core/core.h" #include "src/core/coreav.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/persistence/smileypack.h" #include "src/widget/form/settingswidget.h" #include "src/widget/style.h" #include "src/widget/tool/recursivesignalblocker.h" #include "src/widget/translator.h" #include "src/widget/widget.h" // clang-format off static QStringList locales = { "ar", "be", "bg", "cs", "da", "de", "et", "el", "en", "es", "eo", "fa", "fr", "ko", "he", "hr", "it", "sw", "lt", "jbo", "hu", "mk", "nl", "ja", "no_nb", "pr", "pl", "pt", "pt_BR", "ro", "ru", "sk", "sl", "sr", "sr_Latn", "fi", "sv", "ta", "tr", "ug", "uk", "zh_CN", "zh_TW" }; // clang-format on /** * @class GeneralForm * * This form contains all settings that are not suited to other forms */ GeneralForm::GeneralForm(SettingsWidget* myParent) : GenericForm(QPixmap(":/img/settings/general.png")) , bodyUI(new Ui::GeneralSettings) { parent = myParent; bodyUI->setupUi(this); // block all child signals during initialization const RecursiveSignalBlocker signalBlocker(this); Settings& s = Settings::getInstance(); #ifndef UPDATE_CHECK_ENABLED bodyUI->checkUpdates->setVisible(false); #endif #ifndef SPELL_CHECKING bodyUI->cbSpellChecking->setVisible(false); #endif bodyUI->checkUpdates->setChecked(s.getCheckUpdates()); for (int i = 0; i < locales.size(); ++i) { QString langName; if (locales[i].startsWith(QLatin1String("eo"))) // QTBUG-57802 langName = QLocale::languageToString(QLocale::Esperanto); else if (locales[i].startsWith(QLatin1String("jbo"))) langName = QLatin1String("Lojban"); else if (locales[i].startsWith(QLatin1String("pr"))) langName = QLatin1String("Pirate"); else if (locales[i] == (QLatin1String("pt"))) // QTBUG-47891 langName = QStringLiteral("português"); else langName = QLocale(locales[i]).nativeLanguageName(); bodyUI->transComboBox->insertItem(i, langName); } bodyUI->transComboBox->setCurrentIndex(locales.indexOf(s.getTranslation())); bodyUI->cbAutorun->setChecked(s.getAutorun()); bodyUI->cbSpellChecking->setChecked(s.getSpellCheckingEnabled()); bodyUI->lightTrayIcon->setChecked(s.getLightTrayIcon()); bool showSystemTray = s.getShowSystemTray(); bodyUI->showSystemTray->setChecked(showSystemTray); bodyUI->startInTray->setChecked(s.getAutostartInTray()); bodyUI->startInTray->setEnabled(showSystemTray); bodyUI->minimizeToTray->setChecked(s.getMinimizeToTray()); bodyUI->minimizeToTray->setEnabled(showSystemTray); bodyUI->closeToTray->setChecked(s.getCloseToTray()); bodyUI->closeToTray->setEnabled(showSystemTray); bodyUI->statusChanges->setChecked(s.getStatusChangeNotificationEnabled()); bodyUI->autoAwaySpinBox->setValue(s.getAutoAwayTime()); bodyUI->autoSaveFilesDir->setText(s.getGlobalAutoAcceptDir()); bodyUI->maxAutoAcceptSizeMB->setValue(static_cast(s.getMaxAutoAcceptSize()) / 1024 / 1024); bodyUI->autoacceptFiles->setChecked(s.getAutoSaveEnabled()); #ifndef QTOX_PLATFORM_EXT bodyUI->autoAwayLabel->setEnabled(false); // these don't seem to change the appearance of the widgets, bodyUI->autoAwaySpinBox->setEnabled(false); // though they are unusable #endif eventsInit(); Translator::registerHandler(std::bind(&GeneralForm::retranslateUi, this), this); } GeneralForm::~GeneralForm() { Translator::unregister(this); delete bodyUI; } void GeneralForm::on_transComboBox_currentIndexChanged(int index) { const QString& locale = locales[index]; Settings::getInstance().setTranslation(locale); Translator::translate(locale); } void GeneralForm::on_cbAutorun_stateChanged() { Settings::getInstance().setAutorun(bodyUI->cbAutorun->isChecked()); } void GeneralForm::on_cbSpellChecking_stateChanged() { Settings::getInstance().setSpellCheckingEnabled(bodyUI->cbSpellChecking->isChecked()); } void GeneralForm::on_showSystemTray_stateChanged() { Settings::getInstance().setShowSystemTray(bodyUI->showSystemTray->isChecked()); Settings::getInstance().saveGlobal(); } void GeneralForm::on_startInTray_stateChanged() { Settings::getInstance().setAutostartInTray(bodyUI->startInTray->isChecked()); } void GeneralForm::on_closeToTray_stateChanged() { Settings::getInstance().setCloseToTray(bodyUI->closeToTray->isChecked()); } void GeneralForm::on_lightTrayIcon_stateChanged() { Settings::getInstance().setLightTrayIcon(bodyUI->lightTrayIcon->isChecked()); emit updateIcons(); } void GeneralForm::on_minimizeToTray_stateChanged() { Settings::getInstance().setMinimizeToTray(bodyUI->minimizeToTray->isChecked()); } void GeneralForm::on_statusChanges_stateChanged() { Settings::getInstance().setStatusChangeNotificationEnabled(bodyUI->statusChanges->isChecked()); } void GeneralForm::on_autoAwaySpinBox_editingFinished() { int minutes = bodyUI->autoAwaySpinBox->value(); Settings::getInstance().setAutoAwayTime(minutes); } void GeneralForm::on_autoacceptFiles_stateChanged() { Settings::getInstance().setAutoSaveEnabled(bodyUI->autoacceptFiles->isChecked()); } void GeneralForm::on_autoSaveFilesDir_clicked() { QString previousDir = Settings::getInstance().getGlobalAutoAcceptDir(); QString directory = QFileDialog::getExistingDirectory(Q_NULLPTR, tr("Choose an auto accept directory", "popup title"), QDir::homePath()); if (directory.isEmpty()) // cancel was pressed directory = previousDir; Settings::getInstance().setGlobalAutoAcceptDir(directory); bodyUI->autoSaveFilesDir->setText(directory); } void GeneralForm::on_maxAutoAcceptSizeMB_editingFinished() { auto newMaxSizeMB = bodyUI->maxAutoAcceptSizeMB->value(); auto newMaxSizeB = std::lround(newMaxSizeMB * 1024 * 1024); Settings::getInstance().setMaxAutoAcceptSize(newMaxSizeB); } void GeneralForm::on_checkUpdates_stateChanged() { Settings::getInstance().setCheckUpdates(bodyUI->checkUpdates->isChecked()); } /** * @brief Retranslate all elements in the form. */ void GeneralForm::retranslateUi() { bodyUI->retranslateUi(this); } qTox/src/widget/form/settings/generalform.h000066400000000000000000000035671415623743500213650ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GENERALFORM_H #define GENERALFORM_H #include "genericsettings.h" namespace Ui { class GeneralSettings; } class SettingsWidget; class GeneralForm : public GenericForm { Q_OBJECT public: explicit GeneralForm(SettingsWidget* parent); ~GeneralForm(); virtual QString getFormName() final override { return tr("General"); } signals: void updateIcons(); private slots: void on_transComboBox_currentIndexChanged(int index); void on_cbAutorun_stateChanged(); void on_cbSpellChecking_stateChanged(); void on_showSystemTray_stateChanged(); void on_startInTray_stateChanged(); void on_closeToTray_stateChanged(); void on_lightTrayIcon_stateChanged(); void on_autoAwaySpinBox_editingFinished(); void on_minimizeToTray_stateChanged(); void on_statusChanges_stateChanged(); void on_autoacceptFiles_stateChanged(); void on_maxAutoAcceptSizeMB_editingFinished(); void on_autoSaveFilesDir_clicked(); void on_checkUpdates_stateChanged(); private: void retranslateUi(); private: Ui::GeneralSettings* bodyUI; SettingsWidget* parent; }; #endif qTox/src/widget/form/settings/generalsettings.ui000066400000000000000000000335541415623743500224470ustar00rootroot00000000000000 GeneralSettings 0 0 1312 511 Form 6 6 6 6 true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 0 0 1298 497 32 9 General Settings The translation may not load until qTox restarts. Language: 0 0 The translation may not load until qTox restarts. Qt::Horizontal 40 20 Start qTox on operating system startup (current profile). Autostart Check for updates Spell checking 0 0 Enable light tray icon. Light icon true 0 0 Show system tray icon 40 0 0 qTox will start minimized in tray. Start in tray 0 0 After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. Minimize to tray 0 0 After pressing close (X) qTox will minimize to tray, instead of closing itself. Close to tray Show contacts' status changes Your status is changed to Away after set period of inactivity. Qt::LeftToRight Auto away after (0 to disable): 0 0 Set to 0 to disable true min 0 2147483647 0 Default directory to save files: 0 0 Set where files will be saved. 0 0 You can set this on a per-friend basis by right clicking them. Autoaccept files Max autoaccept file size (0 to disable): MB Qt::Vertical 20 40 VerticalOnlyScroller QScrollArea
src/widget/form/settings/verticalonlyscroller.h
1
scrollArea transComboBox cbAutorun checkUpdates lightTrayIcon showSystemTray startInTray minimizeToTray closeToTray statusChanges autoAwaySpinBox autoSaveFilesDir autoacceptFiles showSystemTray toggled(bool) closeToTray setEnabled(bool) 205 146 224 178 showSystemTray toggled(bool) minimizeToTray setEnabled(bool) 180 144 359 178 showSystemTray toggled(bool) startInTray setEnabled(bool) 105 144 119 177
qTox/src/widget/form/settings/genericsettings.cpp000066400000000000000000000046621415623743500226110ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "genericsettings.h" #include #include #include #include /** * @class GenericForm * * This is abstract class used as superclass for all settings forms. * It provides correct behaviour of controls for settings forms. */ GenericForm::GenericForm(const QPixmap& icon) : formIcon(icon) { } QPixmap GenericForm::getFormIcon() { return formIcon; } /** * @brief Prevent stealing mouse wheel scroll. * * Scrolling event won't be transmitted to comboboxes or spinboxes. * You can scroll through general settings without accidentially changing * theme / skin / icons etc. * @see GenericForm::eventFilter(QObject *o, QEvent *e) at the bottom of this file for more */ void GenericForm::eventsInit() { for (QComboBox* cb : findChildren()) { cb->installEventFilter(this); cb->setFocusPolicy(Qt::StrongFocus); } for (QSpinBox* sp : findChildren()) { sp->installEventFilter(this); sp->setFocusPolicy(Qt::WheelFocus); } for (QCheckBox* cb : findChildren()) // this one is to allow scrolling on checkboxes cb->installEventFilter(this); } /** * @brief Ignore scroll on different controls. * @param o Object which has been installed for the watched object. * @param e Event object. * @return True to stop it being handled further, false otherwise. */ bool GenericForm::eventFilter(QObject* o, QEvent* e) { if ((e->type() == QEvent::Wheel) && (qobject_cast(o) || qobject_cast(o) || qobject_cast(o))) { e->ignore(); return true; } return QWidget::eventFilter(o, e); } qTox/src/widget/form/settings/genericsettings.h000066400000000000000000000022341415623743500222470ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GENERICFORM_H #define GENERICFORM_H #include class GenericForm : public QWidget { Q_OBJECT public: explicit GenericForm(const QPixmap& icon); virtual ~GenericForm() { } virtual QString getFormName() = 0; QPixmap getFormIcon(); protected: bool eventFilter(QObject* o, QEvent* e) final override; void eventsInit(); protected: QPixmap formIcon; }; #endif qTox/src/widget/form/settings/privacyform.cpp000066400000000000000000000110241415623743500217430ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "privacyform.h" #include "ui_privacysettings.h" #include #include #include #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #include #endif #include "src/core/core.h" #include "src/nexus.h" #include "src/persistence/history.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/widget/form/setpassworddialog.h" #include "src/widget/form/settingswidget.h" #include "src/widget/gui.h" #include "src/widget/tool/recursivesignalblocker.h" #include "src/widget/translator.h" #include "src/widget/widget.h" PrivacyForm::PrivacyForm() : GenericForm(QPixmap(":/img/settings/privacy.png")) , bodyUI(new Ui::PrivacySettings) { bodyUI->setupUi(this); // block all child signals during initialization const RecursiveSignalBlocker signalBlocker(this); eventsInit(); Translator::registerHandler(std::bind(&PrivacyForm::retranslateUi, this), this); } PrivacyForm::~PrivacyForm() { Translator::unregister(this); delete bodyUI; } void PrivacyForm::on_cbKeepHistory_stateChanged() { Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked()); if (!bodyUI->cbKeepHistory->isChecked()) { emit clearAllReceipts(); QMessageBox::StandardButton dialogDelHistory; dialogDelHistory = QMessageBox::question(nullptr, tr("Confirmation"), tr("Do you want to permanently delete all chat history?"), QMessageBox::Yes | QMessageBox::No); if (dialogDelHistory == QMessageBox::Yes) { Nexus::getProfile()->getHistory()->eraseHistory(); } } } void PrivacyForm::on_cbTypingNotification_stateChanged() { Settings::getInstance().setTypingNotification(bodyUI->cbTypingNotification->isChecked()); } void PrivacyForm::on_nospamLineEdit_editingFinished() { QString newNospam = bodyUI->nospamLineEdit->text(); bool ok; uint32_t nospam = newNospam.toLongLong(&ok, 16); if (ok) Core::getInstance()->setNospam(nospam); } void PrivacyForm::showEvent(QShowEvent*) { const Settings& s = Settings::getInstance(); bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().getNoSpamString()); bodyUI->cbTypingNotification->setChecked(s.getTypingNotification()); bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging()); bodyUI->blackListTextEdit->setText(s.getBlackList().join('\n')); } void PrivacyForm::on_randomNosapamButton_clicked() { QTime time = QTime::currentTime(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) QRandomGenerator((uint)time.msec()); #else qsrand((uint)time.msec()); #endif uint32_t newNospam{0}; for (int i = 0; i < 4; ++i) #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) newNospam = (newNospam << 8) + (QRandomGenerator::global()->generate() % 256); // Generate byte by byte. For some reason. #else newNospam = (newNospam << 8) + (qrand() % 256); // Generate byte by byte. For some reason. #endif Core::getInstance()->setNospam(newNospam); bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().getNoSpamString()); } void PrivacyForm::on_nospamLineEdit_textChanged() { QString str = bodyUI->nospamLineEdit->text(); int curs = bodyUI->nospamLineEdit->cursorPosition(); if (str.length() != 8) { str = QString("00000000").replace(0, str.length(), str); bodyUI->nospamLineEdit->setText(str); bodyUI->nospamLineEdit->setCursorPosition(curs); }; } void PrivacyForm::on_blackListTextEdit_textChanged() { const QStringList strlist = bodyUI->blackListTextEdit->toPlainText().split('\n'); Settings::getInstance().setBlackList(strlist); } void PrivacyForm::retranslateUi() { bodyUI->retranslateUi(this); } qTox/src/widget/form/settings/privacyform.h000066400000000000000000000030111415623743500214050ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef PRIVACYFORM_H #define PRIVACYFORM_H #include "genericsettings.h" namespace Ui { class PrivacySettings; } class PrivacyForm : public GenericForm { Q_OBJECT public: PrivacyForm(); ~PrivacyForm(); virtual QString getFormName() final override { return tr("Privacy"); } signals: void clearAllReceipts(); private slots: void on_cbKeepHistory_stateChanged(); void on_cbTypingNotification_stateChanged(); void on_nospamLineEdit_editingFinished(); void on_randomNosapamButton_clicked(); void on_nospamLineEdit_textChanged(); void on_blackListTextEdit_textChanged(); virtual void showEvent(QShowEvent*) final override; private: void retranslateUi(); private: Ui::PrivacySettings* bodyUI; }; #endif qTox/src/widget/form/settings/privacysettings.ui000066400000000000000000000125051415623743500225000ustar00rootroot00000000000000 PrivacySettings 0 0 400 359 Form 6 6 6 6 true 0 0 364 509 Privacy Your friends will be able to see when you are typing. Send typing notifications Chat history keeping is still in development. Save format changes are possible, which may result in data loss. Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. true Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse HHHHHHHH Generate random NoSpam BlackList Filter group message by group member's public key. Put public key here, one per line. Qt::Vertical 20 40 VerticalOnlyScroller QScrollArea
src/widget/form/settings/verticalonlyscroller.h
1
qTox/src/widget/form/settings/userinterfaceform.cpp000066400000000000000000000324131415623743500231320ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "userinterfaceform.h" #include "ui_userinterfacesettings.h" #include #include #include #include #include #include #include #include #include #include "src/core/core.h" #include "src/core/coreav.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/persistence/smileypack.h" #include "src/widget/form/settingswidget.h" #include "src/widget/style.h" #include "src/widget/tool/recursivesignalblocker.h" #include "src/widget/translator.h" #include "src/widget/widget.h" /** * @class UserInterfaceForm * * This form contains all settings which change the UI appearance or behaviour. * It also contains the smiley configuration. */ /** * @brief Constructor of UserInterfaceForm. * @param myParent Setting widget which will contain this form as tab. * * Restores all controls from the settings. */ UserInterfaceForm::UserInterfaceForm(SettingsWidget* myParent) : GenericForm(QPixmap(":/img/settings/general.png")) { parent = myParent; bodyUI = new Ui::UserInterfaceSettings; bodyUI->setupUi(this); // block all child signals during initialization const RecursiveSignalBlocker signalBlocker(this); Settings& s = Settings::getInstance(); const QFont chatBaseFont = s.getChatMessageFont(); bodyUI->txtChatFontSize->setValue(QFontInfo(chatBaseFont).pixelSize()); bodyUI->txtChatFont->setCurrentFont(chatBaseFont); int index = static_cast(s.getStylePreference()); bodyUI->textStyleComboBox->setCurrentIndex(index); bodyUI->useNameColors->setChecked(s.getEnableGroupChatsColor()); bodyUI->notify->setChecked(s.getNotify()); // Note: UI is boolean inversed from settings to maintain setting file backwards compatibility bodyUI->groupOnlyNotfiyWhenMentioned->setChecked(!s.getGroupAlwaysNotify()); bodyUI->groupOnlyNotfiyWhenMentioned->setEnabled(s.getNotify()); bodyUI->notifySound->setChecked(s.getNotifySound()); bodyUI->notifyHide->setChecked(s.getNotifyHide()); bodyUI->notifySound->setEnabled(s.getNotify()); bodyUI->busySound->setChecked(s.getBusySound()); bodyUI->busySound->setEnabled(s.getNotifySound() && s.getNotify()); #if DESKTOP_NOTIFICATIONS bodyUI->desktopNotify->setChecked(s.getDesktopNotify()); bodyUI->desktopNotify->setEnabled(s.getNotify()); #else bodyUI->desktopNotify->hide(); #endif bodyUI->showWindow->setChecked(s.getShowWindow()); bodyUI->cbGroupchatPosition->setChecked(s.getGroupchatPosition()); bodyUI->cbCompactLayout->setChecked(s.getCompactLayout()); bodyUI->cbSeparateWindow->setChecked(s.getSeparateWindow()); bodyUI->cbDontGroupWindows->setChecked(s.getDontGroupWindows()); bodyUI->cbDontGroupWindows->setEnabled(s.getSeparateWindow()); bodyUI->cbShowIdenticons->setChecked(s.getShowIdenticons()); bodyUI->useEmoticons->setChecked(s.getUseEmoticons()); for (auto entry : SmileyPack::listSmileyPacks()) bodyUI->smileyPackBrowser->addItem(entry.first, entry.second); smileLabels = {bodyUI->smile1, bodyUI->smile2, bodyUI->smile3, bodyUI->smile4, bodyUI->smile5}; int currentPack = bodyUI->smileyPackBrowser->findData(s.getSmileyPack()); bodyUI->smileyPackBrowser->setCurrentIndex(currentPack); reloadSmileys(); bodyUI->smileyPackBrowser->setEnabled(bodyUI->useEmoticons->isChecked()); bodyUI->styleBrowser->addItem(tr("None")); bodyUI->styleBrowser->addItems(QStyleFactory::keys()); QString style; if (QStyleFactory::keys().contains(s.getStyle())) style = s.getStyle(); else style = tr("None"); bodyUI->styleBrowser->setCurrentText(style); for (QString color : Style::getThemeColorNames()) bodyUI->themeColorCBox->addItem(color); bodyUI->themeColorCBox->setCurrentIndex(s.getThemeColor()); bodyUI->emoticonSize->setValue(s.getEmojiFontPointSize()); QLocale ql; QStringList timeFormats; timeFormats << ql.timeFormat(QLocale::ShortFormat) << ql.timeFormat(QLocale::LongFormat) << "hh:mm AP" << "hh:mm:ss AP" << "hh:mm:ss"; timeFormats.removeDuplicates(); bodyUI->timestamp->addItems(timeFormats); QRegularExpression re(QString("^[^\\n]{0,%0}$").arg(MAX_FORMAT_LENGTH)); QRegularExpressionValidator* validator = new QRegularExpressionValidator(re, this); QString timeFormat = s.getTimestampFormat(); if (!re.match(timeFormat).hasMatch()) timeFormat = timeFormats[0]; bodyUI->timestamp->setCurrentText(timeFormat); bodyUI->timestamp->setValidator(validator); on_timestamp_editTextChanged(timeFormat); QStringList dateFormats; dateFormats << QStringLiteral("yyyy-MM-dd") // ISO 8601 // format strings from system locale << ql.dateFormat(QLocale::LongFormat) << ql.dateFormat(QLocale::ShortFormat) << ql.dateFormat(QLocale::NarrowFormat) << "dd-MM-yyyy" << "d-MM-yyyy" << "dddd dd-MM-yyyy" << "dddd d-MM"; dateFormats.removeDuplicates(); bodyUI->dateFormats->addItems(dateFormats); QString dateFormat = s.getDateFormat(); if (!re.match(dateFormat).hasMatch()) dateFormat = dateFormats[0]; bodyUI->dateFormats->setCurrentText(dateFormat); bodyUI->dateFormats->setValidator(validator); on_dateFormats_editTextChanged(dateFormat); eventsInit(); Translator::registerHandler(std::bind(&UserInterfaceForm::retranslateUi, this), this); } UserInterfaceForm::~UserInterfaceForm() { Translator::unregister(this); delete bodyUI; } void UserInterfaceForm::on_styleBrowser_currentIndexChanged(QString style) { if (bodyUI->styleBrowser->currentIndex() == 0) Settings::getInstance().setStyle("None"); else Settings::getInstance().setStyle(style); this->setStyle(QStyleFactory::create(style)); parent->setBodyHeadStyle(style); } void UserInterfaceForm::on_emoticonSize_editingFinished() { Settings::getInstance().setEmojiFontPointSize(bodyUI->emoticonSize->value()); } void UserInterfaceForm::on_timestamp_editTextChanged(const QString& format) { QString timeExample = QTime::currentTime().toString(format); bodyUI->timeExample->setText(timeExample); Settings::getInstance().setTimestampFormat(format); QString locale = Settings::getInstance().getTranslation(); Translator::translate(locale); } void UserInterfaceForm::on_dateFormats_editTextChanged(const QString& format) { QString dateExample = QDate::currentDate().toString(format); bodyUI->dateExample->setText(dateExample); Settings::getInstance().setDateFormat(format); QString locale = Settings::getInstance().getTranslation(); Translator::translate(locale); } void UserInterfaceForm::on_useEmoticons_stateChanged() { Settings::getInstance().setUseEmoticons(bodyUI->useEmoticons->isChecked()); bodyUI->smileyPackBrowser->setEnabled(bodyUI->useEmoticons->isChecked()); } void UserInterfaceForm::on_textStyleComboBox_currentTextChanged() { Settings::StyleType styleType = static_cast(bodyUI->textStyleComboBox->currentIndex()); Settings::getInstance().setStylePreference(styleType); } void UserInterfaceForm::on_smileyPackBrowser_currentIndexChanged(int index) { QString filename = bodyUI->smileyPackBrowser->itemData(index).toString(); Settings::getInstance().setSmileyPack(filename); reloadSmileys(); } /** * @brief Reload smileys and size information. */ void UserInterfaceForm::reloadSmileys() { QList emoticons = SmileyPack::getInstance().getEmoticons(); // sometimes there are no emoticons available, don't crash in this case if (emoticons.isEmpty()) { qDebug() << "reloadSmilies: No emoticons found"; return; } QStringList smileys; for (int i = 0; i < emoticons.size(); ++i) smileys.push_front(emoticons.at(i).first()); emoticonsIcons.clear(); const QSize size(18, 18); for (int i = 0; i < smileLabels.size(); ++i) { std::shared_ptr icon = SmileyPack::getInstance().getAsIcon(smileys[i]); emoticonsIcons.append(icon); smileLabels[i]->setPixmap(icon->pixmap(size)); smileLabels[i]->setToolTip(smileys[i]); } // set maximum size of emoji QDesktopWidget desktop; // 8 is the count of row and column in emoji's in widget const int sideSize = 8; int maxSide = qMin(desktop.geometry().height() / sideSize, desktop.geometry().width() / sideSize); QSize maxSize(maxSide, maxSide); QSize actualSize = emoticonsIcons.first()->actualSize(maxSize); bodyUI->emoticonSize->setMaximum(actualSize.width()); } void UserInterfaceForm::on_notify_stateChanged() { const bool notify = bodyUI->notify->isChecked(); Settings::getInstance().setNotify(notify); bodyUI->groupOnlyNotfiyWhenMentioned->setEnabled(notify); bodyUI->notifySound->setEnabled(notify); bodyUI->busySound->setEnabled(notify && bodyUI->notifySound->isChecked()); bodyUI->desktopNotify->setEnabled(notify); } void UserInterfaceForm::on_notifySound_stateChanged() { const bool notify = bodyUI->notifySound->isChecked(); Settings::getInstance().setNotifySound(notify); bodyUI->busySound->setEnabled(notify); } void UserInterfaceForm::on_desktopNotify_stateChanged() { const bool notify = bodyUI->desktopNotify->isChecked(); Settings::getInstance().setDesktopNotify(notify); } void UserInterfaceForm::on_busySound_stateChanged() { Settings::getInstance().setBusySound(bodyUI->busySound->isChecked()); } void UserInterfaceForm::on_showWindow_stateChanged() { Settings::getInstance().setShowWindow(bodyUI->showWindow->isChecked()); } void UserInterfaceForm::on_groupOnlyNotfiyWhenMentioned_stateChanged() { // Note: UI is boolean inversed from settings to maintain setting file backwards compatibility Settings::getInstance().setGroupAlwaysNotify(!bodyUI->groupOnlyNotfiyWhenMentioned->isChecked()); } void UserInterfaceForm::on_cbCompactLayout_stateChanged() { Settings::getInstance().setCompactLayout(bodyUI->cbCompactLayout->isChecked()); } void UserInterfaceForm::on_cbSeparateWindow_stateChanged() { bool checked = bodyUI->cbSeparateWindow->isChecked(); bodyUI->cbDontGroupWindows->setEnabled(checked); Settings::getInstance().setSeparateWindow(checked); } void UserInterfaceForm::on_cbDontGroupWindows_stateChanged() { Settings::getInstance().setDontGroupWindows(bodyUI->cbDontGroupWindows->isChecked()); } void UserInterfaceForm::on_cbGroupchatPosition_stateChanged() { Settings::getInstance().setGroupchatPosition(bodyUI->cbGroupchatPosition->isChecked()); } void UserInterfaceForm::on_cbShowIdenticons_stateChanged() { Settings::getInstance().setShowIdenticons(bodyUI->cbShowIdenticons->isChecked()); } void UserInterfaceForm::on_themeColorCBox_currentIndexChanged(int) { int index = bodyUI->themeColorCBox->currentIndex(); Settings::getInstance().setThemeColor(index); Style::setThemeColor(index); Style::applyTheme(); } /** * @brief Retranslate all elements in the form. */ void UserInterfaceForm::retranslateUi() { // Block signals during translation to prevent settings change RecursiveSignalBlocker signalBlocker{this}; bodyUI->retranslateUi(this); // Restore text style index once translation is complete bodyUI->textStyleComboBox->setCurrentIndex( static_cast(Settings::getInstance().getStylePreference())); QStringList colorThemes(Style::getThemeColorNames()); for (int i = 0; i < colorThemes.size(); ++i) { bodyUI->themeColorCBox->setItemText(i, colorThemes[i]); } bodyUI->styleBrowser->setItemText(0, tr("None")); } void UserInterfaceForm::on_txtChatFont_currentFontChanged(const QFont& f) { QFont tmpFont = f; const int px = bodyUI->txtChatFontSize->value(); if (QFontInfo(tmpFont).pixelSize() != px) tmpFont.setPixelSize(px); Settings::getInstance().setChatMessageFont(tmpFont); } void UserInterfaceForm::on_txtChatFontSize_valueChanged(int px) { Settings& s = Settings::getInstance(); QFont tmpFont = s.getChatMessageFont(); const int fontSize = QFontInfo(tmpFont).pixelSize(); if (px != fontSize) { tmpFont.setPixelSize(px); s.setChatMessageFont(tmpFont); } } void UserInterfaceForm::on_useNameColors_stateChanged(int value) { Settings::getInstance().setEnableGroupChatsColor(value); } void UserInterfaceForm::on_notifyHide_stateChanged(int value) { Settings::getInstance().setNotifyHide(value); } qTox/src/widget/form/settings/userinterfaceform.h000066400000000000000000000051031415623743500225730ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef USERINTERFACEFORM_H #define USERINTERFACEFORM_H #include "genericsettings.h" #include "src/widget/form/settingswidget.h" #include namespace Ui { class UserInterfaceSettings; } class UserInterfaceForm : public GenericForm { Q_OBJECT public: explicit UserInterfaceForm(SettingsWidget* myParent); ~UserInterfaceForm(); virtual QString getFormName() final override { return tr("User Interface"); } private slots: void on_smileyPackBrowser_currentIndexChanged(int index); void on_emoticonSize_editingFinished(); void on_styleBrowser_currentIndexChanged(QString style); void on_timestamp_editTextChanged(const QString& format); void on_dateFormats_editTextChanged(const QString& format); void on_textStyleComboBox_currentTextChanged(); void on_useEmoticons_stateChanged(); void on_notify_stateChanged(); void on_desktopNotify_stateChanged(); void on_notifySound_stateChanged(); void on_notifyHide_stateChanged(int); void on_busySound_stateChanged(); void on_showWindow_stateChanged(); void on_groupOnlyNotfiyWhenMentioned_stateChanged(); void on_cbCompactLayout_stateChanged(); void on_cbSeparateWindow_stateChanged(); void on_cbDontGroupWindows_stateChanged(); void on_cbGroupchatPosition_stateChanged(); void on_themeColorCBox_currentIndexChanged(int); void on_cbShowIdenticons_stateChanged(); void on_txtChatFont_currentFontChanged(const QFont& f); void on_txtChatFontSize_valueChanged(int arg1); void on_useNameColors_stateChanged(int value); private: void retranslateUi(); void reloadSmileys(); private: QList smileLabels; QList> emoticonsIcons; SettingsWidget* parent; Ui::UserInterfaceSettings* bodyUI; const int MAX_FORMAT_LENGTH = 128; }; #endif qTox/src/widget/form/settings/userinterfacesettings.ui000066400000000000000000000526301415623743500236650ustar00rootroot00000000000000 UserInterfaceSettings 0 0 678 810 Form 6 6 6 6 true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 0 0 650 950 6 6 Chat 9 9 9 9 Base font: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false 0 0 px Size: New text styling preference may not load until qTox restarts. Text Style format: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 Select text styling preference. 1 Plaintext Show formatting characters Don't show formatting characters Use colored nicknames in chats New message 6 Show a notification when you receive a new message and the window is not selected. Notify 40 Onlys notify about new messages in groupchats when mentioned. Group chats only notify when mentioned Play sound 40 Play sound while Busy Notify via desktop notifications Hide message sender and contents Open qTox's window when you receive a new message and no window is open yet. Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. Place groupchats at top of friend list Your contact list will be shown in compact mode. Compact contact list Multiple windows mode 40 false 0 0 Open each chat in an individual window If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. Use identicons instead of empty avatars Emoticons Use emoticons Smiley Pack: 0 0 Qt::Horizontal 80 20 QLayout::SetDefaultConstraint :) Qt::AlignCenter ;) Qt::AlignCenter :p Qt::AlignCenter :O Qt::AlignCenter :'( Qt::AlignCenter Emoticon size: 0 0 px 1 2147483647 25 Theme QFormLayout::AllNonFixedFieldsGrow 9 9 Style: 0 0 Theme color: 0 0 0 0 true QComboBox::NoInsert Time Example Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Timestamp format: true QComboBox::NoInsert Date Example Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Date format: Qt::Vertical 20 40 VerticalOnlyScroller QScrollArea
src/widget/form/settings/verticalonlyscroller.h
1
scrollArea txtChatFont txtChatFontSize textStyleComboBox notify groupOnlyNotfiyWhenMentioned notifySound busySound showWindow cbGroupchatPosition cbCompactLayout cbSeparateWindow cbDontGroupWindows useEmoticons smileyPackBrowser emoticonSize styleBrowser themeColorCBox timestamp dateFormats
qTox/src/widget/form/settings/verticalonlyscroller.cpp000066400000000000000000000024041415623743500236650ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include #include #include "verticalonlyscroller.h" VerticalOnlyScroller::VerticalOnlyScroller(QWidget* parent) : QScrollArea(parent) { } void VerticalOnlyScroller::resizeEvent(QResizeEvent* event) { QScrollArea::resizeEvent(event); if (widget()) widget()->setMaximumWidth(event->size().width()); } void VerticalOnlyScroller::showEvent(QShowEvent* event) { QScrollArea::showEvent(event); if (widget()) widget()->setMaximumWidth(size().width()); } qTox/src/widget/form/settings/verticalonlyscroller.h000066400000000000000000000022651415623743500233370ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef VERTICALONLYSCROLLER_H #define VERTICALONLYSCROLLER_H #include class QResizeEvent; class QShowEvent; class VerticalOnlyScroller : public QScrollArea { Q_OBJECT public: explicit VerticalOnlyScroller(QWidget* parent = nullptr); protected: virtual void resizeEvent(QResizeEvent* event) final override; virtual void showEvent(QShowEvent* event) final override; }; #endif // VERTICALONLYSCROLLER_H qTox/src/widget/form/settingswidget.cpp000066400000000000000000000111241415623743500206070ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "settingswidget.h" #include "src/audio/audio.h" #include "src/core/core.h" #include "src/core/coreav.h" #include "src/net/updatecheck.h" #include "src/persistence/settings.h" #include "src/video/camerasource.h" #include "src/widget/contentlayout.h" #include "src/widget/form/settings/aboutform.h" #include "src/widget/form/settings/advancedform.h" #include "src/widget/form/settings/avform.h" #include "src/widget/form/settings/generalform.h" #include "src/widget/form/settings/privacyform.h" #include "src/widget/form/settings/userinterfaceform.h" #include "src/widget/translator.h" #include "src/widget/widget.h" #include #include #include #include SettingsWidget::SettingsWidget(UpdateCheck* updateCheck, IAudioControl& audio, Widget* parent) : QWidget(parent, Qt::Window) { CoreAV* coreAV = Core::getInstance()->getAv(); IAudioSettings* audioSettings = &Settings::getInstance(); IVideoSettings* videoSettings = &Settings::getInstance(); CameraSource& camera = CameraSource::getInstance(); setAttribute(Qt::WA_DeleteOnClose); bodyLayout = std::unique_ptr(new QVBoxLayout()); settingsWidgets = std::unique_ptr(new QTabWidget(this)); settingsWidgets->setTabPosition(QTabWidget::North); bodyLayout->addWidget(settingsWidgets.get()); std::unique_ptr gfrm(new GeneralForm(this)); connect(gfrm.get(), &GeneralForm::updateIcons, parent, &Widget::updateIcons); std::unique_ptr uifrm(new UserInterfaceForm(this)); std::unique_ptr pfrm(new PrivacyForm()); connect(pfrm.get(), &PrivacyForm::clearAllReceipts, parent, &Widget::clearAllReceipts); AVForm* rawAvfrm = new AVForm(audio, coreAV, camera, audioSettings, videoSettings); std::unique_ptr avfrm(rawAvfrm); std::unique_ptr expfrm(new AdvancedForm()); std::unique_ptr abtfrm(new AboutForm(updateCheck)); #if UPDATE_CHECK_ENABLED if (updateCheck != nullptr) { connect(updateCheck, &UpdateCheck::updateAvailable, this, &SettingsWidget::onUpdateAvailable); } else { qWarning() << "SettingsWidget passed null UpdateCheck!"; } #endif cfgForms = {{std::move(gfrm), std::move(uifrm), std::move(pfrm), std::move(avfrm), std::move(expfrm), std::move(abtfrm)}}; for (auto& cfgForm : cfgForms) settingsWidgets->addTab(cfgForm.get(), cfgForm->getFormIcon(), cfgForm->getFormName()); connect(settingsWidgets.get(), &QTabWidget::currentChanged, this, &SettingsWidget::onTabChanged); Translator::registerHandler(std::bind(&SettingsWidget::retranslateUi, this), this); } SettingsWidget::~SettingsWidget() { Translator::unregister(this); } void SettingsWidget::setBodyHeadStyle(QString style) { settingsWidgets->setStyle(QStyleFactory::create(style)); } void SettingsWidget::showAbout() { onTabChanged(settingsWidgets->count() - 1); } bool SettingsWidget::isShown() const { if (settingsWidgets->isVisible()) { settingsWidgets->window()->windowHandle()->alert(0); return true; } return false; } void SettingsWidget::show(ContentLayout* contentLayout) { contentLayout->mainContent->layout()->addWidget(settingsWidgets.get()); settingsWidgets->show(); onTabChanged(settingsWidgets->currentIndex()); } void SettingsWidget::onTabChanged(int index) { settingsWidgets->setCurrentIndex(index); } void SettingsWidget::onUpdateAvailable(void) { settingsWidgets->tabBar()->setProperty("update-available", true); settingsWidgets->tabBar()->style()->unpolish(settingsWidgets->tabBar()); settingsWidgets->tabBar()->style()->polish(settingsWidgets->tabBar()); } void SettingsWidget::retranslateUi() { for (size_t i = 0; i < cfgForms.size(); ++i) settingsWidgets->setTabText(i, cfgForms[i]->getFormName()); } qTox/src/widget/form/settingswidget.h000066400000000000000000000033641415623743500202630ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SETTINGSWIDGET_H #define SETTINGSWIDGET_H #include #include #include #include #include class Camera; class GenericForm; class GeneralForm; class IAudioControl; class PrivacyForm; class AVForm; class QLabel; class QTabWidget; class ContentLayout; class UpdateCheck; class Widget; class SettingsWidget : public QWidget { Q_OBJECT public: SettingsWidget(UpdateCheck* updateCheck, IAudioControl& audio, Widget* parent = nullptr); ~SettingsWidget(); bool isShown() const; void show(ContentLayout* contentLayout); void setBodyHeadStyle(QString style); void showAbout(); public slots: void onUpdateAvailable(void); private slots: void onTabChanged(int); private: void retranslateUi(); private: std::unique_ptr bodyLayout; std::unique_ptr settingsWidgets; std::array, 6> cfgForms; int currentIndex; }; #endif // SETTINGSWIDGET_H qTox/src/widget/form/tabcompleter.cpp000066400000000000000000000114561415623743500202340ustar00rootroot00000000000000/* Copyright © 2005-2014 by the Quassel Project devel@quassel-irc.org Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "tabcompleter.h" #include #include #include "src/model/group.h" #include "src/widget/tool/chattextedit.h" /** * @file tabcompleter.h * @file tabcompleter.cpp * These files were taken from the Quassel IRC client source (src/uisupport), and * was greatly simplified for use in qTox. */ const QString TabCompleter::nickSuffix = QString(": "); TabCompleter::TabCompleter(ChatTextEdit* msgEdit, Group* group) : QObject{msgEdit} , msgEdit{msgEdit} , group{group} , enabled{false} , lastCompletionLength{0} { } /* from quassel/src/uisupport/multilineedit.h // Compatibility methods with the rest of the classes which still expect this to be a QLineEdit inline QString text() const { return toPlainText(); } inline QString html() const { return toHtml(); } inline int cursorPosition() const { return textCursor().position(); } inline void insert(const QString &newText) { insertPlainText(newText); } inline void backspace() { keyPressEvent(new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier)); } */ void TabCompleter::buildCompletionList() { // ensure a safe state in case we return early. completionMap.clear(); nextCompletion = completionMap.begin(); // split the string on the given RE (not chars, nums or braces/brackets) and take the last // section QString tabAbbrev = msgEdit->toPlainText() .left(msgEdit->textCursor().position()) .section(QRegExp("[^\\w\\d\\$:@--_\\[\\]{}|`^.\\\\]"), -1, -1); // that section is then used as the completion regex QRegExp regex(QString("^[-_\\[\\]{}|`^.\\\\]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive); const QString ownNick = group->getSelfName(); for (const auto& name : group->getPeerList()) { if (name == ownNick) { continue; // don't auto complete own name } if (regex.indexIn(name) > -1) { SortableString lower = SortableString(name.toLower()); completionMap[lower] = name; } } nextCompletion = completionMap.begin(); lastCompletionLength = tabAbbrev.length(); } void TabCompleter::complete() { if (!enabled) { buildCompletionList(); enabled = true; } if (nextCompletion != completionMap.end()) { // clear previous completion auto cur = msgEdit->textCursor(); cur.setPosition(cur.selectionEnd()); msgEdit->setTextCursor(cur); for (int i = 0; i < lastCompletionLength; ++i) { msgEdit->textCursor().deletePreviousChar(); } // insert completion msgEdit->insertPlainText(*nextCompletion); // remember charcount to delete next time and advance to next completion lastCompletionLength = nextCompletion->length(); ++nextCompletion; // we're completing the first word of the line if (msgEdit->textCursor().position() == lastCompletionLength) { msgEdit->insertPlainText(nickSuffix); lastCompletionLength += nickSuffix.length(); } } else { // we're at the end of the list -> start over again if (!completionMap.isEmpty()) { nextCompletion = completionMap.begin(); complete(); } } } void TabCompleter::reset() { enabled = false; } // this determines the sort order bool TabCompleter::SortableString::operator<(const SortableString& other) const { /* QDateTime thisTime = thisUser->lastChannelActivity(_currentBufferId); QDateTime thatTime = thatUser->lastChannelActivity(_currentBufferId); if (thisTime.isValid() || thatTime.isValid()) return thisTime > thatTime; */ // this could be a // useful feature at // some point return QString::localeAwareCompare(this->contents, other.contents) < 0; } qTox/src/widget/form/tabcompleter.h000066400000000000000000000032241415623743500176730ustar00rootroot00000000000000/* Copyright © 2005-2014 by the Quassel Project devel@quassel-irc.org Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TABCOMPLETER_H #define TABCOMPLETER_H #include "src/model/group.h" #include "src/widget/tool/chattextedit.h" #include #include class TabCompleter : public QObject { Q_OBJECT public: TabCompleter(ChatTextEdit* msgEdit, Group* group); public slots: void complete(); void reset(); private: struct SortableString { explicit SortableString(const QString& n) : contents{n} { } bool operator<(const SortableString& other) const; QString contents; }; ChatTextEdit* msgEdit; Group* group; bool enabled; const static QString nickSuffix; QMap completionMap; QMap::Iterator nextCompletion; int lastCompletionLength; void buildCompletionList(); }; #endif qTox/src/widget/friendlistlayout.cpp000066400000000000000000000077361415623743500202170ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "friendlistlayout.h" #include "friendlistwidget.h" #include "friendwidget.h" #include "src/model/friend.h" #include "src/model/status.h" #include "src/friendlist.h" #include FriendListLayout::FriendListLayout() : QVBoxLayout() { init(); } FriendListLayout::FriendListLayout(QWidget* parent) : QVBoxLayout(parent) { init(); } void FriendListLayout::init() { setSpacing(0); setMargin(0); friendOnlineLayout.getLayout()->setSpacing(0); friendOnlineLayout.getLayout()->setMargin(0); friendOfflineLayout.getLayout()->setSpacing(0); friendOfflineLayout.getLayout()->setMargin(0); addLayout(friendOnlineLayout.getLayout()); addLayout(friendOfflineLayout.getLayout()); } void FriendListLayout::addFriendWidget(FriendWidget* w, Status::Status s) { friendOfflineLayout.removeSortedWidget(w); friendOnlineLayout.removeSortedWidget(w); if (s == Status::Status::Offline) { friendOfflineLayout.addSortedWidget(w); return; } friendOnlineLayout.addSortedWidget(w); } void FriendListLayout::removeFriendWidget(FriendWidget* widget, Status::Status s) { if (s == Status::Status::Offline) friendOfflineLayout.removeSortedWidget(widget); else friendOnlineLayout.removeSortedWidget(widget); } int FriendListLayout::indexOfFriendWidget(GenericChatItemWidget* widget, bool online) const { if (online) return friendOnlineLayout.indexOfSortedWidget(widget); return friendOfflineLayout.indexOfSortedWidget(widget); } void FriendListLayout::moveFriendWidgets(FriendListWidget* listWidget) { while (!friendOnlineLayout.getLayout()->isEmpty()) { QWidget* getWidget = friendOnlineLayout.getLayout()->takeAt(0)->widget(); FriendWidget* friendWidget = qobject_cast(getWidget); const Friend* f = friendWidget->getFriend(); listWidget->moveWidget(friendWidget, f->getStatus(), true); } while (!friendOfflineLayout.getLayout()->isEmpty()) { QWidget* getWidget = friendOfflineLayout.getLayout()->takeAt(0)->widget(); FriendWidget* friendWidget = qobject_cast(getWidget); const Friend* f = friendWidget->getFriend(); listWidget->moveWidget(friendWidget, f->getStatus(), true); } } int FriendListLayout::friendOnlineCount() const { return friendOnlineLayout.getLayout()->count(); } int FriendListLayout::friendTotalCount() const { return friendOfflineLayout.getLayout()->count() + friendOnlineCount(); } bool FriendListLayout::hasChatrooms() const { return !(friendOfflineLayout.getLayout()->isEmpty() && friendOnlineLayout.getLayout()->isEmpty()); } void FriendListLayout::searchChatrooms(const QString& searchString, bool hideOnline, bool hideOffline) { friendOnlineLayout.search(searchString, hideOnline); friendOfflineLayout.search(searchString, hideOffline); } QLayout* FriendListLayout::getLayoutOnline() const { return friendOnlineLayout.getLayout(); } QLayout* FriendListLayout::getLayoutOffline() const { return friendOfflineLayout.getLayout(); } QLayout* FriendListLayout::getFriendLayout(Status::Status s) const { return s == Status::Status::Offline ? friendOfflineLayout.getLayout() : friendOnlineLayout.getLayout(); } qTox/src/widget/friendlistlayout.h000066400000000000000000000035761415623743500176620ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FRIENDLISTLAYOUT_H #define FRIENDLISTLAYOUT_H #include "genericchatitemlayout.h" #include "src/model/status.h" #include "src/core/core.h" #include class FriendWidget; class FriendListWidget; class FriendListLayout : public QVBoxLayout { Q_OBJECT public: explicit FriendListLayout(); explicit FriendListLayout(QWidget* parent); void addFriendWidget(FriendWidget* widget, Status::Status s); void removeFriendWidget(FriendWidget* widget, Status::Status s); int indexOfFriendWidget(GenericChatItemWidget* widget, bool online) const; void moveFriendWidgets(FriendListWidget* listWidget); int friendOnlineCount() const; int friendTotalCount() const; bool hasChatrooms() const; void searchChatrooms(const QString& searchString, bool hideOnline = false, bool hideOffline = false); QLayout* getLayoutOnline() const; QLayout* getLayoutOffline() const; private: void init(); QLayout* getFriendLayout(Status::Status s) const; GenericChatItemLayout friendOnlineLayout; GenericChatItemLayout friendOfflineLayout; }; #endif // FRIENDLISTLAYOUT_H qTox/src/widget/friendlistwidget.cpp000066400000000000000000000564571415623743500201710ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "friendlistwidget.h" #include "circlewidget.h" #include "friendlistlayout.h" #include "friendwidget.h" #include "groupwidget.h" #include "widget.h" #include "src/friendlist.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/model/status.h" #include "src/persistence/settings.h" #include "src/widget/categorywidget.h" #include #include #include #include #include #include enum class Time { Today, Yesterday, ThisWeek, ThisMonth, Month1Ago, Month2Ago, Month3Ago, Month4Ago, Month5Ago, LongAgo, Never }; static const int LAST_TIME = static_cast(Time::Never); Time getTimeBucket(const QDateTime& date) { if (date == QDateTime()) { return Time::Never; } QDate today = QDate::currentDate(); // clang-format off const QMap dates { { Time::Today, today.addDays(0) }, { Time::Yesterday, today.addDays(-1) }, { Time::ThisWeek, today.addDays(-6) }, { Time::ThisMonth, today.addMonths(-1) }, { Time::Month1Ago, today.addMonths(-2) }, { Time::Month2Ago, today.addMonths(-3) }, { Time::Month3Ago, today.addMonths(-4) }, { Time::Month4Ago, today.addMonths(-5) }, { Time::Month5Ago, today.addMonths(-6) }, }; // clang-format on for (Time time : dates.keys()) { if (dates[time] <= date.date()) { return time; } } return Time::LongAgo; } QDateTime getActiveTimeFriend(const Friend* contact) { return Settings::getInstance().getFriendActivity(contact->getPublicKey()); } qint64 timeUntilTomorrow() { QDateTime now = QDateTime::currentDateTime(); QDateTime tomorrow = now.addDays(1); // Tomorrow. tomorrow.setTime(QTime()); // Midnight. return now.msecsTo(tomorrow); } FriendListWidget::FriendListWidget(Widget* parent, bool groupsOnTop) : QWidget(parent) , groupsOnTop(groupsOnTop) { listLayout = new FriendListLayout(); setLayout(listLayout); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); groupLayout.getLayout()->setSpacing(0); groupLayout.getLayout()->setMargin(0); // Prevent QLayout's add child warning before setting the mode. listLayout->removeItem(listLayout->getLayoutOnline()); listLayout->removeItem(listLayout->getLayoutOffline()); mode = Settings::getInstance().getFriendSortingMode(); sortByMode(mode); onGroupchatPositionChanged(groupsOnTop); dayTimer = new QTimer(this); dayTimer->setTimerType(Qt::VeryCoarseTimer); connect(dayTimer, &QTimer::timeout, this, &FriendListWidget::dayTimeout); dayTimer->start(timeUntilTomorrow()); setAcceptDrops(true); } FriendListWidget::~FriendListWidget() { if (activityLayout != nullptr) { QLayoutItem* item; while ((item = activityLayout->takeAt(0)) != nullptr) { delete item->widget(); delete item; } delete activityLayout; } if (circleLayout != nullptr) { QLayoutItem* item; while ((item = circleLayout->getLayout()->takeAt(0)) != nullptr) { delete item->widget(); delete item; } delete circleLayout; } } void FriendListWidget::setMode(SortingMode mode) { if (this->mode == mode) return; this->mode = mode; Settings::getInstance().setFriendSortingMode(mode); sortByMode(mode); } void FriendListWidget::sortByMode(SortingMode mode) { if (mode == SortingMode::Name) { circleLayout = new GenericChatItemLayout; circleLayout->getLayout()->setSpacing(0); circleLayout->getLayout()->setMargin(0); for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i) { addCircleWidget(i); CircleWidget::getFromID(i)->setVisible(false); } // Only display circles once all created to avoid artifacts. for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i) CircleWidget::getFromID(i)->setVisible(true); int count = activityLayout ? activityLayout->count() : 0; for (int i = 0; i < count; i++) { QWidget* widget = activityLayout->itemAt(i)->widget(); CategoryWidget* categoryWidget = qobject_cast(widget); if (categoryWidget) { categoryWidget->moveFriendWidgets(this); } else { qWarning() << "Unexpected widget"; } } listLayout->addLayout(listLayout->getLayoutOnline()); listLayout->addLayout(listLayout->getLayoutOffline()); listLayout->addLayout(circleLayout->getLayout()); onGroupchatPositionChanged(groupsOnTop); if (activityLayout != nullptr) { QLayoutItem* item; while ((item = activityLayout->takeAt(0)) != nullptr) { delete item->widget(); delete item; } delete activityLayout; activityLayout = nullptr; } reDraw(); } else if (mode == SortingMode::Activity) { QLocale ql(Settings::getInstance().getTranslation()); QDate today = QDate::currentDate(); #define COMMENT "Category for sorting friends by activity" // clang-format off const QMap names { { Time::Today, tr("Today", COMMENT) }, { Time::Yesterday, tr("Yesterday", COMMENT) }, { Time::ThisWeek, tr("Last 7 days", COMMENT) }, { Time::ThisMonth, tr("This month", COMMENT) }, { Time::LongAgo, tr("Older than 6 Months", COMMENT) }, { Time::Never, tr("Never", COMMENT) }, { Time::Month1Ago, ql.monthName(today.addMonths(-1).month()) }, { Time::Month2Ago, ql.monthName(today.addMonths(-2).month()) }, { Time::Month3Ago, ql.monthName(today.addMonths(-3).month()) }, { Time::Month4Ago, ql.monthName(today.addMonths(-4).month()) }, { Time::Month5Ago, ql.monthName(today.addMonths(-5).month()) }, }; // clang-format on #undef COMMENT activityLayout = new QVBoxLayout(); bool compact = Settings::getInstance().getCompactLayout(); for (Time t : names.keys()) { CategoryWidget* category = new CategoryWidget(compact, this); category->setName(names[t]); activityLayout->addWidget(category); } moveFriends(listLayout->getLayoutOffline()); moveFriends(listLayout->getLayoutOnline()); if (circleLayout != nullptr) { moveFriends(circleLayout->getLayout()); } for (int i = 0; i < activityLayout->count(); ++i) { QWidget* widget = activityLayout->itemAt(i)->widget(); CategoryWidget* categoryWidget = qobject_cast(widget); categoryWidget->setVisible(categoryWidget->hasChatrooms()); } listLayout->removeItem(listLayout->getLayoutOnline()); listLayout->removeItem(listLayout->getLayoutOffline()); if (circleLayout != nullptr) { listLayout->removeItem(circleLayout->getLayout()); QLayoutItem* item; while ((item = circleLayout->getLayout()->takeAt(0)) != nullptr) { delete item->widget(); delete item; } delete circleLayout; circleLayout = nullptr; } listLayout->insertLayout(1, activityLayout); reDraw(); } } void FriendListWidget::moveFriends(QLayout* layout) { while (!layout->isEmpty()) { QWidget* widget = layout->itemAt(0)->widget(); FriendWidget* friendWidget = qobject_cast(widget); CircleWidget* circleWidget = qobject_cast(widget); if (circleWidget) { circleWidget->moveFriendWidgets(this); } else if (friendWidget) { const Friend* contact = friendWidget->getFriend(); auto* categoryWidget = getTimeCategoryWidget(contact); categoryWidget->addFriendWidget(friendWidget, contact->getStatus()); } } } CategoryWidget* FriendListWidget::getTimeCategoryWidget(const Friend* frd) const { const auto activityTime = getActiveTimeFriend(frd); int timeIndex = static_cast(getTimeBucket(activityTime)); QWidget* widget = activityLayout->itemAt(timeIndex)->widget(); return qobject_cast(widget); } FriendListWidget::SortingMode FriendListWidget::getMode() const { return mode; } void FriendListWidget::addGroupWidget(GroupWidget* widget) { groupLayout.addSortedWidget(widget); Group* g = widget->getGroup(); connect(g, &Group::titleChanged, [=](const QString& author, const QString& name) { Q_UNUSED(author); renameGroupWidget(widget, name); }); } void FriendListWidget::addFriendWidget(FriendWidget* w, Status::Status s, int circleIndex) { CircleWidget* circleWidget = CircleWidget::getFromID(circleIndex); if (circleWidget == nullptr) moveWidget(w, s, true); else circleWidget->addFriendWidget(w, s); connect(w, &FriendWidget::friendWidgetRenamed, this, &FriendListWidget::onFriendWidgetRenamed); } void FriendListWidget::removeGroupWidget(GroupWidget* w) { groupLayout.removeSortedWidget(w); w->deleteLater(); } void FriendListWidget::removeFriendWidget(FriendWidget* w) { const Friend* contact = w->getFriend(); if (mode == SortingMode::Activity) { auto* categoryWidget = getTimeCategoryWidget(contact); categoryWidget->removeFriendWidget(w, contact->getStatus()); categoryWidget->setVisible(categoryWidget->hasChatrooms()); } else { int id = Settings::getInstance().getFriendCircleID(contact->getPublicKey()); CircleWidget* circleWidget = CircleWidget::getFromID(id); if (circleWidget != nullptr) { circleWidget->removeFriendWidget(w, contact->getStatus()); emit searchCircle(*circleWidget); } } } void FriendListWidget::addCircleWidget(int id) { createCircleWidget(id); } void FriendListWidget::addCircleWidget(FriendWidget* friendWidget) { CircleWidget* circleWidget = createCircleWidget(); if (circleWidget != nullptr) { if (friendWidget != nullptr) { const Friend* f = friendWidget->getFriend(); ToxPk toxPk = f->getPublicKey(); int circleId = Settings::getInstance().getFriendCircleID(toxPk); CircleWidget* circleOriginal = CircleWidget::getFromID(circleId); circleWidget->addFriendWidget(friendWidget, f->getStatus()); circleWidget->setExpanded(true); if (circleOriginal != nullptr) emit searchCircle(*circleOriginal); } emit searchCircle(*circleWidget); if (window()->isActiveWindow()) circleWidget->editName(); } reDraw(); } void FriendListWidget::removeCircleWidget(CircleWidget* widget) { circleLayout->removeSortedWidget(widget); widget->deleteLater(); } void FriendListWidget::searchChatrooms(const QString& searchString, bool hideOnline, bool hideOffline, bool hideGroups) { groupLayout.search(searchString, hideGroups); listLayout->searchChatrooms(searchString, hideOnline, hideOffline); if (circleLayout != nullptr) { for (int i = 0; i != circleLayout->getLayout()->count(); ++i) { CircleWidget* circleWidget = static_cast(circleLayout->getLayout()->itemAt(i)->widget()); circleWidget->search(searchString, true, hideOnline, hideOffline); } } else if (activityLayout != nullptr) { for (int i = 0; i != activityLayout->count(); ++i) { CategoryWidget* categoryWidget = static_cast(activityLayout->itemAt(i)->widget()); categoryWidget->search(searchString, true, hideOnline, hideOffline); categoryWidget->setVisible(categoryWidget->hasChatrooms()); } } } void FriendListWidget::renameGroupWidget(GroupWidget* groupWidget, const QString& newName) { groupLayout.removeSortedWidget(groupWidget); groupLayout.addSortedWidget(groupWidget); } void FriendListWidget::renameCircleWidget(CircleWidget* circleWidget, const QString& newName) { circleLayout->removeSortedWidget(circleWidget); circleWidget->setName(newName); circleLayout->addSortedWidget(circleWidget); } void FriendListWidget::onFriendWidgetRenamed(FriendWidget* friendWidget) { const Friend* contact = friendWidget->getFriend(); auto status = contact->getStatus(); if (mode == SortingMode::Activity) { auto* categoryWidget = getTimeCategoryWidget(contact); categoryWidget->removeFriendWidget(friendWidget, status); categoryWidget->addFriendWidget(friendWidget, status); } else { int id = Settings::getInstance().getFriendCircleID(contact->getPublicKey()); CircleWidget* circleWidget = CircleWidget::getFromID(id); if (circleWidget != nullptr) { circleWidget->removeFriendWidget(friendWidget, status); circleWidget->addFriendWidget(friendWidget, status); emit searchCircle(*circleWidget); } else { listLayout->removeFriendWidget(friendWidget, status); listLayout->addFriendWidget(friendWidget, status); } } } void FriendListWidget::onGroupchatPositionChanged(bool top) { groupsOnTop = top; if (mode != SortingMode::Name) return; listLayout->removeItem(groupLayout.getLayout()); if (top) listLayout->insertLayout(0, groupLayout.getLayout()); else listLayout->insertLayout(1, groupLayout.getLayout()); reDraw(); } void FriendListWidget::cycleContacts(GenericChatroomWidget* activeChatroomWidget, bool forward) { if (!activeChatroomWidget) { return; } int index = -1; FriendWidget* friendWidget = qobject_cast(activeChatroomWidget); if (mode == SortingMode::Activity) { if (!friendWidget) { return; } const auto activityTime = getActiveTimeFriend(friendWidget->getFriend()); index = static_cast(getTimeBucket(activityTime)); QWidget* widget = activityLayout->itemAt(index)->widget(); CategoryWidget* categoryWidget = qobject_cast(widget); if (categoryWidget == nullptr || categoryWidget->cycleContacts(friendWidget, forward)) { return; } index += forward ? 1 : -1; for (;;) { // Bounds checking. if (index < 0) { index = LAST_TIME; continue; } else if (index > LAST_TIME) { index = 0; continue; } auto* widget = activityLayout->itemAt(index)->widget(); categoryWidget = qobject_cast(widget); if (categoryWidget != nullptr) { if (!categoryWidget->cycleContacts(forward)) { // Skip empty or finished categories. index += forward ? 1 : -1; continue; } } break; } return; } QLayout* currentLayout = nullptr; CircleWidget* circleWidget = nullptr; if (friendWidget != nullptr) { const ToxPk& pk = friendWidget->getFriend()->getPublicKey(); uint32_t circleId = Settings::getInstance().getFriendCircleID(pk); circleWidget = CircleWidget::getFromID(circleId); if (circleWidget != nullptr) { if (circleWidget->cycleContacts(friendWidget, forward)) { return; } index = circleLayout->indexOfSortedWidget(circleWidget); currentLayout = circleLayout->getLayout(); } else { currentLayout = listLayout->getLayoutOnline(); index = listLayout->indexOfFriendWidget(friendWidget, true); if (index == -1) { currentLayout = listLayout->getLayoutOffline(); index = listLayout->indexOfFriendWidget(friendWidget, false); } } } else { GroupWidget* groupWidget = qobject_cast(activeChatroomWidget); if (groupWidget != nullptr) { currentLayout = groupLayout.getLayout(); index = groupLayout.indexOfSortedWidget(groupWidget); } else { return; }; } index += forward ? 1 : -1; for (;;) { // Bounds checking. if (index < 0) { currentLayout = nextLayout(currentLayout, forward); index = currentLayout->count() - 1; continue; } else if (index >= currentLayout->count()) { currentLayout = nextLayout(currentLayout, forward); index = 0; continue; } // Go to the actual next index. if (currentLayout == listLayout->getLayoutOnline() || currentLayout == listLayout->getLayoutOffline() || currentLayout == groupLayout.getLayout()) { GenericChatroomWidget* chatWidget = qobject_cast(currentLayout->itemAt(index)->widget()); if (chatWidget != nullptr) emit chatWidget->chatroomWidgetClicked(chatWidget); return; } else if (currentLayout == circleLayout->getLayout()) { circleWidget = qobject_cast(currentLayout->itemAt(index)->widget()); if (circleWidget != nullptr) { if (!circleWidget->cycleContacts(forward)) { // Skip empty or finished circles. index += forward ? 1 : -1; continue; } } return; } else { return; } } } void FriendListWidget::dragEnterEvent(QDragEnterEvent* event) { if (!event->mimeData()->hasFormat("toxPk")) { return; } ToxPk toxPk(event->mimeData()->data("toxPk")); Friend* frnd = FriendList::findFriend(toxPk); if (frnd) event->acceptProposedAction(); } void FriendListWidget::dropEvent(QDropEvent* event) { // Check, that the element is dropped from qTox QObject* o = event->source(); FriendWidget* widget = qobject_cast(o); if (!widget) return; // Check, that the user has a friend with the same ToxPk assert(event->mimeData()->hasFormat("toxPk")); const ToxPk toxPk{event->mimeData()->data("toxPk")}; Friend* f = FriendList::findFriend(toxPk); if (!f) return; // Save CircleWidget before changing the Id int circleId = Settings::getInstance().getFriendCircleID(f->getPublicKey()); CircleWidget* circleWidget = CircleWidget::getFromID(circleId); moveWidget(widget, f->getStatus(), true); if (circleWidget) circleWidget->updateStatus(); } void FriendListWidget::dayTimeout() { if (mode == SortingMode::Activity) { setMode(SortingMode::Name); setMode(SortingMode::Activity); // Refresh all. } dayTimer->start(timeUntilTomorrow()); } void FriendListWidget::moveWidget(FriendWidget* widget, Status::Status s, bool add) { if (mode == SortingMode::Name) { const Friend* f = widget->getFriend(); int circleId = Settings::getInstance().getFriendCircleID(f->getPublicKey()); CircleWidget* circleWidget = CircleWidget::getFromID(circleId); if (circleWidget == nullptr || add) { if (circleId != -1) Settings::getInstance().setFriendCircleID(f->getPublicKey(), -1); listLayout->addFriendWidget(widget, s); return; } circleWidget->addFriendWidget(widget, s); } else { const Friend* contact = widget->getFriend(); auto* categoryWidget = getTimeCategoryWidget(contact); categoryWidget->addFriendWidget(widget, contact->getStatus()); categoryWidget->show(); } } void FriendListWidget::updateActivityTime(const QDateTime& time) { if (mode != SortingMode::Activity) return; int timeIndex = static_cast(getTimeBucket(time)); QWidget* widget = activityLayout->itemAt(timeIndex)->widget(); CategoryWidget* categoryWidget = static_cast(widget); categoryWidget->updateStatus(); categoryWidget->setVisible(categoryWidget->hasChatrooms()); } // update widget after add/delete/hide/show void FriendListWidget::reDraw() { hide(); show(); resize(QSize()); // lifehack } CircleWidget* FriendListWidget::createCircleWidget(int id) { if (id == -1) id = Settings::getInstance().addCircle(); // Stop, after it has been created. Code after this is for displaying. if (mode == SortingMode::Activity) return nullptr; assert(circleLayout != nullptr); CircleWidget* circleWidget = new CircleWidget(this, id); emit connectCircleWidget(*circleWidget); circleLayout->addSortedWidget(circleWidget); connect(this, &FriendListWidget::onCompactChanged, circleWidget, &CircleWidget::onCompactChanged); connect(circleWidget, &CircleWidget::renameRequested, this, &FriendListWidget::renameCircleWidget); circleWidget->show(); // Avoid flickering. return circleWidget; } QLayout* FriendListWidget::nextLayout(QLayout* layout, bool forward) const { if (layout == groupLayout.getLayout()) { if (forward) { if (groupsOnTop) return listLayout->getLayoutOnline(); return listLayout->getLayoutOffline(); } else { if (groupsOnTop) return circleLayout->getLayout(); return listLayout->getLayoutOnline(); } } else if (layout == listLayout->getLayoutOnline()) { if (forward) { if (groupsOnTop) return listLayout->getLayoutOffline(); return groupLayout.getLayout(); } else { if (groupsOnTop) return groupLayout.getLayout(); return circleLayout->getLayout(); } } else if (layout == listLayout->getLayoutOffline()) { if (forward) return circleLayout->getLayout(); else if (groupsOnTop) return listLayout->getLayoutOnline(); return groupLayout.getLayout(); } else if (layout == circleLayout->getLayout()) { if (forward) { if (groupsOnTop) return groupLayout.getLayout(); return listLayout->getLayoutOnline(); } else return listLayout->getLayoutOffline(); } return nullptr; } qTox/src/widget/friendlistwidget.h000066400000000000000000000064321415623743500176220ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FRIENDLISTWIDGET_H #define FRIENDLISTWIDGET_H #include "genericchatitemlayout.h" #include "src/core/core.h" #include "src/model/status.h" #include "src/persistence/settings.h" #include class QVBoxLayout; class QGridLayout; class QPixmap; class Widget; class FriendWidget; class GroupWidget; class CircleWidget; class FriendListLayout; class GenericChatroomWidget; class CategoryWidget; class Friend; class FriendListWidget : public QWidget { Q_OBJECT public: using SortingMode = Settings::FriendListSortingMode; explicit FriendListWidget(Widget* parent, bool groupsOnTop = true); ~FriendListWidget(); void setMode(SortingMode mode); SortingMode getMode() const; void addGroupWidget(GroupWidget* widget); void addFriendWidget(FriendWidget* w, Status::Status s, int circleIndex); void removeGroupWidget(GroupWidget* w); void removeFriendWidget(FriendWidget* w); void addCircleWidget(int id); void addCircleWidget(FriendWidget* widget = nullptr); void removeCircleWidget(CircleWidget* widget); void searchChatrooms(const QString& searchString, bool hideOnline = false, bool hideOffline = false, bool hideGroups = false); void cycleContacts(GenericChatroomWidget* activeChatroomWidget, bool forward); void updateActivityTime(const QDateTime& date); void reDraw(); signals: void onCompactChanged(bool compact); void connectCircleWidget(CircleWidget& circleWidget); void searchCircle(CircleWidget& circleWidget); public slots: void renameGroupWidget(GroupWidget* groupWidget, const QString& newName); void renameCircleWidget(CircleWidget* circleWidget, const QString& newName); void onFriendWidgetRenamed(FriendWidget* friendWidget); void onGroupchatPositionChanged(bool top); void moveWidget(FriendWidget* w, Status::Status s, bool add = false); protected: void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; private slots: void dayTimeout(); private: CircleWidget* createCircleWidget(int id = -1); QLayout* nextLayout(QLayout* layout, bool forward) const; void moveFriends(QLayout* layout); CategoryWidget* getTimeCategoryWidget(const Friend* frd) const; void sortByMode(SortingMode mode); SortingMode mode; bool groupsOnTop; FriendListLayout* listLayout; GenericChatItemLayout* circleLayout = nullptr; GenericChatItemLayout groupLayout; QVBoxLayout* activityLayout = nullptr; QTimer* dayTimer; }; #endif // FRIENDLISTWIDGET_H qTox/src/widget/friendwidget.cpp000066400000000000000000000326241415623743500172630ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "friendwidget.h" #include "circlewidget.h" #include "friendlistwidget.h" #include "groupwidget.h" #include "maskablepixmapwidget.h" #include "src/core/core.h" #include "src/friendlist.h" #include "src/model/about/aboutfriend.h" #include "src/model/chatroom/friendchatroom.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/model/status.h" #include "src/persistence/settings.h" #include "src/widget/about/aboutfriendform.h" #include "src/widget/form/chatform.h" #include "src/widget/style.h" #include "src/widget/tool/croppinglabel.h" #include "src/widget/widget.h" #include #include #include #include #include #include #include #include #include #include /** * @class FriendWidget * * Widget, which displays brief information about friend. * For example, used on friend list. * When you click should open the chat with friend. Widget has a context menu. */ FriendWidget::FriendWidget(std::shared_ptr chatroom, bool compact) : GenericChatroomWidget(compact) , chatroom{chatroom} , isDefaultAvatar{true} { avatar->setPixmap(QPixmap(":/img/contact.svg")); statusPic.setPixmap(QPixmap(Status::getIconPath(Status::Status::Offline))); statusPic.setMargin(3); auto frnd = chatroom->getFriend(); nameLabel->setText(frnd->getDisplayedName()); // update alias when edited connect(nameLabel, &CroppingLabel::editFinished, frnd, &Friend::setAlias); // update on changes of the displayed name connect(frnd, &Friend::displayedNameChanged, nameLabel, &CroppingLabel::setText); connect(frnd, &Friend::displayedNameChanged, this, [this](const QString /* &newName */) { emit friendWidgetRenamed(this); }); connect(chatroom.get(), &FriendChatroom::activeChanged, this, &FriendWidget::setActive); statusMessageLabel->setTextFormat(Qt::PlainText); } /** * @brief FriendWidget::contextMenuEvent * @param event Describe a context menu event * * Default context menu event handler. * Redirect all event information to the signal. */ void FriendWidget::contextMenuEvent(QContextMenuEvent* event) { emit contextMenuCalled(event); } /** * @brief FriendWidget::onContextMenuCalled * @param event Redirected from native contextMenuEvent * * Context menu handler. Always should be called to FriendWidget from FriendList */ void FriendWidget::onContextMenuCalled(QContextMenuEvent* event) { if (!active) { setBackgroundRole(QPalette::Highlight); } installEventFilter(this); // Disable leave event. QMenu menu; if (chatroom->possibleToOpenInNewWindow()) { const auto openChatWindow = menu.addAction(tr("Open chat in new window")); connect(openChatWindow, &QAction::triggered, [=]() { emit newWindowOpened(this); }); } if (chatroom->canBeRemovedFromWindow()) { const auto removeChatWindow = menu.addAction(tr("Remove chat from this window")); connect(removeChatWindow, &QAction::triggered, this, &FriendWidget::removeChatWindow); } menu.addSeparator(); QMenu* inviteMenu = menu.addMenu(tr("Invite to group", "Menu to invite a friend to a groupchat")); inviteMenu->setEnabled(chatroom->canBeInvited()); const auto newGroupAction = inviteMenu->addAction(tr("To new group")); connect(newGroupAction, &QAction::triggered, chatroom.get(), &FriendChatroom::inviteToNewGroup); inviteMenu->addSeparator(); for (const auto group : chatroom->getGroups()) { const auto groupAction = inviteMenu->addAction(tr("Invite to group '%1'").arg(group.name)); connect(groupAction, &QAction::triggered, [=]() { chatroom->inviteFriend(group.group); }); } const auto circleId = chatroom->getCircleId(); auto circleMenu = menu.addMenu(tr("Move to circle...", "Menu to move a friend into a different circle")); const auto newCircleAction = circleMenu->addAction(tr("To new circle")); connect(newCircleAction, &QAction::triggered, this, &FriendWidget::moveToNewCircle); if (circleId != -1) { const auto circleName = chatroom->getCircleName(); const auto removeCircleAction = circleMenu->addAction(tr("Remove from circle '%1'").arg(circleName)); connect(removeCircleAction, &QAction::triggered, this, &FriendWidget::removeFromCircle); } circleMenu->addSeparator(); for (const auto circle : chatroom->getOtherCircles()) { QAction* action = new QAction(tr("Move to circle \"%1\"").arg(circle.name), circleMenu); connect(action, &QAction::triggered, [=]() { moveToCircle(circle.circleId); }); circleMenu->addAction(action); } const auto setAlias = menu.addAction(tr("Set alias...")); connect(setAlias, &QAction::triggered, nameLabel, &CroppingLabel::editBegin); menu.addSeparator(); auto autoAccept = menu.addAction(tr("Auto accept files from this friend", "context menu entry")); autoAccept->setCheckable(true); autoAccept->setChecked(!chatroom->autoAcceptEnabled()); connect(autoAccept, &QAction::triggered, this, &FriendWidget::changeAutoAccept); menu.addSeparator(); if (chatroom->friendCanBeRemoved()) { const auto friendPk = chatroom->getFriend()->getPublicKey(); const auto removeAction = menu.addAction(tr("Remove friend", "Menu to remove the friend from our friendlist")); connect(removeAction, &QAction::triggered, this, [=]() { emit removeFriend(friendPk); }, Qt::QueuedConnection); } menu.addSeparator(); const auto aboutWindow = menu.addAction(tr("Show details")); connect(aboutWindow, &QAction::triggered, this, &FriendWidget::showDetails); const auto pos = event->globalPos(); menu.exec(pos); removeEventFilter(this); if (!active) { setBackgroundRole(QPalette::Window); } } void FriendWidget::removeChatWindow() { chatroom->removeFriendFromDialogs(); } namespace { std::tuple getCircleAndFriendList(const Friend* frnd, FriendWidget* fw) { const auto pk = frnd->getPublicKey(); const auto circleId = Settings::getInstance().getFriendCircleID(pk); auto circleWidget = CircleWidget::getFromID(circleId); auto w = circleWidget ? static_cast(circleWidget) : static_cast(fw); auto friendList = qobject_cast(w->parentWidget()); return std::make_tuple(circleWidget, friendList); } } // namespace void FriendWidget::moveToNewCircle() { const auto frnd = chatroom->getFriend(); CircleWidget* circleWidget; FriendListWidget* friendList; std::tie(circleWidget, friendList) = getCircleAndFriendList(frnd, this); if (circleWidget != nullptr) { circleWidget->updateStatus(); } if (friendList != nullptr) { friendList->addCircleWidget(this); } else { const auto pk = frnd->getPublicKey(); auto& s = Settings::getInstance(); auto circleId = s.addCircle(); s.setFriendCircleID(pk, circleId); } } void FriendWidget::removeFromCircle() { const auto frnd = chatroom->getFriend(); CircleWidget* circleWidget; FriendListWidget* friendList; std::tie(circleWidget, friendList) = getCircleAndFriendList(frnd, this); if (friendList != nullptr) { friendList->moveWidget(this, frnd->getStatus(), true); } else { const auto pk = frnd->getPublicKey(); auto& s = Settings::getInstance(); s.setFriendCircleID(pk, -1); } if (circleWidget != nullptr) { circleWidget->updateStatus(); emit searchCircle(*circleWidget); } } void FriendWidget::moveToCircle(int newCircleId) { const auto frnd = chatroom->getFriend(); const auto pk = frnd->getPublicKey(); const auto oldCircleId = Settings::getInstance().getFriendCircleID(pk); auto& s = Settings::getInstance(); auto oldCircleWidget = CircleWidget::getFromID(oldCircleId); auto newCircleWidget = CircleWidget::getFromID(newCircleId); if (newCircleWidget) { newCircleWidget->addFriendWidget(this, frnd->getStatus()); newCircleWidget->setExpanded(true); emit searchCircle(*newCircleWidget); s.savePersonal(); } else { s.setFriendCircleID(pk, newCircleId); } if (oldCircleWidget) { oldCircleWidget->updateStatus(); emit searchCircle(*oldCircleWidget); } } void FriendWidget::changeAutoAccept(bool enable) { if (enable) { const auto oldDir = chatroom->getAutoAcceptDir(); const auto newDir = QFileDialog::getExistingDirectory(Q_NULLPTR, tr("Choose an auto accept directory", "popup title"), oldDir); chatroom->setAutoAcceptDir(newDir); } else { chatroom->disableAutoAccept(); } } void FriendWidget::showDetails() { const auto frnd = chatroom->getFriend(); const auto iabout = new AboutFriend(frnd, &Settings::getInstance()); std::unique_ptr about = std::unique_ptr(iabout); const auto aboutUser = new AboutFriendForm(std::move(about), this); connect(aboutUser, &AboutFriendForm::histroyRemoved, this, &FriendWidget::friendHistoryRemoved); aboutUser->show(); } void FriendWidget::setAsActiveChatroom() { setActive(true); } void FriendWidget::setAsInactiveChatroom() { setActive(false); } void FriendWidget::setActive(bool active) { GenericChatroomWidget::setActive(active); if (isDefaultAvatar) { const auto uri = active ? QStringLiteral(":img/contact_dark.svg") : QStringLiteral(":img/contact.svg"); avatar->setPixmap(QPixmap{uri}); } } void FriendWidget::updateStatusLight() { const auto frnd = chatroom->getFriend(); const bool event = frnd->getEventFlag(); statusPic.setPixmap(QPixmap(Status::getIconPath(frnd->getStatus(), event))); if (event) { const Settings& s = Settings::getInstance(); const uint32_t circleId = s.getFriendCircleID(frnd->getPublicKey()); CircleWidget* circleWidget = CircleWidget::getFromID(circleId); if (circleWidget) { circleWidget->setExpanded(true); } emit updateFriendActivity(*frnd); } statusPic.setMargin(event ? 1 : 3); } QString FriendWidget::getStatusString() const { const auto frnd = chatroom->getFriend(); const int status = static_cast(frnd->getStatus()); const bool event = frnd->getEventFlag(); static const QVector names = { tr("Online"), tr("Away"), tr("Busy"), tr("Offline"), }; return event ? tr("New message") : names.value(status); } const Friend* FriendWidget::getFriend() const { return chatroom->getFriend(); } const Contact* FriendWidget::getContact() const { return getFriend(); } void FriendWidget::search(const QString& searchString, bool hide) { const auto frnd = chatroom->getFriend(); searchName(searchString, hide); const Settings& s = Settings::getInstance(); const uint32_t circleId = s.getFriendCircleID(frnd->getPublicKey()); CircleWidget* circleWidget = CircleWidget::getFromID(circleId); if (circleWidget) { circleWidget->search(searchString); } } void FriendWidget::resetEventFlags() { chatroom->resetEventFlags(); } void FriendWidget::onAvatarSet(const ToxPk& friendPk, const QPixmap& pic) { const auto frnd = chatroom->getFriend(); if (friendPk != frnd->getPublicKey()) { return; } isDefaultAvatar = false; avatar->setPixmap(pic); } void FriendWidget::onAvatarRemoved(const ToxPk& friendPk) { const auto frnd = chatroom->getFriend(); if (friendPk != frnd->getPublicKey()) { return; } isDefaultAvatar = true; const QString path = QString(":/img/contact%1.svg").arg(isActive() ? "_dark" : ""); avatar->setPixmap(QPixmap(path)); } void FriendWidget::mousePressEvent(QMouseEvent* ev) { if (ev->button() == Qt::LeftButton) { dragStartPos = ev->pos(); } GenericChatroomWidget::mousePressEvent(ev); } void FriendWidget::mouseMoveEvent(QMouseEvent* ev) { if (!(ev->buttons() & Qt::LeftButton)) { return; } const int distance = (dragStartPos - ev->pos()).manhattanLength(); if (distance > QApplication::startDragDistance()) { QMimeData* mdata = new QMimeData; const Friend* frnd = getFriend(); mdata->setText(frnd->getDisplayedName()); mdata->setData("toxPk", frnd->getPublicKey().getByteArray()); QDrag* drag = new QDrag(this); drag->setMimeData(mdata); drag->setPixmap(avatar->getPixmap()); drag->exec(Qt::CopyAction | Qt::MoveAction); } } qTox/src/widget/friendwidget.h000066400000000000000000000052131415623743500167220ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FRIENDWIDGET_H #define FRIENDWIDGET_H #include "genericchatroomwidget.h" #include "src/core/toxpk.h" #include class FriendChatroom; class QPixmap; class MaskablePixmapWidget; class CircleWidget; class FriendWidget : public GenericChatroomWidget { Q_OBJECT public: FriendWidget(std::shared_ptr chatform, bool compact); void contextMenuEvent(QContextMenuEvent* event) override final; void setAsActiveChatroom() override final; void setAsInactiveChatroom() override final; void updateStatusLight() override final; void resetEventFlags() override final; QString getStatusString() const override final; const Friend* getFriend() const override final; const Contact* getContact() const override final; void search(const QString& searchString, bool hide = false); signals: void friendWidgetClicked(FriendWidget* widget); void removeFriend(const ToxPk& friendPk); void copyFriendIdToClipboard(const ToxPk& friendPk); void contextMenuCalled(QContextMenuEvent* event); void friendHistoryRemoved(); void friendWidgetRenamed(FriendWidget* friendWidget); void searchCircle(CircleWidget& circleWidget); void updateFriendActivity(Friend& frnd); public slots: void onAvatarSet(const ToxPk& friendPk, const QPixmap& pic); void onAvatarRemoved(const ToxPk& friendPk); void onContextMenuCalled(QContextMenuEvent* event); void setActive(bool active); protected: virtual void mousePressEvent(QMouseEvent* ev) override; virtual void mouseMoveEvent(QMouseEvent* ev) override; void setFriendAlias(); private slots: void removeChatWindow(); void moveToNewCircle(); void removeFromCircle(); void moveToCircle(int circleId); void changeAutoAccept(bool enable); void showDetails(); public: std::shared_ptr chatroom; bool isDefaultAvatar; }; #endif // FRIENDWIDGET_H qTox/src/widget/genericchatitemlayout.cpp000066400000000000000000000075441415623743500212040ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "genericchatitemlayout.h" #include "genericchatitemwidget.h" #include #include #include // As this layout sorts widget, extra care must be taken when inserting widgets. // Prefer using the build in add and remove functions for modifying widgets. // Inserting widgets other ways would cause this layout to be unable to sort. // As such, they are protected using asserts. GenericChatItemLayout::GenericChatItemLayout() : layout(new QVBoxLayout()) { } GenericChatItemLayout::~GenericChatItemLayout() { delete layout; } void GenericChatItemLayout::addSortedWidget(GenericChatItemWidget* widget, int stretch, Qt::Alignment alignment) { int closest = indexOfClosestSortedWidget(widget); layout->insertWidget(closest, widget, stretch, alignment); } int GenericChatItemLayout::indexOfSortedWidget(GenericChatItemWidget* widget) const { if (layout->isEmpty()) return -1; int index = indexOfClosestSortedWidget(widget); if (index >= layout->count()) return -1; GenericChatItemWidget* atMid = qobject_cast(layout->itemAt(index)->widget()); assert(atMid != nullptr); if (atMid == widget) return index; return -1; } bool GenericChatItemLayout::existsSortedWidget(GenericChatItemWidget* widget) const { return indexOfSortedWidget(widget) != -1; } void GenericChatItemLayout::removeSortedWidget(GenericChatItemWidget* widget) { if (layout->isEmpty()) return; int index = indexOfClosestSortedWidget(widget); if (layout->itemAt(index) == nullptr) return; GenericChatItemWidget* atMid = qobject_cast(layout->itemAt(index)->widget()); assert(atMid != nullptr); if (atMid == widget) layout->removeWidget(widget); } void GenericChatItemLayout::search(const QString& searchString, bool hideAll) { for (int index = 0; index < layout->count(); ++index) { GenericChatItemWidget* widgetAt = qobject_cast(layout->itemAt(index)->widget()); assert(widgetAt != nullptr); widgetAt->searchName(searchString, hideAll); } } QLayout* GenericChatItemLayout::getLayout() const { return layout; } int GenericChatItemLayout::indexOfClosestSortedWidget(GenericChatItemWidget* widget) const { // Binary search: Deferred test of equality. int min = 0, max = layout->count(); while (min < max) { int mid = (max - min) / 2 + min; GenericChatItemWidget* atMid = qobject_cast(layout->itemAt(mid)->widget()); assert(atMid != nullptr); bool lessThan = false; QCollator collator; collator.setNumericMode(true); int compareValue = collator.compare(atMid->getName(), widget->getName()); if (compareValue < 0) lessThan = true; else if (compareValue == 0) lessThan = atMid < widget; // Consistent ordering. if (lessThan) min = mid + 1; else max = mid; } return min; } qTox/src/widget/genericchatitemlayout.h000066400000000000000000000034141415623743500206410ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GENERICCHATITEMLAYOUT_H #define GENERICCHATITEMLAYOUT_H #include class QLayout; class QVBoxLayout; class GenericChatItemWidget; class GenericChatItemLayout { public: GenericChatItemLayout(); GenericChatItemLayout(const GenericChatItemLayout& layout) = delete; ~GenericChatItemLayout(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) void addSortedWidget(GenericChatItemWidget* widget, int stretch = 0, Qt::Alignment alignment = Qt::Alignment()); #else void addSortedWidget(GenericChatItemWidget* widget, int stretch = 0, Qt::Alignment alignment = nullptr); #endif int indexOfSortedWidget(GenericChatItemWidget* widget) const; bool existsSortedWidget(GenericChatItemWidget* widget) const; void removeSortedWidget(GenericChatItemWidget* widget); void search(const QString& searchString, bool hideAll = false); QLayout* getLayout() const; private: int indexOfClosestSortedWidget(GenericChatItemWidget* widget) const; QVBoxLayout* layout; }; #endif // GENERICCHATITEMLAYOUT_H qTox/src/widget/genericchatitemwidget.cpp000066400000000000000000000031341415623743500211410ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "genericchatitemwidget.h" #include "src/persistence/settings.h" #include "src/widget/style.h" #include "src/widget/tool/croppinglabel.h" #include GenericChatItemWidget::GenericChatItemWidget(bool compact, QWidget* parent) : QFrame(parent) , compact(false) { setProperty("compact", compact); nameLabel = new CroppingLabel(this); nameLabel->setObjectName("name"); nameLabel->setTextFormat(Qt::PlainText); } bool GenericChatItemWidget::isCompact() const { return compact; } void GenericChatItemWidget::setCompact(bool compact) { this->compact = compact; } QString GenericChatItemWidget::getName() const { return nameLabel->fullText(); } void GenericChatItemWidget::searchName(const QString& searchString, bool hide) { setVisible(!hide && getName().contains(searchString, Qt::CaseInsensitive)); } qTox/src/widget/genericchatitemwidget.h000066400000000000000000000027231415623743500206110ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GENERICCHATITEMWIDGET_H #define GENERICCHATITEMWIDGET_H #include #include class CroppingLabel; class GenericChatItemWidget : public QFrame { Q_OBJECT public: enum ItemType { GroupItem, FriendOfflineItem, FriendOnlineItem }; explicit GenericChatItemWidget(bool compact, QWidget* parent = nullptr); bool isCompact() const; void setCompact(bool compact); QString getName() const; void searchName(const QString& searchString, bool hideAll); Q_PROPERTY(bool compact READ isCompact WRITE setCompact) protected: CroppingLabel* nameLabel; QLabel statusPic; private: bool compact; }; #endif // GENERICCHATITEMWIDGET_H qTox/src/widget/genericchatroomwidget.cpp000066400000000000000000000142651415623743500211660ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "genericchatroomwidget.h" #include "maskablepixmapwidget.h" #include "src/persistence/settings.h" #include "src/widget/style.h" #include "src/widget/tool/croppinglabel.h" #include #include GenericChatroomWidget::GenericChatroomWidget(bool compact, QWidget* parent) : GenericChatItemWidget(compact, parent) , active{false} { // avatar QSize size; if (isCompact()) size = QSize(20, 20); else size = QSize(40, 40); avatar = new MaskablePixmapWidget(this, size, ":/img/avatar_mask.svg"); // status text statusMessageLabel = new CroppingLabel(this); statusMessageLabel->setTextFormat(Qt::PlainText); statusMessageLabel->setForegroundRole(QPalette::WindowText); nameLabel->setForegroundRole(QPalette::WindowText); Settings& s = Settings::getInstance(); connect(&s, &Settings::compactLayoutChanged, this, &GenericChatroomWidget::compactChange); setAutoFillBackground(true); reloadTheme(); compactChange(isCompact()); } bool GenericChatroomWidget::eventFilter(QObject*, QEvent*) { return true; // Disable all events. } void GenericChatroomWidget::compactChange(bool _compact) { if (!isCompact()) delete textLayout; // has to be first, deleted by layout setCompact(_compact); delete mainLayout; mainLayout = new QHBoxLayout; textLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->setSpacing(0); mainLayout->setMargin(0); textLayout->setSpacing(0); textLayout->setMargin(0); setLayoutDirection(Qt::LeftToRight); // parent might have set Qt::RightToLeft // avatar if (isCompact()) { delete textLayout; // Not needed setFixedHeight(25); avatar->setSize(QSize(20, 20)); mainLayout->addSpacing(18); mainLayout->addWidget(avatar); mainLayout->addSpacing(5); mainLayout->addWidget(nameLabel); mainLayout->addWidget(statusMessageLabel); mainLayout->addSpacing(5); mainLayout->addWidget(&statusPic); mainLayout->addSpacing(5); mainLayout->activate(); statusMessageLabel->setFont(Style::getFont(Style::Small)); nameLabel->setFont(Style::getFont(Style::Medium)); } else { setFixedHeight(55); avatar->setSize(QSize(40, 40)); textLayout->addStretch(); textLayout->addWidget(nameLabel); textLayout->addWidget(statusMessageLabel); textLayout->addStretch(); mainLayout->addSpacing(20); mainLayout->addWidget(avatar); mainLayout->addSpacing(10); mainLayout->addLayout(textLayout); mainLayout->addSpacing(10); mainLayout->addWidget(&statusPic); mainLayout->addSpacing(10); mainLayout->activate(); statusMessageLabel->setFont(Style::getFont(Style::Medium)); nameLabel->setFont(Style::getFont(Style::Big)); } } bool GenericChatroomWidget::isActive() { return active; } void GenericChatroomWidget::setActive(bool _active) { active = _active; if (active) { setBackgroundRole(QPalette::Light); statusMessageLabel->setForegroundRole(QPalette::HighlightedText); nameLabel->setForegroundRole(QPalette::HighlightedText); } else { setBackgroundRole(QPalette::Window); statusMessageLabel->setForegroundRole(QPalette::WindowText); nameLabel->setForegroundRole(QPalette::WindowText); } } void GenericChatroomWidget::setName(const QString& name) { nameLabel->setText(name); } void GenericChatroomWidget::setStatusMsg(const QString& status) { statusMessageLabel->setText(status); } QString GenericChatroomWidget::getStatusMsg() const { return statusMessageLabel->text(); } QString GenericChatroomWidget::getTitle() const { QString title = getName(); if (!getStatusString().isNull()) title += QStringLiteral(" (") + getStatusString() + QStringLiteral(")"); return title; } void GenericChatroomWidget::reloadTheme() { QPalette p; p = statusMessageLabel->palette(); p.setColor(QPalette::WindowText, Style::getColor(Style::GroundExtra)); // Base color p.setColor(QPalette::HighlightedText, Style::getColor(Style::StatusActive)); // Color when active statusMessageLabel->setPalette(p); p = nameLabel->palette(); p.setColor(QPalette::WindowText, Style::getColor(Style::GroundBase)); // Base color p.setColor(QPalette::HighlightedText, Style::getColor(Style::NameActive)); // Color when active nameLabel->setPalette(p); p = palette(); p.setColor(QPalette::Window, Style::getColor(Style::ThemeMedium)); // Base background color p.setColor(QPalette::Highlight, Style::getColor(Style::ThemeLight)); // On mouse over p.setColor(QPalette::Light, Style::getColor(Style::GroundBase)); // When active setPalette(p); } void GenericChatroomWidget::activate() { emit chatroomWidgetClicked(this); } void GenericChatroomWidget::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { emit chatroomWidgetClicked(this); } else if (event->button() == Qt::MiddleButton) { emit middleMouseClicked(); } else { event->ignore(); } } void GenericChatroomWidget::enterEvent(QEvent*) { if (!active) setBackgroundRole(QPalette::Highlight); } void GenericChatroomWidget::leaveEvent(QEvent* event) { if (!active) setBackgroundRole(QPalette::Window); QWidget::leaveEvent(event); } qTox/src/widget/genericchatroomwidget.h000066400000000000000000000047661415623743500206400ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GENERICCHATROOMWIDGET_H #define GENERICCHATROOMWIDGET_H #include "genericchatitemwidget.h" class CroppingLabel; class MaskablePixmapWidget; class QVBoxLayout; class QHBoxLayout; class ContentLayout; class Friend; class Group; class Contact; class GenericChatroomWidget : public GenericChatItemWidget { Q_OBJECT public: explicit GenericChatroomWidget(bool compact, QWidget* parent = nullptr); public slots: virtual void setAsActiveChatroom() = 0; virtual void setAsInactiveChatroom() = 0; virtual void updateStatusLight() = 0; virtual void resetEventFlags() = 0; virtual QString getStatusString() const = 0; virtual const Contact* getContact() const = 0; virtual const Friend* getFriend() const { return nullptr; } virtual Group* getGroup() const { return nullptr; } virtual bool eventFilter(QObject*, QEvent*) final override; bool isActive(); void setName(const QString& name); void setStatusMsg(const QString& status); QString getStatusMsg() const; QString getTitle() const; void reloadTheme(); void activate(); void compactChange(bool compact); signals: void chatroomWidgetClicked(GenericChatroomWidget* widget); void newWindowOpened(GenericChatroomWidget* widget); void middleMouseClicked(); protected: void mouseReleaseEvent(QMouseEvent* event) override; void enterEvent(QEvent* e) override; void leaveEvent(QEvent* e) override; void setActive(bool active); protected: QPoint dragStartPos; QColor lastColor; QHBoxLayout* mainLayout = nullptr; QVBoxLayout* textLayout = nullptr; MaskablePixmapWidget* avatar; CroppingLabel* statusMessageLabel; bool active; }; #endif // GENERICCHATROOMWIDGET_H qTox/src/widget/groupwidget.cpp000066400000000000000000000150211415623743500171400ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "groupwidget.h" #include #include #include #include #include #include #include #include "maskablepixmapwidget.h" #include "form/groupchatform.h" #include "src/core/core.h" #include "src/model/friend.h" #include "src/friendlist.h" #include "src/model/group.h" #include "src/model/status.h" #include "src/grouplist.h" #include "src/widget/friendwidget.h" #include "src/widget/style.h" #include "src/widget/translator.h" #include "src/widget/widget.h" #include "tool/croppinglabel.h" GroupWidget::GroupWidget(std::shared_ptr chatroom, bool compact) : GenericChatroomWidget(compact) , groupId{chatroom->getGroup()->getPersistentId()} , chatroom{chatroom} { avatar->setPixmap(Style::scaleSvgImage(":img/group.svg", avatar->width(), avatar->height())); statusPic.setPixmap(QPixmap(Status::getIconPath(Status::Status::Online))); statusPic.setMargin(3); Group* g = chatroom->getGroup(); nameLabel->setText(g->getName()); updateUserCount(g->getPeersCount()); setAcceptDrops(true); connect(g, &Group::titleChanged, this, &GroupWidget::updateTitle); connect(g, &Group::numPeersChanged, this, &GroupWidget::updateUserCount); connect(nameLabel, &CroppingLabel::editFinished, g, &Group::setName); Translator::registerHandler(std::bind(&GroupWidget::retranslateUi, this), this); } GroupWidget::~GroupWidget() { Translator::unregister(this); } void GroupWidget::updateTitle(const QString& author, const QString& newName) { Q_UNUSED(author); nameLabel->setText(newName); } void GroupWidget::contextMenuEvent(QContextMenuEvent* event) { if (!active) { setBackgroundRole(QPalette::Highlight); } installEventFilter(this); // Disable leave event. QMenu menu(this); QAction* openChatWindow = nullptr; if (chatroom->possibleToOpenInNewWindow() ) { openChatWindow = menu.addAction(tr("Open chat in new window")); } QAction* removeChatWindow = nullptr; if (chatroom->canBeRemovedFromWindow()) { removeChatWindow = menu.addAction(tr("Remove chat from this window")); } menu.addSeparator(); QAction* setTitle = menu.addAction(tr("Set title...")); QAction* quitGroup = menu.addAction(tr("Quit group", "Menu to quit a groupchat")); QAction* selectedItem = menu.exec(event->globalPos()); removeEventFilter(this); if (!active) { setBackgroundRole(QPalette::Window); } if (!selectedItem) { return; } if (selectedItem == quitGroup) { emit removeGroup(groupId); } else if (selectedItem == openChatWindow) { emit newWindowOpened(this); } else if (selectedItem == removeChatWindow) { chatroom->removeGroupFromDialogs(); } else if (selectedItem == setTitle) { editName(); } } void GroupWidget::mousePressEvent(QMouseEvent* ev) { if (ev->button() == Qt::LeftButton) { dragStartPos = ev->pos(); } GenericChatroomWidget::mousePressEvent(ev); } void GroupWidget::mouseMoveEvent(QMouseEvent* ev) { if (!(ev->buttons() & Qt::LeftButton)) { return; } if ((dragStartPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) { QMimeData* mdata = new QMimeData; const Group* group = getGroup(); mdata->setText(group->getName()); mdata->setData("groupId", group->getPersistentId().getByteArray()); QDrag* drag = new QDrag(this); drag->setMimeData(mdata); drag->setPixmap(avatar->getPixmap()); drag->exec(Qt::CopyAction | Qt::MoveAction); } } void GroupWidget::updateUserCount(int numPeers) { statusMessageLabel->setText(tr("%n user(s) in chat", "Number of users in chat", numPeers)); } void GroupWidget::setAsActiveChatroom() { setActive(true); avatar->setPixmap(Style::scaleSvgImage(":img/group_dark.svg", avatar->width(), avatar->height())); } void GroupWidget::setAsInactiveChatroom() { setActive(false); avatar->setPixmap(Style::scaleSvgImage(":img/group.svg", avatar->width(), avatar->height())); } void GroupWidget::updateStatusLight() { Group* g = chatroom->getGroup(); const bool event = g->getEventFlag(); statusPic.setPixmap(QPixmap(Status::getIconPath(Status::Status::Online, event))); statusPic.setMargin(event ? 1 : 3); } QString GroupWidget::getStatusString() const { if (chatroom->hasNewMessage()) { return tr("New Message"); } else { return tr("Online"); } } void GroupWidget::editName() { nameLabel->editBegin(); } // TODO: Remove Group* GroupWidget::getGroup() const { return chatroom->getGroup(); } const Contact* GroupWidget::getContact() const { return getGroup(); } void GroupWidget::resetEventFlags() { chatroom->resetEventFlags(); } void GroupWidget::dragEnterEvent(QDragEnterEvent* ev) { if (!ev->mimeData()->hasFormat("toxPk")) { return; } const ToxPk pk{ev->mimeData()->data("toxPk")}; if (chatroom->friendExists(pk)) { ev->acceptProposedAction(); } if (!active) { setBackgroundRole(QPalette::Highlight); } } void GroupWidget::dragLeaveEvent(QDragLeaveEvent*) { if (!active) { setBackgroundRole(QPalette::Window); } } void GroupWidget::dropEvent(QDropEvent* ev) { if (!ev->mimeData()->hasFormat("toxPk")) { return; } const ToxPk pk{ev->mimeData()->data("toxPk")}; if (!chatroom->friendExists(pk)) { return; } chatroom->inviteFriend(pk); if (!active) { setBackgroundRole(QPalette::Window); } } void GroupWidget::setName(const QString& name) { nameLabel->setText(name); } void GroupWidget::retranslateUi() { const Group* group = chatroom->getGroup(); updateUserCount(group->getPeersCount()); } qTox/src/widget/groupwidget.h000066400000000000000000000042741415623743500166150ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GROUPWIDGET_H #define GROUPWIDGET_H #include "genericchatroomwidget.h" #include "src/model/chatroom/groupchatroom.h" #include "src/core/groupid.h" #include class GroupWidget final : public GenericChatroomWidget { Q_OBJECT public: GroupWidget(std::shared_ptr chatroom, bool compact); ~GroupWidget(); void setAsInactiveChatroom() final override; void setAsActiveChatroom() final override; void updateStatusLight() final override; void resetEventFlags() final override; QString getStatusString() const final override; Group* getGroup() const final override; const Contact* getContact() const final override; void setName(const QString& name); void editName(); signals: void groupWidgetClicked(GroupWidget* widget); void removeGroup(const GroupId& groupId); protected: void contextMenuEvent(QContextMenuEvent* event) final override; void mousePressEvent(QMouseEvent* event) final override; void mouseMoveEvent(QMouseEvent* event) final override; void dragEnterEvent(QDragEnterEvent* ev) override; void dragLeaveEvent(QDragLeaveEvent* ev) override; void dropEvent(QDropEvent* ev) override; private slots: void retranslateUi(); void updateTitle(const QString& author, const QString& newName); void updateUserCount(int numPeers); public: GroupId groupId; private: std::shared_ptr chatroom; }; #endif // GROUPWIDGET_H qTox/src/widget/gui.cpp000066400000000000000000000223441415623743500153720ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "gui.h" #include "widget.h" #include "src/nexus.h" #include #include #include #include #include #include #include #include #include #include /** * @class GUI * @brief Abstracts the GUI from the target backend (DesktopGUI, ...) * * All the functions exposed here are thread-safe. * Prefer calling this class to calling a GUI backend directly. * * @fn void GUI::resized() * @brief Emitted when the GUI is resized on supported platforms. */ GUI::GUI(QObject* parent) : QObject(parent) { assert(QThread::currentThread() == qApp->thread()); assert(Nexus::getDesktopGUI()); } /** * @brief Returns the singleton instance. */ GUI& GUI::getInstance() { static GUI gui; return gui; } // Implementation of the public clean interface /** * @brief Will enable or disable the GUI. * @note A disabled GUI can't be interacted with by the user. * @param state Enable/disable GUI. */ void GUI::setEnabled(bool state) { if (QThread::currentThread() == qApp->thread()) { getInstance()._setEnabled(state); } else { QMetaObject::invokeMethod(&getInstance(), "_setEnabled", Qt::BlockingQueuedConnection, Q_ARG(bool, state)); } } /** * @brief Change the title of the main window. * @param title Titile to set. * * This is usually always visible to the user. */ void GUI::setWindowTitle(const QString& title) { if (QThread::currentThread() == qApp->thread()) { getInstance()._setWindowTitle(title); } else { QMetaObject::invokeMethod(&getInstance(), "_setWindowTitle", Qt::BlockingQueuedConnection, Q_ARG(const QString&, title)); } } /** * @brief Reloads the application theme and redraw the window. */ void GUI::reloadTheme() { if (QThread::currentThread() == qApp->thread()) { getInstance()._reloadTheme(); } else { QMetaObject::invokeMethod(&getInstance(), "_reloadTheme", Qt::BlockingQueuedConnection); } } /** * @brief Show some text to the user. * @param title Title of information window. * @param msg Text in information window. */ void GUI::showInfo(const QString& title, const QString& msg) { if (QThread::currentThread() == qApp->thread()) { getInstance()._showInfo(title, msg); } else { QMetaObject::invokeMethod(&getInstance(), "_showInfo", Qt::BlockingQueuedConnection, Q_ARG(const QString&, title), Q_ARG(const QString&, msg)); } } /** * @brief Show a warning to the user * @param title Title of warning window. * @param msg Text in warning window. */ void GUI::showWarning(const QString& title, const QString& msg) { if (QThread::currentThread() == qApp->thread()) { getInstance()._showWarning(title, msg); } else { QMetaObject::invokeMethod(&getInstance(), "_showWarning", Qt::BlockingQueuedConnection, Q_ARG(const QString&, title), Q_ARG(const QString&, msg)); } } /** * @brief Show an error to the user. * @param title Title of error window. * @param msg Text in error window. */ void GUI::showError(const QString& title, const QString& msg) { if (QThread::currentThread() == qApp->thread()) { // If the GUI hasn't started yet and we're on the main thread, // we still want to be able to show error messages if (!Nexus::getDesktopGUI()) QMessageBox::critical(nullptr, title, msg); else getInstance()._showError(title, msg); } else { QMetaObject::invokeMethod(&getInstance(), "_showError", Qt::BlockingQueuedConnection, Q_ARG(const QString&, title), Q_ARG(const QString&, msg)); } } /** * @brief Asks the user a question with Ok/Cancel or Yes/No buttons. * @param title Title of question window. * @param msg Text in question window. * @param defaultAns If is true, default was positive answer. Negative otherwise. * @param warning If is true, we will use a special warning style. * @param yesno Show "Yes" and "No" buttons. * @return True if the answer is positive, false otherwise. */ bool GUI::askQuestion(const QString& title, const QString& msg, bool defaultAns, bool warning, bool yesno) { if (QThread::currentThread() == qApp->thread()) { return getInstance()._askQuestion(title, msg, defaultAns, warning, yesno); } else { bool ret; QMetaObject::invokeMethod(&getInstance(), "_askQuestion", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, title), Q_ARG(const QString&, msg), Q_ARG(bool, defaultAns), Q_ARG(bool, warning), Q_ARG(bool, yesno)); return ret; } } /** * @brief Asks the user a question. * * The text for the displayed buttons can be specified. * @param title Title of question window. * @param msg Text in question window. * @param button1 Text of positive button. * @param button2 Text of negative button. * @param defaultAns If is true, default was positive answer. Negative otherwise. * @param warning If is true, we will use a special warning style. * @return True if the answer is positive, false otherwise. */ bool GUI::askQuestion(const QString& title, const QString& msg, const QString& button1, const QString& button2, bool defaultAns, bool warning) { if (QThread::currentThread() == qApp->thread()) { return getInstance()._askQuestion(title, msg, button1, button2, defaultAns, warning); } else { bool ret; QMetaObject::invokeMethod(&getInstance(), "_askQuestion", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, title), Q_ARG(const QString&, msg), Q_ARG(bool, defaultAns), Q_ARG(bool, warning)); return ret; } } // Private implementations void GUI::_setEnabled(bool state) { Widget* w = Nexus::getDesktopGUI(); if (w) w->setEnabled(state); } void GUI::_setWindowTitle(const QString& title) { QWidget* w = getMainWidget(); if (!w) return; if (title.isEmpty()) w->setWindowTitle("qTox"); else w->setWindowTitle("qTox - " + title); } void GUI::_reloadTheme() { Widget* w = Nexus::getDesktopGUI(); if (w) w->reloadTheme(); } void GUI::_showInfo(const QString& title, const QString& msg) { QMessageBox messageBox(QMessageBox::Information, title, msg, QMessageBox::Ok, getMainWidget()); messageBox.setButtonText(QMessageBox::Ok, QApplication::tr("Ok")); messageBox.exec(); } void GUI::_showWarning(const QString& title, const QString& msg) { QMessageBox messageBox(QMessageBox::Warning, title, msg, QMessageBox::Ok, getMainWidget()); messageBox.setButtonText(QMessageBox::Ok, QApplication::tr("Ok")); messageBox.exec(); } void GUI::_showError(const QString& title, const QString& msg) { QMessageBox messageBox(QMessageBox::Critical, title, msg, QMessageBox::Ok, getMainWidget()); messageBox.setButtonText(QMessageBox::Ok, QApplication::tr("Ok")); messageBox.exec(); } bool GUI::_askQuestion(const QString& title, const QString& msg, bool defaultAns, bool warning, bool yesno) { QString positiveButton = yesno ? QApplication::tr("Yes") : QApplication::tr("Ok"); QString negativeButton = yesno ? QApplication::tr("No") : QApplication::tr("Cancel"); return _askQuestion(title, msg, positiveButton, negativeButton, defaultAns, warning); } bool GUI::_askQuestion(const QString& title, const QString& msg, const QString& button1, const QString& button2, bool defaultAns, bool warning) { QMessageBox::Icon icon = warning ? QMessageBox::Warning : QMessageBox::Question; QMessageBox box(icon, title, msg, QMessageBox::NoButton, getMainWidget()); QPushButton* pushButton1 = box.addButton(button1, QMessageBox::AcceptRole); QPushButton* pushButton2 = box.addButton(button2, QMessageBox::RejectRole); box.setDefaultButton(defaultAns ? pushButton1 : pushButton2); box.setEscapeButton(pushButton2); box.exec(); return box.clickedButton() == pushButton1; } // Other /** * @brief Get the main widget. * @return The main QWidget* of the application */ QWidget* GUI::getMainWidget() { QWidget* maingui{nullptr}; maingui = Nexus::getDesktopGUI(); return maingui; } qTox/src/widget/gui.h000066400000000000000000000046061415623743500150400ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef GUI_H #define GUI_H #include class QWidget; class GUI : public QObject { Q_OBJECT public: static GUI& getInstance(); static QWidget* getMainWidget(); static void setEnabled(bool state); static void setWindowTitle(const QString& title); static void reloadTheme(); static void showInfo(const QString& title, const QString& msg); static void showWarning(const QString& title, const QString& msg); static void showError(const QString& title, const QString& msg); static bool askQuestion(const QString& title, const QString& msg, bool defaultAns = false, bool warning = true, bool yesno = true); static bool askQuestion(const QString& title, const QString& msg, const QString& button1, const QString& button2, bool defaultAns = false, bool warning = true); private: explicit GUI(QObject* parent = nullptr); private slots: // Private implementation, those must be called from the GUI thread void _setEnabled(bool state); void _setWindowTitle(const QString& title); void _reloadTheme(); void _showInfo(const QString& title, const QString& msg); void _showWarning(const QString& title, const QString& msg); void _showError(const QString& title, const QString& msg); bool _askQuestion(const QString& title, const QString& msg, bool defaultAns = false, bool warning = true, bool yesno = true); bool _askQuestion(const QString& title, const QString& msg, const QString& button1, const QString& button2, bool defaultAns = false, bool warning = true); }; #endif // GUI_H qTox/src/widget/loginscreen.cpp000066400000000000000000000172171415623743500171210ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "loginscreen.h" #include "ui_loginscreen.h" #include "src/persistence/profile.h" #include "src/persistence/profilelocker.h" #include "src/persistence/settings.h" #include "src/widget/form/setpassworddialog.h" #include "src/widget/style.h" #include "src/widget/tool/profileimporter.h" #include "src/widget/translator.h" #include #include #include #include LoginScreen::LoginScreen(const QString& initialProfileName, QWidget* parent) : QDialog(parent) , ui(new Ui::LoginScreen) , quitShortcut{QKeySequence(Qt::CTRL + Qt::Key_Q), this} { ui->setupUi(this); // permanently disables maximize button https://github.com/qTox/qTox/issues/1973 this->setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint); this->setFixedSize(this->size()); connect(&quitShortcut, &QShortcut::activated, this, &LoginScreen::close); connect(ui->newProfilePgbtn, &QPushButton::clicked, this, &LoginScreen::onNewProfilePageClicked); connect(ui->loginPgbtn, &QPushButton::clicked, this, &LoginScreen::onLoginPageClicked); connect(ui->createAccountButton, &QPushButton::clicked, this, &LoginScreen::onCreateNewProfile); connect(ui->newUsername, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile); connect(ui->newPass, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile); connect(ui->newPassConfirm, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile); connect(ui->loginButton, &QPushButton::clicked, this, &LoginScreen::onLogin); connect(ui->loginUsernames, &QComboBox::currentTextChanged, this, &LoginScreen::onLoginUsernameSelected); connect(ui->loginPassword, &QLineEdit::returnPressed, this, &LoginScreen::onLogin); connect(ui->newPass, &QLineEdit::textChanged, this, &LoginScreen::onPasswordEdited); connect(ui->newPassConfirm, &QLineEdit::textChanged, this, &LoginScreen::onPasswordEdited); connect(ui->autoLoginCB, &QCheckBox::stateChanged, this, &LoginScreen::onAutoLoginCheckboxChanged); connect(ui->importButton, &QPushButton::clicked, this, &LoginScreen::onImportProfile); reset(initialProfileName); this->setStyleSheet(Style::getStylesheet("loginScreen/loginScreen.css")); retranslateUi(); Translator::registerHandler(std::bind(&LoginScreen::retranslateUi, this), this); } LoginScreen::~LoginScreen() { Translator::unregister(this); delete ui; } /** * @brief Resets the UI, clears all fields. */ void LoginScreen::reset(const QString& initialProfileName) { ui->newUsername->clear(); ui->newPass->clear(); ui->newPassConfirm->clear(); ui->loginPassword->clear(); ui->loginUsernames->clear(); QStringList allProfileNames = Profile::getAllProfileNames(); if (allProfileNames.isEmpty()) { ui->stackedWidget->setCurrentIndex(0); ui->newUsername->setFocus(); } else { for (const QString& profileName : allProfileNames) { ui->loginUsernames->addItem(profileName); } ui->loginUsernames->setCurrentText(initialProfileName); ui->stackedWidget->setCurrentIndex(1); ui->loginPassword->setFocus(); } } void LoginScreen::onProfileLoaded() { done(QDialog::Accepted); } void LoginScreen::onProfileLoadFailed() { QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Wrong password.")); ui->loginPassword->setFocus(); ui->loginPassword->selectAll(); } void LoginScreen::onAutoLoginChanged(bool state) { ui->autoLoginCB->setChecked(state); } bool LoginScreen::event(QEvent* event) { switch (event->type()) { #ifdef Q_OS_MAC case QEvent::WindowActivate: case QEvent::WindowStateChange: emit windowStateChanged(windowState()); break; #endif default: break; } return QWidget::event(event); } void LoginScreen::onNewProfilePageClicked() { ui->stackedWidget->setCurrentIndex(0); } void LoginScreen::onLoginPageClicked() { ui->stackedWidget->setCurrentIndex(1); } void LoginScreen::onCreateNewProfile() { QString name = ui->newUsername->text(); QString pass = ui->newPass->text(); if (name.isEmpty()) { QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The username must not be empty.")); return; } if (pass.size() != 0 && pass.size() < 6) { QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The password must be at least 6 characters long.")); return; } if (ui->newPassConfirm->text() != pass) { QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The passwords you've entered are different.\nPlease make sure to " "enter same password twice.")); return; } if (Profile::exists(name)) { QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("A profile with this name already exists.")); return; } emit createNewProfile(name, pass); } void LoginScreen::onLoginUsernameSelected(const QString& name) { if (name.isEmpty()) return; ui->loginPassword->clear(); if (Profile::isEncrypted(name)) { ui->loginPasswordLabel->show(); ui->loginPassword->show(); // there is no way to do autologin if profile is encrypted, and // visible option confuses users into thinking that it is possible, // thus hide it ui->autoLoginCB->hide(); } else { ui->loginPasswordLabel->hide(); ui->loginPassword->hide(); ui->autoLoginCB->show(); ui->autoLoginCB->setToolTip( tr("Password protected profiles can't be automatically loaded.")); } } void LoginScreen::onLogin() { QString name = ui->loginUsernames->currentText(); QString pass = ui->loginPassword->text(); // name can be empty when there are no profiles if (name.isEmpty()) { QMessageBox::critical(this, tr("Couldn't load profile"), tr("There is no selected profile.\n\n" "You may want to create one.")); return; } if (!ProfileLocker::isLockable(name)) { QMessageBox::critical(this, tr("Couldn't load this profile"), tr("This profile is already in use.")); return; } emit loadProfile(name, pass); } void LoginScreen::onPasswordEdited() { ui->passStrengthMeter->setValue(SetPasswordDialog::getPasswordStrength(ui->newPass->text())); } void LoginScreen::onAutoLoginCheckboxChanged(int state) { auto cstate = static_cast(state); emit autoLoginChanged(cstate == Qt::CheckState::Checked); } void LoginScreen::retranslateUi() { ui->retranslateUi(this); } void LoginScreen::onImportProfile() { ProfileImporter pi(this); if (pi.importProfile()) { reset(); } } qTox/src/widget/loginscreen.h000066400000000000000000000041051415623743500165560ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef LOGINSCREEN_H #define LOGINSCREEN_H #include #include #include class Profile; namespace Ui { class LoginScreen; } class LoginScreen : public QDialog { Q_OBJECT public: LoginScreen(const QString& initialProfileName = QString(), QWidget* parent = nullptr); ~LoginScreen(); bool event(QEvent* event) final override; signals: void windowStateChanged(Qt::WindowStates states); void autoLoginChanged(bool state); void createNewProfile(QString name, const QString& pass); void loadProfile(QString name, const QString& pass); public slots: void onProfileLoaded(); void onProfileLoadFailed(); void onAutoLoginChanged(bool state); private slots: void onAutoLoginCheckboxChanged(int state); void onLoginUsernameSelected(const QString& name); void onPasswordEdited(); // Buttons to change page void onNewProfilePageClicked(); void onLoginPageClicked(); // Buttons to submit form void onCreateNewProfile(); void onLogin(); void onImportProfile(); private: void reset(const QString& initialProfileName = QString()); void retranslateUi(); void showCapsIndicator(); void hideCapsIndicator(); void checkCapsLock(); private: Ui::LoginScreen* ui; QShortcut quitShortcut; }; #endif // LOGINSCREEN_H qTox/src/widget/maskablepixmapwidget.cpp000066400000000000000000000057421415623743500210130ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "maskablepixmapwidget.h" #include #include /** * @var QPixmap* MaskablePixmapWidget::renderTarget * @brief pointer to dynamically call the constructor. */ MaskablePixmapWidget::MaskablePixmapWidget(QWidget* parent, QSize size, QString maskName) : QLabel("", parent) , renderTarget(nullptr) , maskName(maskName) , clickable(false) { setSize(size); } MaskablePixmapWidget::~MaskablePixmapWidget() { delete renderTarget; } void MaskablePixmapWidget::setClickable(bool clickable) { this->clickable = clickable; if (clickable) { setCursor(Qt::PointingHandCursor); } else { unsetCursor(); } } void MaskablePixmapWidget::setPixmap(const QPixmap& pmap) { if (pmap.isNull()) { return; } unscaled = pmap; pixmap = pmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); updatePixmap(); update(); } QPixmap MaskablePixmapWidget::getPixmap() const { return *renderTarget; } void MaskablePixmapWidget::setSize(QSize size) { setFixedSize(size); delete renderTarget; renderTarget = new QPixmap(size); QPixmap pmapMask = QPixmap(maskName); if (!pmapMask.isNull()) { mask = pmapMask.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } if (!unscaled.isNull()) { pixmap = unscaled.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); updatePixmap(); update(); } } void MaskablePixmapWidget::mousePressEvent(QMouseEvent*) { if (clickable) { emit clicked(); } } void MaskablePixmapWidget::updatePixmap() { renderTarget->fill(Qt::transparent); QPoint offset((width() - pixmap.size().width()) / 2, (height() - pixmap.size().height()) / 2); // centering the pixmap QPainter painter(renderTarget); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); painter.drawPixmap(offset, pixmap); painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); painter.drawPixmap(0, 0, mask); painter.end(); QLabel::setPixmap(*renderTarget); } qTox/src/widget/maskablepixmapwidget.h000066400000000000000000000027361415623743500204600ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef MASKABLEPIXMAPWIDGET_H #define MASKABLEPIXMAPWIDGET_H #include class MaskablePixmapWidget final : public QLabel { Q_OBJECT public: MaskablePixmapWidget(QWidget* parent, QSize size, QString maskName = QString()); ~MaskablePixmapWidget() override; void autopickBackground(); void setClickable(bool clickable); void setPixmap(const QPixmap& pmap); QPixmap getPixmap() const; void setSize(QSize size); signals: void clicked(); protected: void mousePressEvent(QMouseEvent*) final override; private: void updatePixmap(); private: QPixmap pixmap, mask, unscaled; QPixmap* renderTarget; QString maskName; bool clickable; }; #endif // MASKABLEPIXMAPWIDGET_H qTox/src/widget/notificationedgewidget.cpp000066400000000000000000000037211415623743500213230ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "notificationedgewidget.h" #include "style.h" #include #include #include NotificationEdgeWidget::NotificationEdgeWidget(Position position, QWidget* parent) : QWidget(parent) { setAttribute(Qt::WA_StyledBackground); // Show background. setStyleSheet(Style::getStylesheet("notificationEdge/notificationEdge.css")); QHBoxLayout* layout = new QHBoxLayout(this); layout->addStretch(); textLabel = new QLabel(this); textLabel->setMinimumHeight(textLabel->sizeHint().height()); // Prevent cut-off text. layout->addWidget(textLabel); QLabel* arrowLabel = new QLabel(this); if (position == Top) arrowLabel->setPixmap(QPixmap(Style::getImagePath("chatArea/scrollBarUpArrow.svg"))); else arrowLabel->setPixmap(QPixmap(Style::getImagePath("chatArea/scrollBarDownArrow.svg"))); layout->addWidget(arrowLabel); layout->addStretch(); setCursor(Qt::PointingHandCursor); } void NotificationEdgeWidget::updateNotificationCount(int count) { textLabel->setText(tr("Unread message(s)", "", count)); } void NotificationEdgeWidget::mouseReleaseEvent(QMouseEvent* event) { emit clicked(); QWidget::mousePressEvent(event); } qTox/src/widget/notificationedgewidget.h000066400000000000000000000024511415623743500207670ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef NOTIFICATIONEDGEWIDGET_H #define NOTIFICATIONEDGEWIDGET_H #include class QLabel; class NotificationEdgeWidget final : public QWidget { Q_OBJECT public: enum Position : uint8_t { Top, Bottom }; explicit NotificationEdgeWidget(Position position, QWidget* parent = nullptr); void updateNotificationCount(int count); signals: void clicked(); protected: void mouseReleaseEvent(QMouseEvent* event) final override; private: QLabel* textLabel; }; #endif // NOTIFICATIONEDGEWIDGET_H qTox/src/widget/notificationscrollarea.cpp000066400000000000000000000145541415623743500213500ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "notificationscrollarea.h" #include "genericchatroomwidget.h" #include "notificationedgewidget.h" #include #include NotificationScrollArea::NotificationScrollArea(QWidget* parent) : AdjustingScrollArea(parent) { connect(verticalScrollBar(), &QAbstractSlider::valueChanged, this, &NotificationScrollArea::updateVisualTracking); connect(verticalScrollBar(), &QAbstractSlider::rangeChanged, this, &NotificationScrollArea::updateVisualTracking); } void NotificationScrollArea::trackWidget(GenericChatroomWidget* widget) { if (trackedWidgets.find(widget) != trackedWidgets.end()) return; Visibility visibility = widgetVisible(widget); if (visibility != Visible) { if (visibility == Above) { if (referencesAbove++ == 0) { assert(topEdge == nullptr); topEdge = new NotificationEdgeWidget(NotificationEdgeWidget::Top, this); connect(topEdge, &NotificationEdgeWidget::clicked, this, &NotificationScrollArea::findPreviousWidget); recalculateTopEdge(); topEdge->show(); } topEdge->updateNotificationCount(referencesAbove); } else { if (referencesBelow++ == 0) { assert(bottomEdge == nullptr); bottomEdge = new NotificationEdgeWidget(NotificationEdgeWidget::Bottom, this); connect(bottomEdge, &NotificationEdgeWidget::clicked, this, &NotificationScrollArea::findNextWidget); recalculateBottomEdge(); bottomEdge->show(); } bottomEdge->updateNotificationCount(referencesBelow); } trackedWidgets.insert(widget, visibility); } } /** * @brief Delete notification bar from visible elements on scroll area */ void NotificationScrollArea::updateVisualTracking() { updateTracking(nullptr); } /** * @brief Delete notification bar from visible elements and widget on scroll area * @param widget Chatroom widget to remove from tracked widgets */ void NotificationScrollArea::updateTracking(GenericChatroomWidget* widget) { QHash::iterator i = trackedWidgets.begin(); while (i != trackedWidgets.end()) { if (i.key() == widget || widgetVisible(i.key()) == Visible) { if (i.value() == Above) { if (--referencesAbove == 0) { topEdge->deleteLater(); topEdge = nullptr; } else { topEdge->updateNotificationCount(referencesAbove); } } else { if (--referencesBelow == 0) { bottomEdge->deleteLater(); bottomEdge = nullptr; } else { bottomEdge->updateNotificationCount(referencesBelow); } } i = trackedWidgets.erase(i); continue; } ++i; } } void NotificationScrollArea::resizeEvent(QResizeEvent* event) { if (topEdge != nullptr) recalculateTopEdge(); if (bottomEdge != nullptr) recalculateBottomEdge(); AdjustingScrollArea::resizeEvent(event); } void NotificationScrollArea::findNextWidget() { int value = 0; GenericChatroomWidget* next = nullptr; QHash::iterator i = trackedWidgets.begin(); // Find the first next, to avoid nullptr. for (; i != trackedWidgets.end(); ++i) { if (i.value() == Below) { next = i.key(); value = next->mapTo(viewport(), QPoint()).y(); break; } } // Try finding a closer one. for (; i != trackedWidgets.end(); ++i) { if (i.value() == Below) { int y = i.key()->mapTo(viewport(), QPoint()).y(); if (y < value) { next = i.key(); value = y; } } } if (next != nullptr) ensureWidgetVisible(next, 0, referencesBelow != 1 ? bottomEdge->height() : 0); } void NotificationScrollArea::findPreviousWidget() { int value = 0; GenericChatroomWidget* next = nullptr; QHash::iterator i = trackedWidgets.begin(); // Find the first next, to avoid nullptr. for (; i != trackedWidgets.end(); ++i) { if (i.value() == Above) { next = i.key(); value = next->mapTo(viewport(), QPoint()).y(); break; } } // Try finding a closer one. for (; i != trackedWidgets.end(); ++i) { if (i.value() == Above) { int y = i.key()->mapTo(viewport(), QPoint()).y(); if (y > value) { next = i.key(); value = y; } } } if (next != nullptr) ensureWidgetVisible(next, 0, referencesAbove != 1 ? topEdge->height() : 0); } NotificationScrollArea::Visibility NotificationScrollArea::widgetVisible(QWidget* widget) const { int y = widget->mapTo(viewport(), QPoint()).y(); if (y < 0) return Above; else if (y + widget->height() - 1 > viewport()->height()) return Below; return Visible; } void NotificationScrollArea::recalculateTopEdge() { topEdge->move(viewport()->pos()); topEdge->resize(viewport()->width(), topEdge->height()); } void NotificationScrollArea::recalculateBottomEdge() { QPoint position = viewport()->pos(); position.setY(position.y() + viewport()->height() - bottomEdge->height()); bottomEdge->move(position); bottomEdge->resize(viewport()->width(), bottomEdge->height()); } qTox/src/widget/notificationscrollarea.h000066400000000000000000000035171415623743500210120ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef NOTIFICATIONSCROLLAREA_H #define NOTIFICATIONSCROLLAREA_H #include "tool/adjustingscrollarea.h" #include class GenericChatroomWidget; class NotificationEdgeWidget; class NotificationScrollArea final : public AdjustingScrollArea { public: explicit NotificationScrollArea(QWidget* parent = nullptr); public slots: void trackWidget(GenericChatroomWidget* widget); void updateVisualTracking(); void updateTracking(GenericChatroomWidget* widget); protected: void resizeEvent(QResizeEvent* event) final override; private slots: void findNextWidget(); void findPreviousWidget(); private: enum Visibility : uint8_t { Visible, Above, Below }; Visibility widgetVisible(QWidget* widget) const; void recalculateTopEdge(); void recalculateBottomEdge(); QHash trackedWidgets; NotificationEdgeWidget* topEdge = nullptr; NotificationEdgeWidget* bottomEdge = nullptr; size_t referencesAbove = 0; size_t referencesBelow = 0; }; #endif // NOTIFICATIONSCROLLAREA_H qTox/src/widget/passwordedit.cpp000066400000000000000000000060111415623743500173070ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "passwordedit.h" #ifdef QTOX_PLATFORM_EXT #include "src/platform/capslock.h" #endif #include // It isn't needed for OSX, because it shows indicator by default #if defined(QTOX_PLATFORM_EXT) && !defined(Q_OS_OSX) #define ENABLE_CAPSLOCK_INDICATOR #endif PasswordEdit::EventHandler* PasswordEdit::eventHandler{nullptr}; PasswordEdit::PasswordEdit(QWidget* parent) : QLineEdit(parent) , action(new QAction(this)) { setEchoMode(QLineEdit::Password); #ifdef ENABLE_CAPSLOCK_INDICATOR action->setIcon(QIcon(":img/caps_lock.svg")); action->setToolTip(tr("CAPS-LOCK ENABLED")); addAction(action, QLineEdit::TrailingPosition); #endif } PasswordEdit::~PasswordEdit() { unregisterHandler(); } void PasswordEdit::registerHandler() { #ifdef ENABLE_CAPSLOCK_INDICATOR if (!eventHandler) eventHandler = new EventHandler(); if (!eventHandler->actions.contains(action)) eventHandler->actions.append(action); #endif } void PasswordEdit::unregisterHandler() { #ifdef ENABLE_CAPSLOCK_INDICATOR int idx; if (eventHandler && (idx = eventHandler->actions.indexOf(action)) >= 0) { eventHandler->actions.remove(idx); if (eventHandler->actions.isEmpty()) { delete eventHandler; eventHandler = nullptr; } } #endif } void PasswordEdit::showEvent(QShowEvent*) { #ifdef ENABLE_CAPSLOCK_INDICATOR action->setVisible(Platform::capsLockEnabled()); #endif registerHandler(); } void PasswordEdit::hideEvent(QHideEvent*) { unregisterHandler(); } #ifdef ENABLE_CAPSLOCK_INDICATOR PasswordEdit::EventHandler::EventHandler() { QCoreApplication::instance()->installEventFilter(this); } PasswordEdit::EventHandler::~EventHandler() { QCoreApplication::instance()->removeEventFilter(this); } void PasswordEdit::EventHandler::updateActions() { bool caps = Platform::capsLockEnabled(); for (QAction* action : actions) action->setVisible(caps); } bool PasswordEdit::EventHandler::eventFilter(QObject* obj, QEvent* event) { switch (event->type()) { case QEvent::WindowActivate: case QEvent::KeyRelease: updateActions(); break; default: break; } return QObject::eventFilter(obj, event); } #endif // ENABLE_CAPSLOCK_INDICATOR qTox/src/widget/passwordedit.h000066400000000000000000000027101415623743500167560ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef PASSWORDEDIT_H #define PASSWORDEDIT_H #include #include class PasswordEdit : public QLineEdit { Q_OBJECT public: explicit PasswordEdit(QWidget* parent); ~PasswordEdit(); protected: virtual void showEvent(QShowEvent* event); virtual void hideEvent(QHideEvent* event); private: class EventHandler : QObject { public: QVector actions; EventHandler(); ~EventHandler(); void updateActions(); bool eventFilter(QObject* obj, QEvent* event); }; void registerHandler(); void unregisterHandler(); private: QAction* action; static EventHandler* eventHandler; }; #endif // PASSWORDEDIT_H qTox/src/widget/qrwidget.cpp000066400000000000000000000075051415623743500164360ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "qrwidget.h" #include #include #include #include #include #ifdef Q_OS_WIN32 #include #else #include #endif /** * @file qrwidget.cpp * @link https://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c */ QRWidget::QRWidget(QWidget* parent) : QWidget(parent) , data("0") // Note: The encoding fails with empty string so I just default to something else. // Use the setQRData() call to change this. { // size of the qimage might be problematic in the future, but it works for me size.setWidth(480); size.setHeight(480); image = new QImage(size, QImage::Format_RGB32); } QRWidget::~QRWidget() { delete image; } void QRWidget::setQRData(const QString& data) { this->data = data; paintImage(); } QImage* QRWidget::getImage() { return image; } /** * @brief QRWidget::saveImage * @param path Full path to the file with extension. * @return indicate if saving was successful. */ bool QRWidget::saveImage(QString path) { // 0 - image format same as file extension, 75-quality, png file is ~6.3kb return image->save(path, nullptr, 75); } // http://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c void QRWidget::paintImage() { QPainter painter(image); // NOTE: I have hardcoded some parameters here that would make more sense as variables. // ECLEVEL_M is much faster recognizable by barcodescanner any any other type // https://fukuchi.org/works/qrencode/manual/qrencode_8h.html#a4cebc3c670efe1b8866b14c42737fc8f // any mode other than QR_MODE_8 or QR_MODE_KANJI results in EINVAL. First 1 is version, second // is case sensitivity const std::string dataString = data.toStdString(); QRcode* qr = QRcode_encodeString(dataString.c_str(), 1, QR_ECLEVEL_M, QR_MODE_8, 1); if (qr != nullptr) { QColor fg("black"); QColor bg("white"); painter.setBrush(bg); painter.setPen(Qt::NoPen); painter.drawRect(0, 0, size.width(), size.height()); painter.setBrush(fg); painter.scale(0.96, 0.96); painter.translate(size.width() * 0.02, size.height() * 0.02); const int s = qr->width > 0 ? qr->width : 1; const double w = width(); const double h = height(); const double aspect = w / h; const double scale = ((aspect > 1.0) ? h : w) / s; for (int y = 0; y < s; ++y) { const int yy = y * s; for (int x = 0; x < s; ++x) { const int xx = yy + x; const unsigned char b = qr->data[xx]; if (b & 0x01) { const double rx1 = x * scale, ry1 = y * scale; QRectF r(rx1, ry1, scale, scale); painter.drawRects(&r, 1); } } } QRcode_free(qr); } else { QColor error("red"); painter.setBrush(error); painter.drawRect(0, 0, width(), height()); qDebug() << "QR FAIL: " << strerror(errno); } qr = nullptr; } qTox/src/widget/qrwidget.h000066400000000000000000000022121415623743500160710ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef QRWIDGET_H #define QRWIDGET_H #include class QRWidget : public QWidget { Q_OBJECT public: explicit QRWidget(QWidget* parent = nullptr); ~QRWidget(); void setQRData(const QString& data); QImage* getImage(); bool saveImage(QString path); private: QString data; void paintImage(); QImage* image; QSize size; }; #endif // QRWIDGET_H qTox/src/widget/searchform.cpp000066400000000000000000000226641415623743500167440ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "searchform.h" #include "form/searchsettingsform.h" #include "src/widget/style.h" #include #include #include #include #include #include static std::array STATE_NAME = { QString{}, QStringLiteral("green"), QStringLiteral("red"), }; SearchForm::SearchForm(QWidget* parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(); QHBoxLayout* layoutNavigation = new QHBoxLayout(); QHBoxLayout* layoutMessage = new QHBoxLayout(); QSpacerItem *lSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Ignored); QSpacerItem *rSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Ignored); searchLine = new LineEdit(); settings = new SearchSettingsForm(); messageLabel = new QLabel(); settings->setVisible(false); messageLabel->setProperty("state", QStringLiteral("red")); messageLabel->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/labels.css"))); messageLabel->setText(tr("The text could not be found.")); messageLabel->setVisible(false); settingsButton = createButton("searchSettingsButton", "green"); upButton = createButton("searchUpButton", "green"); downButton = createButton("searchDownButton", "green"); hideButton = createButton("searchHideButton", "red"); startButton = createButton("startButton", "green"); startButton->setText(tr("Start")); layoutNavigation->setMargin(0); layoutNavigation->addWidget(settingsButton); layoutNavigation->addWidget(searchLine); layoutNavigation->addWidget(startButton); layoutNavigation->addWidget(upButton); layoutNavigation->addWidget(downButton); layoutNavigation->addWidget(hideButton); layout->addLayout(layoutNavigation); layout->addWidget(settings); layoutMessage->addSpacerItem(lSpacer); layoutMessage->addWidget(messageLabel); layoutMessage->addSpacerItem(rSpacer); layout->addLayout(layoutMessage); startButton->setHidden(true); setLayout(layout); connect(searchLine, &LineEdit::textChanged, this, &SearchForm::changedSearchPhrase); connect(searchLine, &LineEdit::clickEnter, this, &SearchForm::clickedUp); connect(searchLine, &LineEdit::clickShiftEnter, this, &SearchForm::clickedDown); connect(searchLine, &LineEdit::clickEsc, this, &SearchForm::clickedHide); connect(upButton, &QPushButton::clicked, this, &SearchForm::clickedUp); connect(downButton, &QPushButton::clicked, this, &SearchForm::clickedDown); connect(hideButton, &QPushButton::clicked, this, &SearchForm::clickedHide); connect(startButton, &QPushButton::clicked, this, &SearchForm::clickedStart); connect(settingsButton, &QPushButton::clicked, this, &SearchForm::clickedSearch); connect(settings, &SearchSettingsForm::updateSettings, this, &SearchForm::changedState); } void SearchForm::removeSearchPhrase() { searchLine->setText(""); } QString SearchForm::getSearchPhrase() const { return searchPhrase; } ParameterSearch SearchForm::getParameterSearch() { return parameter; } void SearchForm::setFocusEditor() { searchLine->setFocus(); } void SearchForm::insertEditor(const QString &text) { searchLine->insert(text); } void SearchForm::reloadTheme() { settingsButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); upButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); downButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); hideButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); startButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); settings->reloadTheme(); } void SearchForm::showEvent(QShowEvent* event) { QWidget::showEvent(event); emit visibleChanged(); } QPushButton *SearchForm::createButton(const QString& name, const QString& state) { QPushButton* btn = new QPushButton(); btn->setAttribute(Qt::WA_LayoutUsesWidgetRect); btn->setObjectName(name); btn->setProperty("state", state); btn->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); return btn; } ParameterSearch SearchForm::getAndCheckParametrSearch() { if (isActiveSettings) { auto sendParam = settings->getParameterSearch(); if (!isChangedPhrase && !sendParam.isUpdate) { sendParam.period = PeriodSearch::None; } isChangedPhrase = false; parameter = sendParam; return sendParam; } return ParameterSearch(); } void SearchForm::setStateName(QPushButton *btn, ToolButtonState state) { const auto index = static_cast(state); btn->setProperty("state", STATE_NAME[index]); btn->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); btn->setEnabled(index != 0); } void SearchForm::useBeginState() { setStateName(upButton, ToolButtonState::Common); setStateName(downButton, ToolButtonState::Common); messageLabel->setVisible(false); isPrevSearch = false; } void SearchForm::changedSearchPhrase(const QString& text) { useBeginState(); if (searchPhrase == text) { return; } QString l = text.right(1); if (!l.isEmpty() && l != " " && l[0].isSpace()) { searchLine->setText(searchPhrase); return; } searchPhrase = text; isChangedPhrase = true; if (isActiveSettings) { if (startButton->isHidden()) { changedState(true); } } else { isSearchInBegin = true; emit searchInBegin(searchPhrase, getAndCheckParametrSearch()); } } void SearchForm::clickedUp() { if (downButton->isEnabled()) { isPrevSearch = false; } else { isPrevSearch = true; setStateName(downButton, ToolButtonState::Common); messageLabel->setVisible(false); } if (startButton->isHidden()) { isSearchInBegin = false; emit searchUp(searchPhrase, getAndCheckParametrSearch()); } else { clickedStart(); } } void SearchForm::clickedDown() { if (upButton->isEnabled()) { isPrevSearch = false; } else { isPrevSearch = true; setStateName(upButton, ToolButtonState::Common); messageLabel->setVisible(false); } if (startButton->isHidden()) { isSearchInBegin = false; emit searchDown(searchPhrase, getAndCheckParametrSearch()); } else { clickedStart(); } } void SearchForm::clickedHide() { hide(); emit visibleChanged(); } void SearchForm::clickedStart() { changedState(false); isSearchInBegin = true; emit searchInBegin(searchPhrase, getAndCheckParametrSearch()); } void SearchForm::clickedSearch() { isActiveSettings = !isActiveSettings; settings->setVisible(isActiveSettings); useBeginState(); if (isActiveSettings) { setStateName(settingsButton, ToolButtonState::Active); } else { setStateName(settingsButton, ToolButtonState::Common); changedState(false); } } void SearchForm::changedState(bool isUpdate) { if (isUpdate) { startButton->setHidden(false); upButton->setHidden(true); downButton->setHidden(true); } else { startButton->setHidden(true); upButton->setHidden(false); downButton->setHidden(false); } useBeginState(); } void SearchForm::showMessageNotFound(SearchDirection direction) { if (isSearchInBegin) { if (parameter.period == PeriodSearch::AfterDate) { setStateName(downButton, ToolButtonState::Disabled); } else if (parameter.period == PeriodSearch::BeforeDate) { setStateName(upButton, ToolButtonState::Disabled); } else { setStateName(upButton, ToolButtonState::Disabled); setStateName(downButton, ToolButtonState::Disabled); } } else if (isPrevSearch) { setStateName(upButton, ToolButtonState::Disabled); setStateName(downButton, ToolButtonState::Disabled); } else if (direction == SearchDirection::Up) { setStateName(upButton, ToolButtonState::Disabled); } else { setStateName(downButton, ToolButtonState::Disabled); } messageLabel->setVisible(true); } LineEdit::LineEdit(QWidget* parent) : QLineEdit(parent) { } void LineEdit::keyPressEvent(QKeyEvent* event) { int key = event->key(); if ((key == Qt::Key_Enter || key == Qt::Key_Return)) { if ((event->modifiers() & Qt::ShiftModifier)) { emit clickShiftEnter(); } else { emit clickEnter(); } } else if (key == Qt::Key_Escape) { emit clickEsc(); } QLineEdit::keyPressEvent(event); } qTox/src/widget/searchform.h000066400000000000000000000057601415623743500164070ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SEARCHFORM_H #define SEARCHFORM_H #include #include #include "searchtypes.h" class QPushButton; class QLabel; class LineEdit; class SearchSettingsForm; class SearchForm final : public QWidget { Q_OBJECT public: enum class ToolButtonState { Disabled = 0, // Grey Common = 1, // Green Active = 2, // Red }; explicit SearchForm(QWidget* parent = nullptr); void removeSearchPhrase(); QString getSearchPhrase() const; ParameterSearch getParameterSearch(); void setFocusEditor(); void insertEditor(const QString &text); void reloadTheme(); protected: virtual void showEvent(QShowEvent* event) final override; private: // TODO: Merge with 'createButton' from chatformheader.cpp QPushButton* createButton(const QString& name, const QString& state); ParameterSearch getAndCheckParametrSearch(); void setStateName(QPushButton* btn, ToolButtonState state); void useBeginState(); QPushButton* settingsButton; QPushButton* upButton; QPushButton* downButton; QPushButton* hideButton; QPushButton* startButton; LineEdit* searchLine; SearchSettingsForm* settings; QLabel* messageLabel; QString searchPhrase; ParameterSearch parameter; bool isActiveSettings{false}; bool isChangedPhrase{false}; bool isSearchInBegin{true}; bool isPrevSearch{false}; private slots: void changedSearchPhrase(const QString& text); void clickedUp(); void clickedDown(); void clickedHide(); void clickedStart(); void clickedSearch(); void changedState(bool isUpdate); public slots: void showMessageNotFound(SearchDirection direction); signals: void searchInBegin(const QString& phrase, const ParameterSearch& parameter); void searchUp(const QString& phrase, const ParameterSearch& parameter); void searchDown(const QString& phrase, const ParameterSearch& parameter); void visibleChanged(); }; class LineEdit : public QLineEdit { Q_OBJECT public: LineEdit(QWidget* parent = nullptr); protected: virtual void keyPressEvent(QKeyEvent* event) final override; signals: void clickEnter(); void clickShiftEnter(); void clickEsc(); }; #endif // SEARCHFORM_H qTox/src/widget/searchtypes.h000066400000000000000000000050711415623743500166030ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SEARCHTYPES_H #define SEARCHTYPES_H #include #include enum class FilterSearch { None, Register, WordsOnly, Regular, RegisterAndWordsOnly, RegisterAndRegular }; enum class PeriodSearch { None, WithTheEnd, WithTheFirst, AfterDate, BeforeDate }; enum class SearchDirection { Up, Down }; struct ParameterSearch { FilterSearch filter{FilterSearch::None}; PeriodSearch period{PeriodSearch::None}; QDate date; bool isUpdate{false}; bool operator ==(const ParameterSearch& other) { return filter == other.filter && period == other.period && date == other.date; } bool operator !=(const ParameterSearch& other) { return !(*this == other); } }; class SearchExtraFunctions { public: /** * @brief generateFilterWordsOnly generate string for filter "Whole words only" for correct search phrase * containing symbols "\[]/^$.|?*+(){}" * @param phrase for search * @return new phrase for search */ static QString generateFilterWordsOnly(const QString &phrase) { QString filter = QRegularExpression::escape(phrase); const QString symbols = QStringLiteral("\\[]/^$.|?*+(){}"); if (filter != phrase) { if (filter.left(1) != QLatin1String("\\")) { filter = QLatin1String("\\b") + filter; } else { filter = QLatin1String("(^|\\s)") + filter; } if (!symbols.contains(filter.right(1))) { filter += QLatin1String("\\b"); } else { filter += QLatin1String("($|\\s)"); } } else { filter = QStringLiteral("\\b%1\\b").arg(filter); } return filter; } }; #endif //SEARCHTYPES_H qTox/src/widget/splitterrestorer.cpp000066400000000000000000000044151415623743500202410ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/widget/splitterrestorer.h" #include /** * @class SplitterRestorer * @brief Restore splitter from saved state and reset to default */ /** * @brief The width of the default splitter handles. * By default, this property contains a value that depends on the user's * platform and style preferences. */ static int defaultWidth = 0; /** * @brief Width of left splitter size in percents. */ const static int leftWidthPercent = 33; SplitterRestorer::SplitterRestorer(QSplitter* splitter) : splitter{splitter} { if (defaultWidth == 0) { defaultWidth = QSplitter().handleWidth(); } } /** * @brief Restore splitter from state. And reset in case of error. * Set the splitter to a reasonnable width by default and on first start * @param state State saved by QSplitter::saveState() * @param windowSize Widnow size (used to calculate splitter size) */ void SplitterRestorer::restore(const QByteArray& state, const QSize& windowSize) { bool brokenSplitter = !splitter->restoreState(state) || splitter->orientation() != Qt::Horizontal || splitter->handleWidth() > defaultWidth; if (splitter->count() == 2 && brokenSplitter) { splitter->setOrientation(Qt::Horizontal); splitter->setHandleWidth(defaultWidth); splitter->resize(windowSize); QList sizes = splitter->sizes(); sizes[0] = splitter->width() * leftWidthPercent / 100; sizes[1] = splitter->width() - sizes[0]; splitter->setSizes(sizes); } } qTox/src/widget/splitterrestorer.h000066400000000000000000000021061415623743500177010ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SPLITTERRESTORER_H #define SPLITTERRESTORER_H class QSize; class QSplitter; class QByteArray; class SplitterRestorer { public: explicit SplitterRestorer(QSplitter* splitter); void restore(const QByteArray& state, const QSize& windowSize); private: QSplitter* splitter; }; #endif // SPLITTERRESTORER_H qTox/src/widget/style.cpp000066400000000000000000000333411415623743500157450ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "style.h" #include "src/persistence/settings.h" #include "src/widget/gui.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /** * @enum Style::Font * * @var ExtraBig * @brief [SystemDefault + 2]px, bold * * @var Big * @brief [SystemDefault]px * * @var BigBold * @brief [SystemDefault]px, bold * * @var Medium * @brief [SystemDefault - 1]px * * @var MediumBold * @brief [SystemDefault - 1]px, bold * * @var Small * @brief [SystemDefault - 2]px * * @var SmallLight * @brief [SystemDefault - 2]px, light * * @var BuiltinThemePath * @brief Path to the theme built into the application binary */ namespace { const QLatin1String ThemeSubFolder{"themes/"}; const QLatin1String BuiltinThemeDefaultPath{":themes/default/"}; const QLatin1String BuiltinThemeDarkPath{":themes/dark/"}; } // helper functions QFont appFont(int pixelSize, int weight) { QFont font; font.setPixelSize(pixelSize); font.setWeight(weight); return font; } QString qssifyFont(QFont font) { return QString("%1 %2px \"%3\"").arg(font.weight() * 8).arg(font.pixelSize()).arg(font.family()); } static QMap palette; static QMap dictColor; static QMap dictFont; static QMap dictTheme; QList Style::themeNameColors = { {Style::Light, QObject::tr("Default"), QColor()}, {Style::Light, QObject::tr("Blue"), QColor("#004aa4")}, {Style::Light, QObject::tr("Olive"), QColor("#97ba00")}, {Style::Light, QObject::tr("Red"), QColor("#c23716")}, {Style::Light, QObject::tr("Violet"), QColor("#4617b5")}, {Style::Dark, QObject::tr("Dark"), QColor()}, {Style::Dark, QObject::tr("Dark blue"), QColor("#00336d")}, {Style::Dark, QObject::tr("Dark olive"), QColor("#4d5f00")}, {Style::Dark, QObject::tr("Dark red"), QColor("#7a210d")}, {Style::Dark, QObject::tr("Dark violet"), QColor("#280d6c")} }; QStringList Style::getThemeColorNames() { QStringList l; for (auto t : themeNameColors) { l << t.name; } return l; } QString Style::getThemeName() { //TODO: return name of the current theme const QString themeName = "default"; return QStringLiteral("default"); } QString Style::getThemeFolder() { const QString themeName = getThemeName(); const QString themeFolder = ThemeSubFolder % themeName; const QString fullPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, themeFolder, QStandardPaths::LocateDirectory); // No themes available, fallback to builtin if(fullPath.isEmpty()) { return getThemePath(); } return fullPath % QDir::separator(); } QMap Style::aliasColors = {{TransferGood, "transferGood"}, {TransferWait, "transferWait"}, {TransferBad, "transferBad"}, {TransferMiddle, "transferMiddle"}, {MainText,"mainText"}, {NameActive, "nameActive"}, {StatusActive,"statusActive"}, {GroundExtra, "groundExtra"}, {GroundBase, "groundBase"}, {Orange, "orange"}, {ThemeDark, "themeDark"}, {ThemeMediumDark, "themeMediumDark"}, {ThemeMedium, "themeMedium"}, {ThemeLight, "themeLight"}, {Action, "action"}, {Link, "link"}, {SearchHighlighted, "searchHighlighted"}, {SelectText, "selectText"}}; // stylesheet filename, font -> stylesheet // QString implicit sharing deduplicates stylesheets rather than constructing a new one each time std::map, const QString> Style::stylesheetsCache; const QString Style::getStylesheet(const QString& filename, const QFont& baseFont) { const QString fullPath = getThemeFolder() + filename; const std::pair cacheKey(fullPath, baseFont); auto it = stylesheetsCache.find(cacheKey); if (it != stylesheetsCache.end()) { // cache hit return it->second; } // cache miss, new styleSheet, read it from file and add to cache const QString newStylesheet = resolve(filename, baseFont); stylesheetsCache.insert(std::make_pair(cacheKey, newStylesheet)); return newStylesheet; } static QStringList existingImagesCache; const QString Style::getImagePath(const QString& filename) { QString fullPath = getThemeFolder() + filename; // search for image in cache if (existingImagesCache.contains(fullPath)) { return fullPath; } // if not in cache if (QFileInfo::exists(fullPath)) { existingImagesCache << fullPath; return fullPath; } else { qWarning() << "Failed to open file (using defaults):" << fullPath; fullPath = getThemePath() % filename; if (QFileInfo::exists(fullPath)) { return fullPath; } else { qWarning() << "Failed to open default file:" << fullPath; return {}; } } } QColor Style::getColor(Style::ColorPalette entry) { return palette[entry]; } QFont Style::getFont(Style::Font font) { // fonts as defined in // https://github.com/ItsDuke/Tox-UI/blob/master/UI%20GUIDELINES.md static int defSize = QFontInfo(QFont()).pixelSize(); static QFont fonts[] = { appFont(defSize + 3, QFont::Bold), // extra big appFont(defSize + 1, QFont::Normal), // big appFont(defSize + 1, QFont::Bold), // big bold appFont(defSize, QFont::Normal), // medium appFont(defSize, QFont::Bold), // medium bold appFont(defSize - 1, QFont::Normal), // small appFont(defSize - 1, QFont::Light), // small light }; return fonts[font]; } const QString Style::resolve(const QString& filename, const QFont& baseFont) { QString themePath = getThemeFolder(); QString fullPath = themePath + filename; QString qss; QFile file{fullPath}; if (file.open(QFile::ReadOnly | QFile::Text)) { qss = file.readAll(); } else { qWarning() << "Failed to open file (using defaults):" << fullPath; fullPath = getThemePath(); QFile file{fullPath}; if (file.open(QFile::ReadOnly | QFile::Text)) { qss = file.readAll(); } else { qWarning() << "Failed to open default file:" << fullPath; return {}; } } if (palette.isEmpty()) { initPalette(); } if (dictColor.isEmpty()) { initDictColor(); } if (dictFont.isEmpty()) { dictFont = { {"@baseFont", QString::fromUtf8("'%1' %2px").arg(baseFont.family()).arg(QFontInfo(baseFont).pixelSize())}, {"@extraBig", qssifyFont(Style::getFont(Style::ExtraBig))}, {"@big", qssifyFont(Style::getFont(Style::Big))}, {"@bigBold", qssifyFont(Style::getFont(Style::BigBold))}, {"@medium", qssifyFont(Style::getFont(Style::Medium))}, {"@mediumBold", qssifyFont(Style::getFont(Style::MediumBold))}, {"@small", qssifyFont(Style::getFont(Style::Small))}, {"@smallLight", qssifyFont(Style::getFont(Style::SmallLight))}}; } for (const QString& key : dictColor.keys()) { qss.replace(QRegularExpression(key % QLatin1String{"\\b"}), dictColor[key]); } for (const QString& key : dictFont.keys()) { qss.replace(QRegularExpression(key % QLatin1String{"\\b"}), dictFont[key]); } for (const QString& key : dictTheme.keys()) { qss.replace(QRegularExpression(key % QLatin1String{"\\b"}), dictTheme[key]); } // @getImagePath() function const QRegularExpression re{QStringLiteral(R"(@getImagePath\([^)\s]*\))")}; QRegularExpressionMatchIterator i = re.globalMatch(qss); while (i.hasNext()) { QRegularExpressionMatch match = i.next(); QString path = match.captured(0); const QString phrase = path; path.remove(QStringLiteral("@getImagePath(")); path.chop(1); QString fullImagePath = getThemeFolder() + path; // image not in cache if (!existingImagesCache.contains(fullPath)) { if (QFileInfo::exists(fullImagePath)) { existingImagesCache << fullImagePath; } else { qWarning() << "Failed to open file (using defaults):" << fullImagePath; fullImagePath = getThemePath() % path; } } qss.replace(phrase, fullImagePath); } return qss; } void Style::repolish(QWidget* w) { w->style()->unpolish(w); w->style()->polish(w); for (QObject* o : w->children()) { QWidget* c = qobject_cast(o); if (c) { c->style()->unpolish(c); c->style()->polish(c); } } } void Style::setThemeColor(int color) { stylesheetsCache.clear(); // clear stylesheet cache which includes color info palette.clear(); dictColor.clear(); initPalette(); initDictColor(); if (color < 0 || color >= themeNameColors.size()) setThemeColor(QColor()); else setThemeColor(themeNameColors[color].color); } /** * @brief Set theme color. * @param color Color to set. * * Pass an invalid QColor to reset to defaults. */ void Style::setThemeColor(const QColor& color) { if (!color.isValid()) { // Reset to default palette[ThemeDark] = getColor(ThemeDark); palette[ThemeMediumDark] = getColor(ThemeMediumDark); palette[ThemeMedium] = getColor(ThemeMedium); palette[ThemeLight] = getColor(ThemeLight); } else { palette[ThemeDark] = color.darker(155); palette[ThemeMediumDark] = color.darker(135); palette[ThemeMedium] = color.darker(120); palette[ThemeLight] = color.lighter(110); } dictTheme["@themeDark"] = getColor(ThemeDark).name(); dictTheme["@themeMediumDark"] = getColor(ThemeMediumDark).name(); dictTheme["@themeMedium"] = getColor(ThemeMedium).name(); dictTheme["@themeLight"] = getColor(ThemeLight).name(); } /** * @brief Reloads some CCS */ void Style::applyTheme() { GUI::reloadTheme(); } QPixmap Style::scaleSvgImage(const QString& path, uint32_t width, uint32_t height) { QSvgRenderer render(path); QPixmap pixmap(width, height); pixmap.fill(QColor(0, 0, 0, 0)); QPainter painter(&pixmap); render.render(&painter, pixmap.rect()); return pixmap; } void Style::initPalette() { QSettings settings(getThemePath() % "palette.ini", QSettings::IniFormat); auto keys = aliasColors.keys(); settings.beginGroup("colors"); QMap c; for (auto k : keys) { c[k] = settings.value(aliasColors[k], "#000").toString(); palette[k] = QColor(settings.value(aliasColors[k], "#000").toString()); } auto p = palette; settings.endGroup(); } void Style::initDictColor() { dictColor = { {"@transferGood", Style::getColor(Style::TransferGood).name()}, {"@transferWait", Style::getColor(Style::TransferWait).name()}, {"@transferBad", Style::getColor(Style::TransferBad).name()}, {"@transferMiddle", Style::getColor(Style::TransferMiddle).name()}, {"@mainText", Style::getColor(Style::MainText).name()}, {"@nameActive", Style::getColor(Style::NameActive).name()}, {"@statusActive", Style::getColor(Style::StatusActive).name()}, {"@groundExtra", Style::getColor(Style::GroundExtra).name()}, {"@groundBase", Style::getColor(Style::GroundBase).name()}, {"@orange", Style::getColor(Style::Orange).name()}, {"@action", Style::getColor(Style::Action).name()}, {"@link", Style::getColor(Style::Link).name()}, {"@searchHighlighted", Style::getColor(Style::SearchHighlighted).name()}, {"@selectText", Style::getColor(Style::SelectText).name()}}; } QString Style::getThemePath() { const int num = Settings::getInstance().getThemeColor(); if (themeNameColors[num].type == Dark) { return BuiltinThemeDarkPath; } return BuiltinThemeDefaultPath; } qTox/src/widget/style.h000066400000000000000000000052061415623743500154110ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef STYLE_H #define STYLE_H #include #include class QString; class QWidget; class Style { public: enum ColorPalette { TransferGood, TransferWait, TransferBad, TransferMiddle, MainText, NameActive, StatusActive, GroundExtra, GroundBase, Orange, ThemeDark, ThemeMediumDark, ThemeMedium, ThemeLight, Action, Link, SearchHighlighted, SelectText }; enum Font { ExtraBig, Big, BigBold, Medium, MediumBold, Small, SmallLight }; enum MainTheme { Light, Dark }; struct ThemeNameColor { MainTheme type; QString name; QColor color; }; static QStringList getThemeColorNames(); static const QString getStylesheet(const QString& filename, const QFont& baseFont = QFont()); static const QString getImagePath(const QString& filename); static QString getThemeFolder(); static QString getThemeName(); static QColor getColor(ColorPalette entry); static QFont getFont(Font font); static const QString resolve(const QString& filename, const QFont& baseFont = QFont()); static void repolish(QWidget* w); static void setThemeColor(int color); static void setThemeColor(const QColor& color); static void applyTheme(); static QPixmap scaleSvgImage(const QString& path, uint32_t width, uint32_t height); static void initPalette(); static void initDictColor(); static QString getThemePath(); signals: void themeChanged(); private: Style(); private: static QList themeNameColors; static std::map, const QString> stylesheetsCache; static QMap aliasColors; }; #endif // STYLE_H qTox/src/widget/tool/000077500000000000000000000000001415623743500150525ustar00rootroot00000000000000qTox/src/widget/tool/activatedialog.cpp000066400000000000000000000021621415623743500205370ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "activatedialog.h" #include ActivateDialog::ActivateDialog(QWidget* parent, Qt::WindowFlags f) : QDialog(parent, f) { } bool ActivateDialog::event(QEvent* event) { if (event->type() == QEvent::WindowActivate || event->type() == QEvent::WindowStateChange) emit windowStateChanged(windowState()); return QDialog::event(event); } qTox/src/widget/tool/activatedialog.h000066400000000000000000000023511415623743500202040ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ACTIVATEDIALOG_H #define ACTIVATEDIALOG_H #include class ActivateDialog : public QDialog { Q_OBJECT public: #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) ActivateDialog(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); #else ActivateDialog(QWidget* parent = nullptr, Qt::WindowFlags f = nullptr); #endif bool event(QEvent* event) override; signals: void windowStateChanged(Qt::WindowStates state); }; #endif // ACTIVATEDIALOG_H qTox/src/widget/tool/adjustingscrollarea.cpp000066400000000000000000000030471415623743500216220ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "adjustingscrollarea.h" #include #include #include #include AdjustingScrollArea::AdjustingScrollArea(QWidget* parent) : QScrollArea(parent) { } void AdjustingScrollArea::resizeEvent(QResizeEvent* ev) { int scrollBarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->sizeHint().width() : 0; if (layoutDirection() == Qt::RightToLeft) setViewportMargins(-scrollBarWidth, 0, 0, 0); updateGeometry(); QScrollArea::resizeEvent(ev); } QSize AdjustingScrollArea::sizeHint() const { if (widget()) { int scrollbarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0; return widget()->sizeHint() + QSize(scrollbarWidth, 0); } return QScrollArea::sizeHint(); } qTox/src/widget/tool/adjustingscrollarea.h000066400000000000000000000022431415623743500212640ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef ADJUSTINGSCROLLAREA_H #define ADJUSTINGSCROLLAREA_H #include class AdjustingScrollArea : public QScrollArea { Q_OBJECT public: explicit AdjustingScrollArea(QWidget* parent = nullptr); virtual ~AdjustingScrollArea() = default; protected: virtual void resizeEvent(QResizeEvent* ev) override; virtual QSize sizeHint() const final override; }; #endif // ADJUSTINGSCROLLAREA_H qTox/src/widget/tool/callconfirmwidget.cpp000066400000000000000000000135671415623743500212670ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "callconfirmwidget.h" #include "src/widget/style.h" #include "src/widget/widget.h" #include #include #include #include #include #include #include #include #include #include #include /** * @class CallConfirmWidget * @brief This is a widget with dialog buttons to accept/reject a call * * It tracks the position of another widget called the anchor * and looks like a bubble at the bottom of that widget. * * @var const QWidget* CallConfirmWidget::anchor * @brief The widget we're going to be tracking * * @var const int CallConfirmWidget::roundedFactor * @brief By how much are the corners of the main rect rounded * * @var const qreal CallConfirmWidget::rectRatio * @brief Used to correct the rounding factors on non-square rects */ CallConfirmWidget::CallConfirmWidget(const QWidget* anchor) : QWidget() , anchor(anchor) , rectW{120} , rectH{85} , spikeW{30} , spikeH{15} , roundedFactor{20} , rectRatio(static_cast(rectH) / static_cast(rectW)) { setWindowFlags(Qt::SubWindow); setAttribute(Qt::WA_DeleteOnClose); QPalette palette; palette.setColor(QPalette::WindowText, Qt::white); setPalette(palette); QVBoxLayout* layout = new QVBoxLayout(this); QLabel* callLabel = new QLabel(QObject::tr("Incoming call..."), this); callLabel->setStyleSheet("QLabel{color: white;} QToolTip{color: black;}"); callLabel->setAlignment(Qt::AlignHCenter); callLabel->setToolTip(callLabel->text()); // Note: At the moment this may not work properly. For languages written // from right to left, there is no translation for the phrase "Incoming call...". // In this situation, the phrase "Incoming call..." looks as "...oming call..." Qt::TextElideMode elideMode = (QGuiApplication::layoutDirection() == Qt::LeftToRight) ? Qt::ElideRight : Qt::ElideLeft; int marginSize = 12; QFontMetrics fontMetrics(callLabel->font()); QString elidedText = fontMetrics.elidedText(callLabel->text(), elideMode, rectW - marginSize * 2 - 4); callLabel->setText(elidedText); QDialogButtonBox* buttonBox = new QDialogButtonBox(Qt::Horizontal, this); QPushButton *accept = new QPushButton(this), *reject = new QPushButton(this); accept->setFlat(true); reject->setFlat(true); accept->setStyleSheet("QPushButton{border:none;}"); reject->setStyleSheet("QPushButton{border:none;}"); accept->setIcon(QIcon(Style::getImagePath("acceptCall/acceptCall.svg"))); reject->setIcon(QIcon(Style::getImagePath("rejectCall/rejectCall.svg"))); accept->setIconSize(accept->size()); reject->setIconSize(reject->size()); buttonBox->addButton(accept, QDialogButtonBox::AcceptRole); buttonBox->addButton(reject, QDialogButtonBox::RejectRole); connect(buttonBox, &QDialogButtonBox::accepted, this, &CallConfirmWidget::accepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &CallConfirmWidget::rejected); layout->setMargin(marginSize); layout->addSpacing(spikeH); layout->addWidget(callLabel); layout->addWidget(buttonBox); setFixedSize(rectW, rectH + spikeH); reposition(); } /** * @brief Recalculate our positions to track the anchor */ void CallConfirmWidget::reposition() { if (parentWidget()) parentWidget()->removeEventFilter(this); setParent(anchor->window()); parentWidget()->installEventFilter(this); QWidget* w = anchor->window(); QPoint pos = anchor->mapToGlobal({(anchor->width() - rectW) / 2, anchor->height()}) - w->mapToGlobal({0, 0}); // We don't want the widget to overflow past the right of the screen int xOverflow = 0; if (pos.x() + rectW > w->width()) xOverflow = pos.x() + rectW - w->width(); pos.rx() -= xOverflow; mainRect = {0, spikeH, rectW, rectH}; brush = QBrush(QColor(65, 65, 65)); spikePoly = QPolygon({{(rectW - spikeW) / 2 + xOverflow, spikeH}, {rectW / 2 + xOverflow, 0}, {(rectW + spikeW) / 2 + xOverflow, spikeH}}); move(pos); update(); } void CallConfirmWidget::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(brush); painter.setPen(Qt::NoPen); painter.drawRoundedRect(mainRect, roundedFactor * rectRatio, roundedFactor, Qt::RelativeSize); painter.drawPolygon(spikePoly); } void CallConfirmWidget::showEvent(QShowEvent*) { // Kriby: Legacy comment, is this still true? // If someone does show() from Widget or lower, the event will reach us // because it's our parent, and we could show up in the wrong form. // So check here if our friend's form is actually the active one. reposition(); update(); } void CallConfirmWidget::hideEvent(QHideEvent*) { if (parentWidget()) parentWidget()->removeEventFilter(this); setParent(nullptr); } bool CallConfirmWidget::eventFilter(QObject*, QEvent* event) { if (event->type() == QEvent::Resize) reposition(); return false; } qTox/src/widget/tool/callconfirmwidget.h000066400000000000000000000031061415623743500207200ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CALLCONFIRMWIDGET_H #define CALLCONFIRMWIDGET_H #include #include #include #include class QPaintEvent; class QShowEvent; class CallConfirmWidget final : public QWidget { Q_OBJECT public: explicit CallConfirmWidget(const QWidget* anchor); signals: void accepted(); void rejected(); public slots: void reposition(); protected: void paintEvent(QPaintEvent* event) final; void showEvent(QShowEvent* event) final; void hideEvent(QHideEvent* event) final; bool eventFilter(QObject*, QEvent* event) final; private: const QWidget* anchor; QRect mainRect; QPolygon spikePoly; QBrush brush; const int rectW, rectH; const int spikeW, spikeH; const int roundedFactor; const qreal rectRatio; }; #endif // CALLCONFIRMWIDGET_H qTox/src/widget/tool/chattextedit.cpp000066400000000000000000000053301415623743500202510ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "chattextedit.h" #include "src/widget/translator.h" #include #include #include #include ChatTextEdit::ChatTextEdit(QWidget* parent) : QTextEdit(parent) { retranslateUi(); setAcceptRichText(false); setAcceptDrops(false); Translator::registerHandler(std::bind(&ChatTextEdit::retranslateUi, this), this); } ChatTextEdit::~ChatTextEdit() { Translator::unregister(this); } void ChatTextEdit::keyPressEvent(QKeyEvent* event) { int key = event->key(); if ((key == Qt::Key_Enter || key == Qt::Key_Return) && !(event->modifiers() & Qt::ShiftModifier)) { emit enterPressed(); return; } if (key == Qt::Key_Tab) { if (event->modifiers()) event->ignore(); else { emit tabPressed(); event->ignore(); } return; } if (key == Qt::Key_Up && this->toPlainText().isEmpty()) { this->setPlainText(lastMessage); this->setFocus(); this->moveCursor(QTextCursor::MoveOperation::End, QTextCursor::MoveMode::MoveAnchor); return; } if (event->matches(QKeySequence::Paste) && pasteIfImage(event)) { return; } emit keyPressed(); QTextEdit::keyPressEvent(event); } void ChatTextEdit::setLastMessage(QString lm) { lastMessage = lm; } void ChatTextEdit::retranslateUi() { setPlaceholderText(tr("Type your message here...")); } void ChatTextEdit::sendKeyEvent(QKeyEvent* event) { emit keyPressEvent(event); } bool ChatTextEdit::pasteIfImage(QKeyEvent* event) { const QClipboard* const clipboard = QApplication::clipboard(); if (!clipboard) { return false; } const QMimeData* const mimeData = clipboard->mimeData(); if (!mimeData || !mimeData->hasImage()) { return false; } const QPixmap pixmap(clipboard->pixmap()); if (pixmap.isNull()) { return false; } emit pasteImage(pixmap); return true; } qTox/src/widget/tool/chattextedit.h000066400000000000000000000025711415623743500177220ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CHATTEXTEDIT_H #define CHATTEXTEDIT_H #include class ChatTextEdit final : public QTextEdit { Q_OBJECT public: explicit ChatTextEdit(QWidget* parent = nullptr); ~ChatTextEdit(); void setLastMessage(QString lm); void sendKeyEvent(QKeyEvent* event); signals: void enterPressed(); void tabPressed(); void keyPressed(); void pasteImage(const QPixmap& pixmap); protected: virtual void keyPressEvent(QKeyEvent* event) final override; private: void retranslateUi(); bool pasteIfImage(QKeyEvent* event); private: QString lastMessage; }; #endif // CHATTEXTEDIT_H qTox/src/widget/tool/croppinglabel.cpp000066400000000000000000000113221415623743500203760ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "croppinglabel.h" #include #include #include #include CroppingLabel::CroppingLabel(QWidget* parent) : QLabel(parent) , blockPaintEvents(false) , editable(false) , elideMode(Qt::ElideRight) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); class LineEdit : public QLineEdit { public: explicit LineEdit(QWidget* parent = nullptr) : QLineEdit(parent) {} protected: void keyPressEvent(QKeyEvent* event) override { if (event->key() == Qt::Key_Escape) { undo(); clearFocus(); } QLineEdit::keyPressEvent(event); } }; textEdit = new LineEdit(this); textEdit->hide(); textEdit->setInputMethodHints(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhPreferLatin); connect(textEdit, &QLineEdit::editingFinished, this, &CroppingLabel::editingFinished); } void CroppingLabel::editBegin() { showTextEdit(); textEdit->selectAll(); } void CroppingLabel::setEditable(bool editable) { this->editable = editable; if (editable) setCursor(Qt::PointingHandCursor); else unsetCursor(); } void CroppingLabel::setElideMode(Qt::TextElideMode elide) { elideMode = elide; } void CroppingLabel::setText(const QString& text) { origText = text.trimmed(); setElidedText(); } void CroppingLabel::setPlaceholderText(const QString& text) { textEdit->setPlaceholderText(text); setElidedText(); } void CroppingLabel::resizeEvent(QResizeEvent* ev) { setElidedText(); textEdit->resize(ev->size()); QLabel::resizeEvent(ev); } QSize CroppingLabel::sizeHint() const { return QSize(0, QLabel::sizeHint().height()); } QSize CroppingLabel::minimumSizeHint() const { #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) return QSize(fontMetrics().horizontalAdvance("..."), QLabel::minimumSizeHint().height()); #else return QSize(fontMetrics().width("..."), QLabel::minimumSizeHint().height()); #endif } void CroppingLabel::mouseReleaseEvent(QMouseEvent* e) { if (editable) showTextEdit(); emit clicked(); QLabel::mouseReleaseEvent(e); } void CroppingLabel::paintEvent(QPaintEvent* paintEvent) { if (blockPaintEvents) { paintEvent->ignore(); return; } QLabel::paintEvent(paintEvent); } void CroppingLabel::setElidedText() { QString elidedText = fontMetrics().elidedText(origText, elideMode, width()); if (elidedText != origText) setToolTip(Qt::convertFromPlainText(origText, Qt::WhiteSpaceNormal)); else setToolTip(QString()); if (!elidedText.isEmpty()) { QLabel::setText(elidedText); } else { // NOTE: it would be nice if the label had custom styling when it was default QLabel::setText(textEdit->placeholderText()); } } void CroppingLabel::hideTextEdit() { textEdit->hide(); blockPaintEvents = false; } void CroppingLabel::showTextEdit() { blockPaintEvents = true; textEdit->show(); textEdit->setFocus(); textEdit->setText(origText); textEdit->setFocusPolicy(Qt::ClickFocus); } /** * @brief Get original full text. * @return The un-cropped text. */ QString CroppingLabel::fullText() { return origText; } void CroppingLabel::minimizeMaximumWidth() { // This function chooses the smallest possible maximum width. // Text width + padding. Without padding, we'll have elipses. #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) setMaximumWidth(fontMetrics().horizontalAdvance(origText) + fontMetrics().horizontalAdvance("...")); #else setMaximumWidth(fontMetrics().width(origText) + fontMetrics().width("...")); #endif } void CroppingLabel::editingFinished() { hideTextEdit(); QString newText = textEdit->text().trimmed().remove(QRegExp("[\\t\\n\\v\\f\\r\\x0000]")); if (origText != newText) emit editFinished(textEdit->text()); emit editRemoved(); } qTox/src/widget/tool/croppinglabel.h000066400000000000000000000036321415623743500200500ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef CROPPINGLABEL_H #define CROPPINGLABEL_H #include class QLineEdit; class CroppingLabel : public QLabel { Q_OBJECT public: explicit CroppingLabel(QWidget* parent = nullptr); public slots: void editBegin(); void setEditable(bool editable); void setElideMode(Qt::TextElideMode elide); QString fullText(); public slots: void setText(const QString& text); void setPlaceholderText(const QString& text); void minimizeMaximumWidth(); signals: void editFinished(const QString& newText); void editRemoved(); void clicked(); protected: void paintEvent(QPaintEvent* paintEvent) override; void setElidedText(); void hideTextEdit(); void showTextEdit(); virtual void resizeEvent(QResizeEvent* ev) final override; virtual QSize sizeHint() const final override; virtual QSize minimumSizeHint() const final override; virtual void mouseReleaseEvent(QMouseEvent* e) final override; private slots: void editingFinished(); private: QString origText; QLineEdit* textEdit; bool blockPaintEvents; bool editable; Qt::TextElideMode elideMode; }; #endif // CROPPINGLABEL_H qTox/src/widget/tool/flyoutoverlaywidget.cpp000066400000000000000000000064041415623743500217120ustar00rootroot00000000000000/* Copyright (C) 2013 by Maxim Biro Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "flyoutoverlaywidget.h" #include #include #include #include #include FlyoutOverlayWidget::FlyoutOverlayWidget(QWidget* parent) : QWidget(parent) { setContentsMargins(0, 0, 0, 0); animation = new QPropertyAnimation(this, QByteArrayLiteral("flyoutPercent"), this); animation->setKeyValueAt(0, 0.0f); animation->setKeyValueAt(1, 1.0f); animation->setDuration(200); connect(animation, &QAbstractAnimation::finished, this, &FlyoutOverlayWidget::finishedAnimation); setFlyoutPercent(0); hide(); } FlyoutOverlayWidget::~FlyoutOverlayWidget() { } int FlyoutOverlayWidget::animationDuration() const { return animation->duration(); } void FlyoutOverlayWidget::setAnimationDuration(int timeMs) { animation->setDuration(timeMs); } qreal FlyoutOverlayWidget::flyoutPercent() const { return percent; } void FlyoutOverlayWidget::setFlyoutPercent(qreal progress) { percent = progress; QSize self = size(); setMask(QRegion(0, 0, self.width() * progress + 1, self.height())); move(startPos.x() + self.width() - self.width() * percent, startPos.y()); setVisible(progress != 0); } bool FlyoutOverlayWidget::isShown() const { return (percent == 1); } bool FlyoutOverlayWidget::isBeingAnimated() const { return (animation->state() == QAbstractAnimation::Running); } bool FlyoutOverlayWidget::isBeingShown() const { return (isBeingAnimated() && animation->direction() == QAbstractAnimation::Forward); } void FlyoutOverlayWidget::animateShow() { if (percent == 1.0f) return; if (animation->state() != QAbstractAnimation::Running) this->startPos = pos(); startAnimation(true); } void FlyoutOverlayWidget::animateHide() { if (animation->state() != QAbstractAnimation::Running) this->startPos = pos(); startAnimation(false); } void FlyoutOverlayWidget::finishedAnimation() { bool hide = (animation->direction() == QAbstractAnimation::Backward); // Delay it by a few frames to let the system catch up on rendering if (hide) QTimer::singleShot(50, this, SIGNAL(hidden())); } void FlyoutOverlayWidget::startAnimation(bool forward) { setAttribute(Qt::WA_TransparentForMouseEvents, !forward); animation->setDirection(forward ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); animation->start(); animation->setCurrentTime(animation->duration() * percent); } qTox/src/widget/tool/flyoutoverlaywidget.h000066400000000000000000000033061415623743500213550ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FLYOUTOVERLAYWIDGET_HPP #define FLYOUTOVERLAYWIDGET_HPP #include class QPropertyAnimation; class FlyoutOverlayWidget : public QWidget { Q_OBJECT Q_PROPERTY(qreal flyoutPercent READ flyoutPercent WRITE setFlyoutPercent) public: explicit FlyoutOverlayWidget(QWidget* parent = nullptr); ~FlyoutOverlayWidget(); int animationDuration() const; void setAnimationDuration(int timeMs); qreal flyoutPercent() const; void setFlyoutPercent(qreal progress); bool isShown() const; bool isBeingAnimated() const; bool isBeingShown() const; void animateShow(); void animateHide(); signals: void hidden(); private: void finishedAnimation(); void startAnimation(bool forward); QWidget* container; QPropertyAnimation* animation; qreal percent = 1.0f; QPoint startPos; }; #endif // FLYOUTOVERLAYWIDGET_HPP qTox/src/widget/tool/friendrequestdialog.cpp000066400000000000000000000050351415623743500216210ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "friendrequestdialog.h" #include #include #include #include #include #include FriendRequestDialog::FriendRequestDialog(QWidget* parent, const QString& userId, const QString& message) : QDialog(parent) { setAttribute(Qt::WA_QuitOnClose, false); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(tr("Friend request", "Title of the window to aceept/deny a friend request")); QLabel* friendsLabel = new QLabel(tr("Someone wants to make friends with you"), this); QLabel* userIdLabel = new QLabel(tr("User ID:"), this); QLineEdit* userIdEdit = new QLineEdit(userId, this); userIdEdit->setCursorPosition(0); userIdEdit->setReadOnly(true); QLabel* messageLabel = new QLabel(tr("Friend request message:"), this); QPlainTextEdit* messageEdit = new QPlainTextEdit(message, this); messageEdit->setReadOnly(true); QDialogButtonBox* buttonBox = new QDialogButtonBox(Qt::Horizontal, this); buttonBox->addButton(tr("Accept", "Accept a friend request"), QDialogButtonBox::AcceptRole); buttonBox->addButton(tr("Reject", "Reject a friend request"), QDialogButtonBox::RejectRole); connect(buttonBox, &QDialogButtonBox::accepted, this, &FriendRequestDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &FriendRequestDialog::reject); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(friendsLabel); layout->addSpacing(12); layout->addWidget(userIdLabel); layout->addWidget(userIdEdit); layout->addWidget(messageLabel); layout->addWidget(messageEdit); layout->addWidget(buttonBox); resize(300, 200); } qTox/src/widget/tool/friendrequestdialog.h000066400000000000000000000021401415623743500212600ustar00rootroot00000000000000/* Copyright © 2013 by Maxim Biro Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef FRIENDREQUESTDIALOG_HPP #define FRIENDREQUESTDIALOG_HPP #include class FriendRequestDialog : public QDialog { Q_OBJECT public: explicit FriendRequestDialog(QWidget* parent, const QString& userId, const QString& message); }; #endif // FRIENDREQUESTDIALOG_HPP qTox/src/widget/tool/identicon.cpp000066400000000000000000000112531415623743500175340ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "identicon.h" #include "src/core/toxpk.h" #include #include #include #include #include // The following constants change the appearance of the identicon // they have been choosen by trying to make the output look nice. /** * @var Identicon::IDENTICON_COLOR_BYTES * Specifies how many bytes should define the foreground color * must be smaller than 8, else there'll be overflows */ /** * @var Identicon::COLORS * Number of colors to use for the identicon */ /** * @var Identicon::IDENTICON_ROWS * Specifies how many rows of blocks the identicon should have */ /** * @var Identicon::ACTIVE_COLS * Width from the center to the outside, for 5 columns it's 3, 6 -> 3, 7 -> 4 */ /** * @var Identicon::HASH_MIN_LEN * Min length of the hash in bytes, 7 bytes control the color, * the rest controls the pixel placement */ /** * @brief Creates an Identicon, that visualizes a hash in graphical form. * @param data Data to visualize */ Identicon::Identicon(const QByteArray& data) { static_assert(Identicon::COLORS == 2, "Only two colors are implemented"); // hash with sha256 QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256); for (int colorIndex = 0; colorIndex < COLORS; ++colorIndex) { const QByteArray hashPart = hash.right(IDENTICON_COLOR_BYTES); hash.truncate(hash.length() - IDENTICON_COLOR_BYTES); const qreal hue = bytesToColor(hashPart); // change offset when COLORS != 2 const qreal lig = static_cast(colorIndex) / COLORS + 0.3; const qreal sat = 0.5; colors[colorIndex].setHslF(hue, sat, lig); } const uint8_t* const hashBytes = reinterpret_cast(hash.constData()); // compute the block colors from the hash for (int row = 0; row < IDENTICON_ROWS; ++row) { for (int col = 0; col < ACTIVE_COLS; ++col) { const int hashIdx = row * ACTIVE_COLS + col; const uint8_t colorIndex = hashBytes[hashIdx] % COLORS; identiconColors[row][col] = colorIndex; } } } /** * @brief Converts a series of IDENTICON_COLOR_BYTES bytes to a value in the range 0.0..1.0 * @param bytes Bytes to convert to a color * @return Value in the range of 0.0..1.0 */ float Identicon::bytesToColor(QByteArray bytes) { static_assert(IDENTICON_COLOR_BYTES <= 8, "IDENTICON_COLOR max value is 8"); const uint8_t* const bytesChr = reinterpret_cast(bytes.constData()); assert(bytes.length() == IDENTICON_COLOR_BYTES); // get foreground color uint64_t hue = bytesChr[0]; // convert the last bytes to an uint for (int i = 1; i < IDENTICON_COLOR_BYTES; ++i) { hue = hue << 8; hue += bytesChr[i]; } // normalize to 0.0 ... 1.0 return (static_cast(hue)) / (static_cast(((static_cast(1)) << (8 * IDENTICON_COLOR_BYTES)) - 1)); } /** * @brief Writes the Identicon to a QImage * @param scaleFactor the image will be a square with scaleFactor * IDENTICON_ROWS pixels, * must be >= 1 * @return a QImage with the identicon */ QImage Identicon::toImage(int scaleFactor) { if (scaleFactor < 1) { qDebug() << "Can't scale with values <1, clamping to 1"; scaleFactor = 1; } scaleFactor *= IDENTICON_ROWS; QImage pixels(IDENTICON_ROWS, IDENTICON_ROWS, QImage::Format_RGB888); for (int row = 0; row < IDENTICON_ROWS; ++row) { for (int col = 0; col < IDENTICON_ROWS; ++col) { // mirror on vertical axis const int columnIdx = abs((col * 2 - (IDENTICON_ROWS - 1)) / 2); const int colorIdx = identiconColors[row][columnIdx]; pixels.setPixel(col, row, colors[colorIdx].rgb()); } } // scale up without smoothing to make it look sharp return pixels.scaled(scaleFactor, scaleFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation); } qTox/src/widget/tool/identicon.h000066400000000000000000000026771415623743500172130ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef IDENTICON_H #define IDENTICON_H #include #include class Identicon { public: Identicon(const QByteArray& data); QImage toImage(int scaleFactor = 1); public: static constexpr int IDENTICON_ROWS = 5; private: float bytesToColor(QByteArray bytes); private: static constexpr int COLORS = 2; static constexpr int ACTIVE_COLS = (IDENTICON_ROWS + 1) / 2; static constexpr int IDENTICON_COLOR_BYTES = 6; static constexpr int HASH_MIN_LEN = ACTIVE_COLS * IDENTICON_ROWS + COLORS * IDENTICON_COLOR_BYTES; uint8_t identiconColors[IDENTICON_ROWS][ACTIVE_COLS]; QColor colors[COLORS]; }; #endif // IDENTICON_H qTox/src/widget/tool/movablewidget.cpp000066400000000000000000000204751415623743500204170ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "movablewidget.h" #include #include #include MovableWidget::MovableWidget(QWidget* parent) : QWidget(parent) { setMinimumHeight(64); setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)); actualSize = minimumSize(); boundaryRect = QRect(0, 0, 0, 0); setRatio(1.0f); resize(minimumSize()); actualPos = QPoint(0, 0); } void MovableWidget::resetBoundary(QRect newBoundary) { boundaryRect = newBoundary; resize(QSize(round(actualSize.width()), round(actualSize.height()))); QPoint moveTo = QPoint(round(actualPos.x()), round(actualPos.y())); checkBoundary(moveTo); move(moveTo); actualPos = moveTo; } void MovableWidget::setBoundary(QRect newBoundary) { if (boundaryRect.isNull()) { boundaryRect = newBoundary; return; } float changeX = newBoundary.width() / static_cast(boundaryRect.width()); float changeY = newBoundary.height() / static_cast(boundaryRect.height()); float percentageX = (x() - boundaryRect.x()) / static_cast(boundaryRect.width() - width()); float percentageY = (y() - boundaryRect.y()) / static_cast(boundaryRect.height() - height()); actualSize.setWidth(actualSize.width() * changeX); actualSize.setHeight(actualSize.height() * changeY); if (actualSize.width() == 0) actualSize.setWidth(1); if (actualSize.height() == 0) actualSize.setHeight(1); resize(QSize(round(actualSize.width()), round(actualSize.height()))); actualPos = QPointF(percentageX * (newBoundary.width() - width()), percentageY * (newBoundary.height() - height())); actualPos += QPointF(newBoundary.topLeft()); QPoint moveTo = QPoint(round(actualPos.x()), round(actualPos.y())); move(moveTo); boundaryRect = newBoundary; } float MovableWidget::getRatio() const { return ratio; } void MovableWidget::setRatio(float r) { ratio = r; resize(width(), width() / ratio); QPoint position = QPoint(actualPos.x(), actualPos.y()); checkBoundary(position); move(position); actualPos = pos(); actualSize = size(); } void MovableWidget::mousePressEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { if (!(mode & Resize)) mode |= Moving; lastPoint = event->globalPos(); } } void MovableWidget::mouseMoveEvent(QMouseEvent* event) { if (mode & Moving) { QPoint moveTo = pos() - (lastPoint - event->globalPos()); checkBoundary(moveTo); move(moveTo); lastPoint = event->globalPos(); actualPos = pos(); } else { if (!(event->buttons() & Qt::LeftButton)) { if (event->x() < 6) mode |= ResizeLeft; else mode &= ~ResizeLeft; if (event->y() < 6) mode |= ResizeUp; else mode &= ~ResizeUp; if (event->x() > width() - 6) mode |= ResizeRight; else mode &= ~ResizeRight; if (event->y() > height() - 6) mode |= ResizeDown; else mode &= ~ResizeDown; } if (mode & Resize) { const Modes ResizeUpRight = ResizeUp | ResizeRight; const Modes ResizeUpLeft = ResizeUp | ResizeLeft; const Modes ResizeDownRight = ResizeDown | ResizeRight; const Modes ResizeDownLeft = ResizeDown | ResizeLeft; if ((mode & ResizeUpRight) == ResizeUpRight || (mode & ResizeDownLeft) == ResizeDownLeft) setCursor(Qt::SizeBDiagCursor); else if ((mode & ResizeUpLeft) == ResizeUpLeft || (mode & ResizeDownRight) == ResizeDownRight) setCursor(Qt::SizeFDiagCursor); else if (mode & (ResizeLeft | ResizeRight)) setCursor(Qt::SizeHorCursor); else setCursor(Qt::SizeVerCursor); if (event->buttons() & Qt::LeftButton) { QPoint lastPosition = pos(); QPoint displacement = lastPoint - event->globalPos(); QSize lastSize = size(); if (mode & ResizeUp) { lastSize.setHeight(height() + displacement.y()); if (lastSize.height() > maximumHeight()) lastPosition.setY(y() - displacement.y() + (lastSize.height() - maximumHeight())); else lastPosition.setY(y() - displacement.y()); } if (mode & ResizeLeft) { lastSize.setWidth(width() + displacement.x()); if (lastSize.width() > maximumWidth()) lastPosition.setX(x() - displacement.x() + (lastSize.width() - maximumWidth())); else lastPosition.setX(x() - displacement.x()); } if (mode & ResizeRight) lastSize.setWidth(width() - displacement.x()); if (mode & ResizeDown) lastSize.setHeight(height() - displacement.y()); if (lastSize.height() > maximumHeight()) lastSize.setHeight(maximumHeight()); if (lastSize.width() > maximumWidth()) lastSize.setWidth(maximumWidth()); if (mode & (ResizeLeft | ResizeRight)) { if (mode & (ResizeUp | ResizeDown)) { int height = lastSize.width() / getRatio(); if (!(mode & ResizeDown)) lastPosition.setY(lastPosition.y() - (height - lastSize.height())); resize(lastSize.width(), height); if (lastSize.width() < minimumWidth()) lastPosition.setX(pos().x()); if (height < minimumHeight()) lastPosition.setY(pos().y()); } else { resize(lastSize.width(), lastSize.width() / getRatio()); } } else { resize(lastSize.height() * getRatio(), lastSize.height()); } updateGeometry(); checkBoundary(lastPosition); move(lastPosition); lastPoint = event->globalPos(); actualSize = size(); actualPos = pos(); } } else { unsetCursor(); } } } void MovableWidget::mouseReleaseEvent(QMouseEvent* event) { if (!(event->buttons() & Qt::LeftButton)) mode = 0; } void MovableWidget::mouseDoubleClickEvent(QMouseEvent* event) { if (!(event->buttons() & Qt::LeftButton)) return; if (!graphicsEffect()) { QGraphicsOpacityEffect* opacityEffect = new QGraphicsOpacityEffect(this); opacityEffect->setOpacity(0.5); setGraphicsEffect(opacityEffect); } else { setGraphicsEffect(nullptr); } } void MovableWidget::checkBoundary(QPoint& point) const { int x1, y1, x2, y2; boundaryRect.getCoords(&x1, &y1, &x2, &y2); if (point.x() < boundaryRect.left()) point.setX(boundaryRect.left()); if (point.y() < boundaryRect.top()) point.setY(boundaryRect.top()); if (point.x() + width() > boundaryRect.right() + 1) point.setX(boundaryRect.right() - width() + 1); if (point.y() + height() > boundaryRect.bottom() + 1) point.setY(boundaryRect.bottom() - height() + 1); } qTox/src/widget/tool/movablewidget.h000066400000000000000000000033761415623743500200650ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef MOVABLEWIDGET_H #define MOVABLEWIDGET_H #include class MovableWidget : public QWidget { public: explicit MovableWidget(QWidget* parent); void resetBoundary(QRect newBoundary); void setBoundary(QRect newBoundary); float getRatio() const; void setRatio(float r); protected: void mousePressEvent(QMouseEvent* event); void mouseMoveEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); void mouseDoubleClickEvent(QMouseEvent* event); private: void checkBoundary(QPoint& point) const; void checkBoundaryLeft(int& x) const; typedef uint8_t Modes; enum Mode : Modes { Moving = 0x01, ResizeLeft = 0x02, ResizeRight = 0x04, ResizeUp = 0x08, ResizeDown = 0x10, Resize = ResizeLeft | ResizeRight | ResizeUp | ResizeDown }; Modes mode = 0; QPoint lastPoint; QRect boundaryRect; QSizeF actualSize; QPointF actualPos; float ratio; }; #endif // MOVABLEWIDGET_H qTox/src/widget/tool/profileimporter.cpp000066400000000000000000000111321415623743500207760ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "profileimporter.h" #include #include #include #include #include "src/core/core.h" #include "src/persistence/settings.h" /** * @class ProfileImporter * @brief This class provides the ability to import profile. * @note This class can be used before @a Nexus instance will be created, * consequently it can't use @a GUI class. Therefore it should use QMessageBox * to create dialog forms. */ ProfileImporter::ProfileImporter(QWidget* parent) : QWidget(parent) { } /** * @brief Show a file dialog. Selected file will be imported as Tox profile. * @return True, if the import was succesful. False otherwise. */ bool ProfileImporter::importProfile() { QString title = tr("Import profile", "import dialog title"); QString filter = tr("Tox save file (*.tox)", "import dialog filter"); QString dir = QDir::homePath(); // TODO: Change all QFileDialog instances across project to use // this instead of Q_NULLPTR. Possibly requires >Qt 5.9 due to: // https://bugreports.qt.io/browse/QTBUG-59184 QString path = QFileDialog::getOpenFileName(Q_NULLPTR, title, dir, filter); return importProfile(path); } /** * @brief Asks the user a question with Yes/No choices. * @param title Title of question window. * @param message Text in question window. * @return True if the answer is positive, false otherwise. */ bool ProfileImporter::askQuestion(QString title, QString message) { QMessageBox::Icon icon = QMessageBox::Warning; QMessageBox box(icon, title, message, QMessageBox::NoButton, this); QPushButton* pushButton1 = box.addButton(QApplication::tr("Yes"), QMessageBox::AcceptRole); QPushButton* pushButton2 = box.addButton(QApplication::tr("No"), QMessageBox::RejectRole); box.setDefaultButton(pushButton2); box.setEscapeButton(pushButton2); box.exec(); return box.clickedButton() == pushButton1; } /** * @brief Try to import Tox profile. * @param path Path to Tox profile. * @return True, if the import was succesful. False otherwise. */ bool ProfileImporter::importProfile(const QString& path) { if (path.isEmpty()) return false; QFileInfo info(path); if (!info.exists()) { QMessageBox::warning(this, tr("File doesn't exist"), tr("Profile doesn't exist"), QMessageBox::Ok); return false; } QString profile = info.completeBaseName(); if (info.suffix() != "tox") { QMessageBox::warning(this, tr("Ignoring non-Tox file", "popup title"), tr("Warning: You have chosen a file that is not a " "Tox save file; ignoring.", "popup text"), QMessageBox::Ok); return false; // ingore importing non-tox file } QString settingsPath = Settings::getInstance().getSettingsDirPath(); QString profilePath = QDir(settingsPath).filePath(profile + Core::TOX_EXT); if (QFileInfo(profilePath).exists()) { QString title = tr("Profile already exists", "import confirm title"); QString message = tr("A profile named \"%1\" already exists. " "Do you want to erase it?", "import confirm text") .arg(profile); bool erase = askQuestion(title, message); if (!erase) return false; // import canelled QFile(profilePath).remove(); } QFile::copy(path, profilePath); // no good way to update the ui from here... maybe we need a Widget:refreshUi() function... // such a thing would simplify other code as well I believe QMessageBox::information(this, tr("Profile imported"), tr("%1.tox was successfully imported").arg(profile), QMessageBox::Ok); return true; // import successfull } qTox/src/widget/tool/profileimporter.h000066400000000000000000000021511415623743500204440ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef PROFILEIMPORTER_H #define PROFILEIMPORTER_H #include class ProfileImporter : public QWidget { Q_OBJECT public: explicit ProfileImporter(QWidget* parent = nullptr); bool importProfile(const QString& path); bool importProfile(); private: bool askQuestion(QString title, QString message); }; #endif // PROFILEIMPORTER_H qTox/src/widget/tool/recursivesignalblocker.cpp000066400000000000000000000034311415623743500223260ustar00rootroot00000000000000/* Copyright © 2016-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "recursivesignalblocker.h" #include #include /** @class RecursiveSignalBlocker @brief Recursively blocks all signals from an object and its children. @note All children must be created before the blocker is used. Wraps a QSignalBlocker on each object. Signals will be unblocked when the blocker gets destroyed. According to QSignalBlocker, we are also exception safe. */ /** @brief Creates a QSignalBlocker recursively on the object and child objects. @param[in] object the object, which signals should be blocked */ RecursiveSignalBlocker::RecursiveSignalBlocker(QObject* object) { recursiveBlock(object); } RecursiveSignalBlocker::~RecursiveSignalBlocker() { qDeleteAll(mBlockers); } /** @brief Recursively blocks all signals of the object. @param[in] object the object to block */ void RecursiveSignalBlocker::recursiveBlock(QObject* object) { mBlockers << new QSignalBlocker(object); for (QObject* child : object->children()) { recursiveBlock(child); } } qTox/src/widget/tool/recursivesignalblocker.h000066400000000000000000000021621415623743500217730ustar00rootroot00000000000000/* Copyright © 2016-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef QTOX_RECURSIVESIGNALBLOCKER_H #define QTOX_RECURSIVESIGNALBLOCKER_H #include class QObject; class QSignalBlocker; class RecursiveSignalBlocker { public: explicit RecursiveSignalBlocker(QObject* object); ~RecursiveSignalBlocker(); void recursiveBlock(QObject* object); private: QVector mBlockers; }; #endif qTox/src/widget/tool/removefrienddialog.cpp000066400000000000000000000034151415623743500214260ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "removefrienddialog.h" #include RemoveFriendDialog::RemoveFriendDialog(QWidget* parent, const Friend* f) : QDialog(parent) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setAttribute(Qt::WA_QuitOnClose, false); ui.setupUi(this); QString name = f->getDisplayedName().toHtmlEscaped(); QString text = tr("Are you sure you want to remove %1 from your contacts list?") .arg(QString("%1").arg(name)); ui.label->setText(text); auto removeButton = ui.buttonBox->button(QDialogButtonBox::Ok); auto cancelButton = ui.buttonBox->button(QDialogButtonBox::Cancel); removeButton->setText(tr("Remove")); cancelButton->setDefault(true); adjustSize(); connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &RemoveFriendDialog::onAccepted); connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &RemoveFriendDialog::close); setFocus(); } void RemoveFriendDialog::onAccepted() { _accepted = true; close(); } qTox/src/widget/tool/removefrienddialog.h000066400000000000000000000024761415623743500211010ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef DELETEFRIENDDIALOG_H #define DELETEFRIENDDIALOG_H #include "ui_removefrienddialog.h" #include "src/model/friend.h" #include class RemoveFriendDialog : public QDialog { Q_OBJECT public: explicit RemoveFriendDialog(QWidget* parent, const Friend* f); inline bool removeHistory() { return ui.removeHistory->isChecked(); } inline bool accepted() { return _accepted; } public slots: void onAccepted(); protected: Ui_RemoveFriendDialog ui; bool _accepted = false; }; #endif // DELETEFRIENDDIALOG_H qTox/src/widget/tool/screengrabberchooserrectitem.cpp000066400000000000000000000231741415623743500235110ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "screengrabberchooserrectitem.h" #include #include #include #include enum { HandleSize = 10, MinRectSize = 2, }; ScreenGrabberChooserRectItem::ScreenGrabberChooserRectItem(QGraphicsScene* scene) { scene->addItem(this); setCursor(QCursor(Qt::OpenHandCursor)); this->mainRect = createHandleItem(scene); this->topLeft = createHandleItem(scene); this->topCenter = createHandleItem(scene); this->topRight = createHandleItem(scene); this->rightCenter = createHandleItem(scene); this->bottomRight = createHandleItem(scene); this->bottomCenter = createHandleItem(scene); this->bottomLeft = createHandleItem(scene); this->leftCenter = createHandleItem(scene); this->topLeft->setCursor(QCursor(Qt::SizeFDiagCursor)); this->bottomRight->setCursor(QCursor(Qt::SizeFDiagCursor)); this->topRight->setCursor(QCursor(Qt::SizeBDiagCursor)); this->bottomLeft->setCursor(QCursor(Qt::SizeBDiagCursor)); this->leftCenter->setCursor(QCursor(Qt::SizeHorCursor)); this->rightCenter->setCursor(QCursor(Qt::SizeHorCursor)); this->topCenter->setCursor(QCursor(Qt::SizeVerCursor)); this->bottomCenter->setCursor(QCursor(Qt::SizeVerCursor)); this->mainRect->setRect(QRect()); hideHandles(); } ScreenGrabberChooserRectItem::~ScreenGrabberChooserRectItem() { } QRectF ScreenGrabberChooserRectItem::boundingRect() const { return QRectF(-HandleSize - 1, -HandleSize - 1, rectWidth + HandleSize + 1, rectHeight + HandleSize + 1); } void ScreenGrabberChooserRectItem::beginResize(QPointF mousePos) { rectWidth = this->rectHeight = 0; mainRect->setRect(QRect()); state = Resizing; startPos = mousePos; setCursor(QCursor(Qt::CrossCursor)); hideHandles(); mainRect->grabMouse(); } QRect ScreenGrabberChooserRectItem::chosenRect() const { QRect rect(x(), y(), rectWidth, rectHeight); if (rectWidth < 0) { rect.setX(rect.x() + rectWidth); rect.setWidth(-rectWidth); } if (rectHeight < 0) { rect.setY(rect.y() + rectHeight); rect.setHeight(-rectHeight); } return rect; } void ScreenGrabberChooserRectItem::showHandles() { this->topLeft->show(); this->topCenter->show(); this->topRight->show(); this->rightCenter->show(); this->bottomRight->show(); this->bottomCenter->show(); this->bottomLeft->show(); this->leftCenter->show(); } void ScreenGrabberChooserRectItem::hideHandles() { this->topLeft->hide(); this->topCenter->hide(); this->topRight->hide(); this->rightCenter->hide(); this->bottomRight->hide(); this->bottomCenter->hide(); this->bottomLeft->hide(); this->leftCenter->hide(); } void ScreenGrabberChooserRectItem::mousePress(QGraphicsSceneMouseEvent* event) { if (event->button() == Qt::LeftButton) { this->state = Moving; setCursor(QCursor(Qt::ClosedHandCursor)); } } void ScreenGrabberChooserRectItem::mouseMove(QGraphicsSceneMouseEvent* event) { if (this->state == Moving) { QPointF delta = event->scenePos() - event->lastScenePos(); moveBy(delta.x(), delta.y()); } else if (this->state == Resizing) { prepareGeometryChange(); QPointF size = event->scenePos() - scenePos(); mainRect->setRect(0, 0, size.x(), size.y()); rectWidth = size.x(); rectHeight = size.y(); updateHandlePositions(); } else { return; } emit regionChosen(chosenRect()); } void ScreenGrabberChooserRectItem::mouseRelease(QGraphicsSceneMouseEvent* event) { if (event->button() == Qt::LeftButton) { setCursor(QCursor(Qt::OpenHandCursor)); QPointF delta = (event->scenePos() - startPos); if (qAbs(delta.x()) < MinRectSize || qAbs(delta.y()) < MinRectSize) { rectWidth = rectHeight = 0; mainRect->setRect(QRect()); } else { QRect normalized = chosenRect(); rectWidth = normalized.width(); rectHeight = normalized.height(); setPos(normalized.x(), normalized.y()); mainRect->setRect(0, 0, rectWidth, rectHeight); updateHandlePositions(); showHandles(); } emit regionChosen(chosenRect()); state = None; mainRect->ungrabMouse(); } } void ScreenGrabberChooserRectItem::mouseDoubleClick(QGraphicsSceneMouseEvent* event) { Q_UNUSED(event); emit doubleClicked(); } void ScreenGrabberChooserRectItem::mousePressHandle(int x, int y, QGraphicsSceneMouseEvent* event) { Q_UNUSED(x); Q_UNUSED(y); if (event->button() == Qt::LeftButton) this->state = HandleResizing; } void ScreenGrabberChooserRectItem::mouseMoveHandle(int x, int y, QGraphicsSceneMouseEvent* event) { if (this->state != HandleResizing) return; QPointF delta = event->scenePos() - event->lastScenePos(); delta.rx() *= qAbs(x); delta.ry() *= qAbs(y); // We increase if the multiplier and the delta have the same sign bool increaseX = ((x < 0) == (delta.x() < 0)); bool increaseY = ((y < 0) == (delta.y() < 0)); if ((delta.x() < 0 && increaseX) || (delta.x() >= 0 && !increaseX)) { moveBy(delta.x(), 0); delta.rx() *= -1; } if ((delta.y() < 0 && increaseY) || (delta.y() >= 0 && !increaseY)) { moveBy(0, delta.y()); delta.ry() *= -1; } // this->rectWidth += delta.x(); this->rectHeight += delta.y(); this->mainRect->setRect(0, 0, this->rectWidth, this->rectHeight); updateHandlePositions(); emit regionChosen(chosenRect()); } void ScreenGrabberChooserRectItem::mouseReleaseHandle(int x, int y, QGraphicsSceneMouseEvent* event) { Q_UNUSED(x); Q_UNUSED(y); if (event->button() == Qt::LeftButton) this->state = None; } QPoint ScreenGrabberChooserRectItem::getHandleMultiplier(QGraphicsItem* handle) { if (handle == this->topLeft) return QPoint(-1, -1); if (handle == this->topCenter) return QPoint(0, -1); if (handle == this->topRight) return QPoint(1, -1); if (handle == this->rightCenter) return QPoint(1, 0); if (handle == this->bottomRight) return QPoint(1, 1); if (handle == this->bottomCenter) return QPoint(0, 1); if (handle == this->bottomLeft) return QPoint(-1, 1); if (handle == this->leftCenter) return QPoint(-1, 0); return QPoint(); } void ScreenGrabberChooserRectItem::updateHandlePositions() { this->topLeft->setPos(-HandleSize, -HandleSize); this->topCenter->setPos((this->rectWidth - HandleSize) / 2, -HandleSize); this->topRight->setPos(this->rectWidth, -HandleSize); this->rightCenter->setPos(this->rectWidth, (this->rectHeight - HandleSize) / 2); this->bottomRight->setPos(this->rectWidth, this->rectHeight); this->bottomCenter->setPos((this->rectWidth - HandleSize) / 2, this->rectHeight); this->bottomLeft->setPos(-HandleSize, this->rectHeight); this->leftCenter->setPos(-HandleSize, (this->rectHeight - HandleSize) / 2); } QGraphicsRectItem* ScreenGrabberChooserRectItem::createHandleItem(QGraphicsScene* scene) { QGraphicsRectItem* handle = new QGraphicsRectItem(0, 0, HandleSize, HandleSize); handle->setPen(QPen(Qt::blue)); handle->setBrush(Qt::NoBrush); scene->addItem(handle); addToGroup(handle); handle->installSceneEventFilter(this); return handle; } bool ScreenGrabberChooserRectItem::sceneEventFilter(QGraphicsItem* watched, QEvent* event) { if (watched == this->mainRect) forwardMainRectEvent(event); else forwardHandleEvent(watched, event); return true; } void ScreenGrabberChooserRectItem::forwardMainRectEvent(QEvent* event) { QGraphicsSceneMouseEvent* mouseEvent = static_cast(event); switch (event->type()) { case QEvent::GraphicsSceneMousePress: return mousePress(mouseEvent); case QEvent::GraphicsSceneMouseMove: return mouseMove(mouseEvent); case QEvent::GraphicsSceneMouseRelease: return mouseRelease(mouseEvent); case QEvent::GraphicsSceneMouseDoubleClick: return mouseDoubleClick(mouseEvent); default: return; } } void ScreenGrabberChooserRectItem::forwardHandleEvent(QGraphicsItem* watched, QEvent* event) { QGraphicsSceneMouseEvent* mouseEvent = static_cast(event); QPoint multiplier = getHandleMultiplier(watched); if (multiplier.isNull()) return; switch (event->type()) { case QEvent::GraphicsSceneMousePress: return mousePressHandle(multiplier.x(), multiplier.y(), mouseEvent); case QEvent::GraphicsSceneMouseMove: return mouseMoveHandle(multiplier.x(), multiplier.y(), mouseEvent); case QEvent::GraphicsSceneMouseRelease: return mouseReleaseHandle(multiplier.x(), multiplier.y(), mouseEvent); default: return; } } qTox/src/widget/tool/screengrabberchooserrectitem.h000066400000000000000000000052461415623743500231560ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SCREENGRABBERCHOOSERRECTITEM_HPP #define SCREENGRABBERCHOOSERRECTITEM_HPP #include class ScreenGrabberChooserRectItem final : public QObject, public QGraphicsItemGroup { Q_OBJECT public: explicit ScreenGrabberChooserRectItem(QGraphicsScene* scene); ~ScreenGrabberChooserRectItem(); virtual QRectF boundingRect() const final override; void beginResize(QPointF mousePos); QRect chosenRect() const; void showHandles(); void hideHandles(); signals: void doubleClicked(); void regionChosen(QRect rect); protected: virtual bool sceneEventFilter(QGraphicsItem* watched, QEvent* event) final override; private: enum State { None, Resizing, HandleResizing, Moving, }; State state = None; int rectWidth = 0; int rectHeight = 0; QPointF startPos; void forwardMainRectEvent(QEvent* event); void forwardHandleEvent(QGraphicsItem* watched, QEvent* event); void mousePress(QGraphicsSceneMouseEvent* event); void mouseMove(QGraphicsSceneMouseEvent* event); void mouseRelease(QGraphicsSceneMouseEvent* event); void mouseDoubleClick(QGraphicsSceneMouseEvent* event); void mousePressHandle(int x, int y, QGraphicsSceneMouseEvent* event); void mouseMoveHandle(int x, int y, QGraphicsSceneMouseEvent* event); void mouseReleaseHandle(int x, int y, QGraphicsSceneMouseEvent* event); QPoint getHandleMultiplier(QGraphicsItem* handle); void updateHandlePositions(); QGraphicsRectItem* createHandleItem(QGraphicsScene* scene); QGraphicsRectItem* mainRect; QGraphicsRectItem* topLeft; QGraphicsRectItem* topCenter; QGraphicsRectItem* topRight; QGraphicsRectItem* rightCenter; QGraphicsRectItem* bottomRight; QGraphicsRectItem* bottomCenter; QGraphicsRectItem* bottomLeft; QGraphicsRectItem* leftCenter; }; #endif // SCREENGRABBERCHOOSERRECTITEM_HPP qTox/src/widget/tool/screengrabberoverlayitem.cpp000066400000000000000000000045501415623743500226470ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "screengrabberoverlayitem.h" #include #include #include #include #include "screenshotgrabber.h" ScreenGrabberOverlayItem::ScreenGrabberOverlayItem(ScreenshotGrabber* grabber) : screnshootGrabber(grabber) { QBrush overlayBrush(QColor(0x00, 0x00, 0x00, 0x70)); // Translucent black setCursor(QCursor(Qt::CrossCursor)); setBrush(overlayBrush); setPen(QPen(Qt::NoPen)); } ScreenGrabberOverlayItem::~ScreenGrabberOverlayItem() { } void ScreenGrabberOverlayItem::setChosenRect(QRect rect) { QRect oldRect = chosenRect; chosenRect = rect; update(oldRect.united(rect)); } void ScreenGrabberOverlayItem::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (event->button() == Qt::LeftButton) this->screnshootGrabber->beginRectChooser(event); } void ScreenGrabberOverlayItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { painter->setBrush(brush()); painter->setPen(pen()); QRectF self = rect(); qreal leftX = chosenRect.x(); qreal rightX = chosenRect.x() + chosenRect.width(); qreal topY = chosenRect.y(); qreal bottomY = chosenRect.y() + chosenRect.height(); painter->drawRect(0, 0, leftX, self.height()); // Left of chosen painter->drawRect(rightX, 0, self.width() - rightX, self.height()); // Right of chosen painter->drawRect(leftX, 0, chosenRect.width(), topY); // Top of chosen painter->drawRect(leftX, bottomY, chosenRect.width(), self.height() - bottomY); // Bottom of chosen } qTox/src/widget/tool/screengrabberoverlayitem.h000066400000000000000000000027221415623743500223130ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SCREENGRABBEROVERLAYITEM_HPP #define SCREENGRABBEROVERLAYITEM_HPP #include class ScreenshotGrabber; class ScreenGrabberOverlayItem final : public QObject, public QGraphicsRectItem { Q_OBJECT public: explicit ScreenGrabberOverlayItem(ScreenshotGrabber* grabber); ~ScreenGrabberOverlayItem(); void setChosenRect(QRect rect); protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) final override; virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) final override; private: ScreenshotGrabber* screnshootGrabber; QRect chosenRect; }; #endif // SCREENGRABBEROVERLAYITEM_HPP qTox/src/widget/tool/screenshotgrabber.cpp000066400000000000000000000175451415623743500212740ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "screenshotgrabber.h" #include #include #include #include #include #include #include #include #include #include #include "screengrabberchooserrectitem.h" #include "screengrabberoverlayitem.h" #include "toolboxgraphicsitem.h" #include "src/widget/widget.h" ScreenshotGrabber::ScreenshotGrabber() : QObject() , mKeysBlocked(false) , scene(nullptr) , mQToxVisible(true) { window = new QGraphicsView(scene); // Top-level widget window->setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint); window->setContentsMargins(0, 0, 0, 0); window->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); window->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); window->setFrameShape(QFrame::NoFrame); window->installEventFilter(this); pixRatio = QApplication::primaryScreen()->devicePixelRatio(); setupScene(); } void ScreenshotGrabber::reInit() { window->resetCachedContent(); setupScene(); showGrabber(); mKeysBlocked = false; } ScreenshotGrabber::~ScreenshotGrabber() { delete scene; delete window; } bool ScreenshotGrabber::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::KeyPress) return handleKeyPress(static_cast(event)); return QObject::eventFilter(object, event); } void ScreenshotGrabber::showGrabber() { this->screenGrab = grabScreen(); this->screenGrabDisplay->setPixmap(this->screenGrab); this->window->show(); this->window->setFocus(); this->window->grabKeyboard(); QRect fullGrabbedRect = screenGrab.rect(); QRect rec = QApplication::primaryScreen()->virtualGeometry(); this->window->setGeometry(rec); this->scene->setSceneRect(fullGrabbedRect); this->overlay->setRect(fullGrabbedRect); adjustTooltipPosition(); } bool ScreenshotGrabber::handleKeyPress(QKeyEvent* event) { if (mKeysBlocked) return false; if (event->key() == Qt::Key_Escape) reject(); else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) acceptRegion(); else if (event->key() == Qt::Key_Space) { mKeysBlocked = true; if (mQToxVisible) hideVisibleWindows(); else restoreHiddenWindows(); window->hide(); QTimer::singleShot(350, this, SLOT(reInit())); } else return false; return true; } void ScreenshotGrabber::acceptRegion() { QRect rect = this->chooserRect->chosenRect(); if (rect.width() < 1 || rect.height() < 1) return; // Scale the accepted region from DIPs to actual pixels rect.setRect(rect.x() * pixRatio, rect.y() * pixRatio, rect.width() * pixRatio, rect.height() * pixRatio); emit regionChosen(rect); qDebug() << "Screenshot accepted, chosen region" << rect; QPixmap pixmap = this->screenGrab.copy(rect); restoreHiddenWindows(); emit screenshotTaken(pixmap); deleteLater(); } void ScreenshotGrabber::setupScene() { delete scene; scene = new QGraphicsScene; window->setScene(scene); this->overlay = new ScreenGrabberOverlayItem(this); this->helperToolbox = new ToolBoxGraphicsItem; this->screenGrabDisplay = scene->addPixmap(this->screenGrab); this->helperTooltip = scene->addText(QString()); scene->addItem(this->overlay); this->chooserRect = new ScreenGrabberChooserRectItem(scene); scene->addItem(this->helperToolbox); this->helperToolbox->addToGroup(this->helperTooltip); this->helperTooltip->setDefaultTextColor(Qt::black); useNothingSelectedTooltip(); connect(this->chooserRect, &ScreenGrabberChooserRectItem::doubleClicked, this, &ScreenshotGrabber::acceptRegion); connect(this->chooserRect, &ScreenGrabberChooserRectItem::regionChosen, this, &ScreenshotGrabber::chooseHelperTooltipText); connect(this->chooserRect, &ScreenGrabberChooserRectItem::regionChosen, this->overlay, &ScreenGrabberOverlayItem::setChosenRect); } void ScreenshotGrabber::useNothingSelectedTooltip() { helperTooltip->setHtml( tr("Click and drag to select a region. Press %1 to " "hide/show qTox window, or %2 to cancel.", "Help text shown when no region has been selected yet") .arg(QString("%1").arg(tr("Space", "[Space] key on the keyboard")), QString("%1").arg(tr("Escape", "[Escape] key on the keyboard")))); adjustTooltipPosition(); } void ScreenshotGrabber::useRegionSelectedTooltip() { helperTooltip->setHtml( tr("Press %1 to send a screenshot of the selection, " "%2 to hide/show qTox window, or %3 to cancel.", "Help text shown when a region has been selected") .arg(QString("%1").arg(tr("Enter", "[Enter] key on the keyboard")), QString("%1").arg(tr("Space", "[Space] key on the keyboard")), QString("%1").arg(tr("Escape", "[Escape] key on the keyboard")))); adjustTooltipPosition(); } void ScreenshotGrabber::chooseHelperTooltipText(QRect rect) { if (rect.size().isNull()) useNothingSelectedTooltip(); else useRegionSelectedTooltip(); } /** * @internal * @brief Align the tooltip centered at top of screen with the mouse cursor. */ void ScreenshotGrabber::adjustTooltipPosition() { QRect recGL = QGuiApplication::primaryScreen()->virtualGeometry(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) const auto rec = QGuiApplication::screenAt(QCursor::pos())->geometry(); #else const auto rec = qApp->desktop()->screenGeometry(QCursor::pos()); #endif const QRectF ttRect = this->helperToolbox->childrenBoundingRect(); int x = qAbs(recGL.x()) + rec.x() + ((rec.width() - ttRect.width()) / 2); int y = qAbs(recGL.y()) + rec.y(); helperToolbox->setX(x); helperToolbox->setY(y); } void ScreenshotGrabber::reject() { restoreHiddenWindows(); deleteLater(); } QPixmap ScreenshotGrabber::grabScreen() { QScreen* screen = QGuiApplication::primaryScreen(); QRect rec = screen->virtualGeometry(); // Multiply by devicePixelRatio to get actual desktop size return screen->grabWindow(QApplication::desktop()->winId(), rec.x() * pixRatio, rec.y() * pixRatio, rec.width() * pixRatio, rec.height() * pixRatio); } void ScreenshotGrabber::hideVisibleWindows() { foreach (QWidget* w, qApp->topLevelWidgets()) { if (w != window && w->isVisible()) { mHiddenWindows << w; w->setVisible(false); } } mQToxVisible = false; } void ScreenshotGrabber::restoreHiddenWindows() { foreach (QWidget* w, mHiddenWindows) { if (w) w->setVisible(true); } mHiddenWindows.clear(); mQToxVisible = true; } void ScreenshotGrabber::beginRectChooser(QGraphicsSceneMouseEvent* event) { QPointF pos = event->scenePos(); this->chooserRect->setX(pos.x()); this->chooserRect->setY(pos.y()); this->chooserRect->beginResize(event->scenePos()); } qTox/src/widget/tool/screenshotgrabber.h000066400000000000000000000045651415623743500207370ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SCREENSHOTGRABBER_H #define SCREENSHOTGRABBER_H #include #include class QGraphicsSceneMouseEvent; class QGraphicsPixmapItem; class QGraphicsRectItem; class QGraphicsTextItem; class QGraphicsScene; class QGraphicsView; class QKeyEvent; class ScreenGrabberChooserRectItem; class ScreenGrabberOverlayItem; class ToolBoxGraphicsItem; class ScreenshotGrabber : public QObject { Q_OBJECT public: ScreenshotGrabber(); ~ScreenshotGrabber() override; bool eventFilter(QObject* object, QEvent* event) override; void showGrabber(); public slots: void acceptRegion(); void reInit(); signals: void screenshotTaken(const QPixmap& pixmap); void regionChosen(QRect region); void rejected(); private: friend class ScreenGrabberOverlayItem; bool mKeysBlocked; void setupScene(); void useNothingSelectedTooltip(); void useRegionSelectedTooltip(); void chooseHelperTooltipText(QRect rect); void adjustTooltipPosition(); bool handleKeyPress(QKeyEvent* event); void reject(); QPixmap grabScreen(); void hideVisibleWindows(); void restoreHiddenWindows(); void beginRectChooser(QGraphicsSceneMouseEvent* event); private: QPixmap screenGrab; QGraphicsScene* scene; QGraphicsView* window; QGraphicsPixmapItem* screenGrabDisplay; ScreenGrabberOverlayItem* overlay; ScreenGrabberChooserRectItem* chooserRect; ToolBoxGraphicsItem* helperToolbox; QGraphicsTextItem* helperTooltip; qreal pixRatio = 1.0; bool mQToxVisible; QVector> mHiddenWindows; }; #endif // SCREENSHOTGRABBER_H qTox/src/widget/tool/toolboxgraphicsitem.cpp000066400000000000000000000041601415623743500216450ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "toolboxgraphicsitem.h" #include ToolBoxGraphicsItem::ToolBoxGraphicsItem() { this->opacityAnimation = new QPropertyAnimation(this, QByteArrayLiteral("opacity"), this); this->opacityAnimation->setKeyValueAt(0, this->idleOpacity); this->opacityAnimation->setKeyValueAt(1, this->activeOpacity); this->opacityAnimation->setDuration(this->fadeTimeMs); setOpacity(this->activeOpacity); } ToolBoxGraphicsItem::~ToolBoxGraphicsItem() { } void ToolBoxGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event) { startAnimation(QAbstractAnimation::Backward); QGraphicsItemGroup::hoverEnterEvent(event); } void ToolBoxGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { startAnimation(QAbstractAnimation::Forward); QGraphicsItemGroup::hoverLeaveEvent(event); } void ToolBoxGraphicsItem::startAnimation(QAbstractAnimation::Direction direction) { this->opacityAnimation->setDirection(direction); this->opacityAnimation->start(); } void ToolBoxGraphicsItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { painter->save(); painter->setPen(Qt::NoPen); painter->setBrush(QBrush(QColor(0xFF, 0xE2, 0x82))); painter->drawRect(childrenBoundingRect()); painter->restore(); QGraphicsItemGroup::paint(painter, option, widget); } qTox/src/widget/tool/toolboxgraphicsitem.h000066400000000000000000000032301415623743500213070ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TOOLBOXGRAPHICSITEM_HPP #define TOOLBOXGRAPHICSITEM_HPP #include #include #include class ToolBoxGraphicsItem final : public QObject, public QGraphicsItemGroup { Q_OBJECT Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) public: ToolBoxGraphicsItem(); ~ToolBoxGraphicsItem(); virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) final override; protected: virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event) final override; virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) final override; private: void startAnimation(QAbstractAnimation::Direction direction); QPropertyAnimation* opacityAnimation; qreal idleOpacity = 0.0f; qreal activeOpacity = 1.0f; int fadeTimeMs = 300; }; #endif // TOOLBOXGRAPHICSITEM_HPP qTox/src/widget/translator.cpp000066400000000000000000000067141415623743500170020ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "translator.h" #include #include #include #include #include #include #include #include QTranslator* Translator::translator{nullptr}; QVector Translator::callbacks; QMutex Translator::lock; /** * @brief Loads the translations according to the settings or locale. */ void Translator::translate(const QString& localeName) { QMutexLocker locker{&lock}; if (!translator) translator = new QTranslator(); // Load translations QCoreApplication::removeTranslator(translator); QString locale = localeName.isEmpty() ? QLocale::system().name().section('_', 0, 0) : localeName; if (locale != "en") { if (translator->load(locale, ":translations/")) { qDebug() << "Loaded translation" << locale; // system menu translation QTranslator* qtTranslator = new QTranslator(); QString s_locale = "qt_" + locale; QString location = QLibraryInfo::location(QLibraryInfo::TranslationsPath); if (qtTranslator->load(s_locale, location)) { QApplication::installTranslator(qtTranslator); qDebug() << "System translation loaded" << locale; } else { qDebug() << "System translation not loaded" << locale; } } else { qDebug() << "Error loading translation" << locale; } QCoreApplication::installTranslator(translator); } // After the language is changed from RTL to LTR, the layout direction isn't // always restored const QString direction = QApplication::tr("LTR", "Translate this string to the string 'RTL' in" " right-to-left languages (for example Hebrew and" " Arabic) to get proper widget layout"); QGuiApplication::setLayoutDirection(direction == "RTL" ? Qt::RightToLeft : Qt::LeftToRight); for (auto pair : callbacks) pair.second(); } /** * @brief Register a function to be called when the UI needs to be retranslated. * @param f Function, wich will called. * @param owner Widget to retanslate. */ void Translator::registerHandler(const std::function& f, void* owner) { QMutexLocker locker{&lock}; callbacks.push_back({owner, f}); } /** * @brief Unregisters all handlers of an owner. * @param owner Owner to unregister. */ void Translator::unregister(void* owner) { QMutexLocker locker{&lock}; callbacks.erase(std::remove_if(begin(callbacks), end(callbacks), [=](const Callback& c) { return c.first == owner; }), end(callbacks)); } qTox/src/widget/translator.h000066400000000000000000000024271415623743500164440ustar00rootroot00000000000000/* Copyright © 2015-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef TRANSLATOR_H #define TRANSLATOR_H #include #include #include #include class QTranslator; class Translator { public: static void translate(const QString& localeName); static void registerHandler(const std::function&, void* owner); static void unregister(void* owner); private: using Callback = QPair>; static QVector callbacks; static QMutex lock; static QTranslator* translator; }; #endif // TRANSLATOR_H qTox/src/widget/widget.cpp000066400000000000000000002656551415623743500161070ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #include #include #endif #include "circlewidget.h" #include "contentdialog.h" #include "contentlayout.h" #include "friendlistwidget.h" #include "friendwidget.h" #include "groupwidget.h" #include "maskablepixmapwidget.h" #include "splitterrestorer.h" #include "form/groupchatform.h" #include "src/audio/audio.h" #include "src/chatlog/content/filetransferwidget.h" #include "src/core/core.h" #include "src/core/coreav.h" #include "src/core/corefile.h" #include "src/friendlist.h" #include "src/grouplist.h" #include "src/model/chathistory.h" #include "src/model/chatroom/friendchatroom.h" #include "src/model/chatroom/groupchatroom.h" #include "src/model/friend.h" #include "src/model/group.h" #include "src/model/groupinvite.h" #include "src/model/profile/profileinfo.h" #include "src/model/status.h" #include "src/net/updatecheck.h" #include "src/nexus.h" #include "src/persistence/offlinemsgengine.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/platform/timer.h" #include "src/widget/contentdialogmanager.h" #include "src/widget/form/addfriendform.h" #include "src/widget/form/chatform.h" #include "src/widget/form/filesform.h" #include "src/widget/form/groupinviteform.h" #include "src/widget/form/profileform.h" #include "src/widget/form/settingswidget.h" #include "src/widget/gui.h" #include "src/widget/style.h" #include "src/widget/translator.h" #include "tool/removefrienddialog.h" bool toxActivateEventHandler(const QByteArray&) { Widget* widget = Nexus::getDesktopGUI(); if (!widget) { return true; } qDebug() << "Handling [activate] event from other instance"; widget->forceShow(); return true; } namespace { /** * @brief Dangerous way to find out if a path is writable. * @param filepath Path to file which should be deleted. * @return True, if file writeable, false otherwise. */ bool tryRemoveFile(const QString& filepath) { QFile tmp(filepath); bool writable = tmp.open(QIODevice::WriteOnly); tmp.remove(); return writable; } void acceptFileTransfer(const ToxFile& file, const QString& path) { QString filepath; int number = 0; QString suffix = QFileInfo(file.fileName).completeSuffix(); QString base = QFileInfo(file.fileName).baseName(); do { filepath = QString("%1/%2%3.%4") .arg(path, base, number > 0 ? QString(" (%1)").arg(QString::number(number)) : QString(), suffix); ++number; } while (QFileInfo(filepath).exists()); // Do not automatically accept the file-transfer if the path is not writable. // The user can still accept it manually. if (tryRemoveFile(filepath)) { CoreFile* coreFile = Core::getInstance()->getCoreFile(); coreFile->acceptFileRecvRequest(file.friendId, file.fileNum, filepath); } else { qWarning() << "Cannot write to " << filepath; } } } // namespace Widget* Widget::instance{nullptr}; Widget::Widget(IAudioControl& audio, QWidget* parent) : QMainWindow(parent) , icon{nullptr} , trayMenu{nullptr} , ui(new Ui::MainWindow) , activeChatroomWidget{nullptr} , eventFlag(false) , eventIcon(false) , audio(audio) , settings(Settings::getInstance()) { installEventFilter(this); QString locale = settings.getTranslation(); Translator::translate(locale); } void Widget::init() { ui->setupUi(this); QIcon themeIcon = QIcon::fromTheme("qtox"); if (!themeIcon.isNull()) { setWindowIcon(themeIcon); } timer = new QTimer(); timer->start(1000); icon_size = 15; actionShow = new QAction(this); connect(actionShow, &QAction::triggered, this, &Widget::forceShow); // Preparing icons and set their size statusOnline = new QAction(this); statusOnline->setIcon( prepareIcon(Status::getIconPath(Status::Status::Online), icon_size, icon_size)); connect(statusOnline, &QAction::triggered, this, &Widget::setStatusOnline); statusAway = new QAction(this); statusAway->setIcon(prepareIcon(Status::getIconPath(Status::Status::Away), icon_size, icon_size)); connect(statusAway, &QAction::triggered, this, &Widget::setStatusAway); statusBusy = new QAction(this); statusBusy->setIcon(prepareIcon(Status::getIconPath(Status::Status::Busy), icon_size, icon_size)); connect(statusBusy, &QAction::triggered, this, &Widget::setStatusBusy); actionLogout = new QAction(this); actionLogout->setIcon(prepareIcon(":/img/others/logout-icon.svg", icon_size, icon_size)); actionQuit = new QAction(this); #ifndef Q_OS_OSX actionQuit->setMenuRole(QAction::QuitRole); #endif actionQuit->setIcon( prepareIcon(Style::getImagePath("rejectCall/rejectCall.svg"), icon_size, icon_size)); connect(actionQuit, &QAction::triggered, qApp, &QApplication::quit); layout()->setContentsMargins(0, 0, 0, 0); profilePicture = new MaskablePixmapWidget(this, QSize(40, 40), ":/img/avatar_mask.svg"); profilePicture->setPixmap(QPixmap(":/img/contact_dark.svg")); profilePicture->setClickable(true); profilePicture->setObjectName("selfAvatar"); ui->myProfile->insertWidget(0, profilePicture); ui->myProfile->insertSpacing(1, 7); filterMenu = new QMenu(this); filterGroup = new QActionGroup(this); filterDisplayGroup = new QActionGroup(this); filterDisplayName = new QAction(this); filterDisplayName->setCheckable(true); filterDisplayName->setChecked(true); filterDisplayGroup->addAction(filterDisplayName); filterMenu->addAction(filterDisplayName); filterDisplayActivity = new QAction(this); filterDisplayActivity->setCheckable(true); filterDisplayGroup->addAction(filterDisplayActivity); filterMenu->addAction(filterDisplayActivity); settings.getFriendSortingMode() == FriendListWidget::SortingMode::Name ? filterDisplayName->setChecked(true) : filterDisplayActivity->setChecked(true); filterMenu->addSeparator(); filterAllAction = new QAction(this); filterAllAction->setCheckable(true); filterAllAction->setChecked(true); filterGroup->addAction(filterAllAction); filterMenu->addAction(filterAllAction); filterOnlineAction = new QAction(this); filterOnlineAction->setCheckable(true); filterGroup->addAction(filterOnlineAction); filterMenu->addAction(filterOnlineAction); filterOfflineAction = new QAction(this); filterOfflineAction->setCheckable(true); filterGroup->addAction(filterOfflineAction); filterMenu->addAction(filterOfflineAction); filterFriendsAction = new QAction(this); filterFriendsAction->setCheckable(true); filterGroup->addAction(filterFriendsAction); filterMenu->addAction(filterFriendsAction); filterGroupsAction = new QAction(this); filterGroupsAction->setCheckable(true); filterGroup->addAction(filterGroupsAction); filterMenu->addAction(filterGroupsAction); ui->searchContactFilterBox->setMenu(filterMenu); contactListWidget = new FriendListWidget(this, settings.getGroupchatPosition()); connect(contactListWidget, &FriendListWidget::searchCircle, this, &Widget::searchCircle); connect(contactListWidget, &FriendListWidget::connectCircleWidget, this, &Widget::connectCircleWidget); ui->friendList->setWidget(contactListWidget); ui->friendList->setLayoutDirection(Qt::RightToLeft); ui->friendList->setContextMenuPolicy(Qt::CustomContextMenu); ui->statusLabel->setEditable(true); QMenu* statusButtonMenu = new QMenu(ui->statusButton); statusButtonMenu->addAction(statusOnline); statusButtonMenu->addAction(statusAway); statusButtonMenu->addAction(statusBusy); ui->statusButton->setMenu(statusButtonMenu); // disable proportional scaling ui->mainSplitter->setStretchFactor(0, 0); ui->mainSplitter->setStretchFactor(1, 1); onStatusSet(Status::Status::Offline); // Disable some widgets until we're connected to the DHT ui->statusButton->setEnabled(false); Style::setThemeColor(settings.getThemeColor()); filesForm = new FilesForm(); addFriendForm = new AddFriendForm; groupInviteForm = new GroupInviteForm; #if UPDATE_CHECK_ENABLED updateCheck = std::unique_ptr(new UpdateCheck(settings)); connect(updateCheck.get(), &UpdateCheck::updateAvailable, this, &Widget::onUpdateAvailable); #endif settingsWidget = new SettingsWidget(updateCheck.get(), audio, this); #if UPDATE_CHECK_ENABLED updateCheck->checkForUpdate(); #endif core = Nexus::getCore(); CoreFile* coreFile = core->getCoreFile(); Profile* profile = Nexus::getProfile(); profileInfo = new ProfileInfo(core, profile); profileForm = new ProfileForm(profileInfo); // connect logout tray menu action connect(actionLogout, &QAction::triggered, profileForm, &ProfileForm::onLogoutClicked); connect(profile, &Profile::selfAvatarChanged, profileForm, &ProfileForm::onSelfAvatarLoaded); connect(coreFile, &CoreFile::fileReceiveRequested, this, &Widget::onFileReceiveRequested); connect(coreFile, &CoreFile::fileDownloadFinished, filesForm, &FilesForm::onFileDownloadComplete); connect(coreFile, &CoreFile::fileUploadFinished, filesForm, &FilesForm::onFileUploadComplete); connect(ui->addButton, &QPushButton::clicked, this, &Widget::onAddClicked); connect(ui->groupButton, &QPushButton::clicked, this, &Widget::onGroupClicked); connect(ui->transferButton, &QPushButton::clicked, this, &Widget::onTransferClicked); connect(ui->settingsButton, &QPushButton::clicked, this, &Widget::onShowSettings); connect(profilePicture, &MaskablePixmapWidget::clicked, this, &Widget::showProfile); connect(ui->nameLabel, &CroppingLabel::clicked, this, &Widget::showProfile); connect(ui->statusLabel, &CroppingLabel::editFinished, this, &Widget::onStatusMessageChanged); connect(ui->mainSplitter, &QSplitter::splitterMoved, this, &Widget::onSplitterMoved); connect(addFriendForm, &AddFriendForm::friendRequested, this, &Widget::friendRequested); connect(groupInviteForm, &GroupInviteForm::groupCreate, core, &Core::createGroup); connect(timer, &QTimer::timeout, this, &Widget::onUserAwayCheck); connect(timer, &QTimer::timeout, this, &Widget::onEventIconTick); connect(timer, &QTimer::timeout, this, &Widget::onTryCreateTrayIcon); connect(ui->searchContactText, &QLineEdit::textChanged, this, &Widget::searchContacts); connect(filterGroup, &QActionGroup::triggered, this, &Widget::searchContacts); connect(filterDisplayGroup, &QActionGroup::triggered, this, &Widget::changeDisplayMode); connect(ui->friendList, &QWidget::customContextMenuRequested, this, &Widget::friendListContextMenu); connect(coreFile, &CoreFile::fileSendStarted, this, &Widget::dispatchFile); connect(coreFile, &CoreFile::fileReceiveRequested, this, &Widget::dispatchFile); connect(coreFile, &CoreFile::fileTransferAccepted, this, &Widget::dispatchFile); connect(coreFile, &CoreFile::fileTransferCancelled, this, &Widget::dispatchFile); connect(coreFile, &CoreFile::fileTransferFinished, this, &Widget::dispatchFile); connect(coreFile, &CoreFile::fileTransferPaused, this, &Widget::dispatchFile); connect(coreFile, &CoreFile::fileTransferInfo, this, &Widget::dispatchFile); connect(coreFile, &CoreFile::fileTransferRemotePausedUnpaused, this, &Widget::dispatchFileWithBool); connect(coreFile, &CoreFile::fileTransferBrokenUnbroken, this, &Widget::dispatchFileWithBool); connect(coreFile, &CoreFile::fileSendFailed, this, &Widget::dispatchFileSendFailed); // NOTE: We intentionally do not connect the fileUploadFinished and fileDownloadFinished signals // because they are duplicates of fileTransferFinished NOTE: We don't hook up the // fileNameChanged signal since it is only emitted before a fileReceiveRequest. We get the // initial request with the sanitized name so there is no work for us to do // keyboard shortcuts new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close())); new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this, SLOT(previousContact())); new QShortcut(Qt::CTRL + Qt::Key_Tab, this, SLOT(nextContact())); new QShortcut(Qt::CTRL + Qt::Key_PageUp, this, SLOT(previousContact())); new QShortcut(Qt::CTRL + Qt::Key_PageDown, this, SLOT(nextContact())); new QShortcut(Qt::Key_F11, this, SLOT(toggleFullscreen())); #ifdef Q_OS_MAC QMenuBar* globalMenu = Nexus::getInstance().globalMenuBar; QAction* windowMenu = Nexus::getInstance().windowMenu->menuAction(); QAction* viewMenu = Nexus::getInstance().viewMenu->menuAction(); QAction* frontAction = Nexus::getInstance().frontAction; fileMenu = globalMenu->insertMenu(viewMenu, new QMenu(this)); editProfileAction = fileMenu->menu()->addAction(QString()); connect(editProfileAction, &QAction::triggered, this, &Widget::showProfile); changeStatusMenu = fileMenu->menu()->addMenu(QString()); fileMenu->menu()->addAction(changeStatusMenu->menuAction()); changeStatusMenu->addAction(statusOnline); changeStatusMenu->addSeparator(); changeStatusMenu->addAction(statusAway); changeStatusMenu->addAction(statusBusy); fileMenu->menu()->addSeparator(); logoutAction = fileMenu->menu()->addAction(QString()); connect(logoutAction, &QAction::triggered, [this]() { Nexus::getInstance().showLogin(); }); editMenu = globalMenu->insertMenu(viewMenu, new QMenu(this)); editMenu->menu()->addSeparator(); viewMenu->menu()->insertMenu(Nexus::getInstance().fullscreenAction, filterMenu); viewMenu->menu()->insertSeparator(Nexus::getInstance().fullscreenAction); contactMenu = globalMenu->insertMenu(windowMenu, new QMenu(this)); addContactAction = contactMenu->menu()->addAction(QString()); connect(addContactAction, &QAction::triggered, this, &Widget::onAddClicked); nextConversationAction = new QAction(this); Nexus::getInstance().windowMenu->insertAction(frontAction, nextConversationAction); nextConversationAction->setShortcut(QKeySequence::SelectNextPage); connect(nextConversationAction, &QAction::triggered, [this]() { if (ContentDialogManager::getInstance()->current() == QApplication::activeWindow()) ContentDialogManager::getInstance()->current()->cycleContacts(true); else if (QApplication::activeWindow() == this) cycleContacts(true); }); previousConversationAction = new QAction(this); Nexus::getInstance().windowMenu->insertAction(frontAction, previousConversationAction); previousConversationAction->setShortcut(QKeySequence::SelectPreviousPage); connect(previousConversationAction, &QAction::triggered, [this] { if (ContentDialogManager::getInstance()->current() == QApplication::activeWindow()) ContentDialogManager::getInstance()->current()->cycleContacts(false); else if (QApplication::activeWindow() == this) cycleContacts(false); }); windowMenu->menu()->insertSeparator(frontAction); QAction* preferencesAction = viewMenu->menu()->addAction(QString()); preferencesAction->setMenuRole(QAction::PreferencesRole); connect(preferencesAction, &QAction::triggered, this, &Widget::onShowSettings); QAction* aboutAction = viewMenu->menu()->addAction(QString()); aboutAction->setMenuRole(QAction::AboutRole); connect(aboutAction, &QAction::triggered, [this]() { onShowSettings(); settingsWidget->showAbout(); }); QMenu* dockChangeStatusMenu = new QMenu(tr("Status"), this); dockChangeStatusMenu->addAction(statusOnline); statusOnline->setIconVisibleInMenu(true); dockChangeStatusMenu->addSeparator(); dockChangeStatusMenu->addAction(statusAway); dockChangeStatusMenu->addAction(statusBusy); Nexus::getInstance().dockMenu->addAction(dockChangeStatusMenu->menuAction()); connect(this, &Widget::windowStateChanged, &Nexus::getInstance(), &Nexus::onWindowStateChanged); #endif contentLayout = nullptr; onSeparateWindowChanged(settings.getSeparateWindow(), false); ui->addButton->setCheckable(true); ui->groupButton->setCheckable(true); ui->transferButton->setCheckable(true); ui->settingsButton->setCheckable(true); if (contentLayout) { onAddClicked(); } // restore window state restoreGeometry(settings.getWindowGeometry()); restoreState(settings.getWindowState()); SplitterRestorer restorer(ui->mainSplitter); restorer.restore(settings.getSplitterState(), size()); friendRequestsButton = nullptr; groupInvitesButton = nullptr; unreadGroupInvites = 0; connect(addFriendForm, &AddFriendForm::friendRequested, this, &Widget::friendRequestsUpdate); connect(addFriendForm, &AddFriendForm::friendRequestsSeen, this, &Widget::friendRequestsUpdate); connect(addFriendForm, &AddFriendForm::friendRequestAccepted, this, &Widget::friendRequestAccepted); connect(groupInviteForm, &GroupInviteForm::groupInvitesSeen, this, &Widget::groupInvitesClear); connect(groupInviteForm, &GroupInviteForm::groupInviteAccepted, this, &Widget::onGroupInviteAccepted); // settings connect(&settings, &Settings::showSystemTrayChanged, this, &Widget::onSetShowSystemTray); connect(&settings, &Settings::separateWindowChanged, this, &Widget::onSeparateWindowClicked); connect(&settings, &Settings::compactLayoutChanged, contactListWidget, &FriendListWidget::onCompactChanged); connect(&settings, &Settings::groupchatPositionChanged, contactListWidget, &FriendListWidget::onGroupchatPositionChanged); reloadTheme(); updateIcons(); retranslateUi(); Translator::registerHandler(std::bind(&Widget::retranslateUi, this), this); if (!settings.getShowSystemTray()) { show(); } #ifdef Q_OS_MAC Nexus::getInstance().updateWindows(); #endif } bool Widget::eventFilter(QObject* obj, QEvent* event) { QWindowStateChangeEvent* ce = nullptr; Qt::WindowStates state = windowState(); switch (event->type()) { case QEvent::Close: // It's needed if user enable `Close to tray` wasMaximized = state.testFlag(Qt::WindowMaximized); break; case QEvent::WindowStateChange: ce = static_cast(event); if (state.testFlag(Qt::WindowMinimized) && obj) { wasMaximized = ce->oldState().testFlag(Qt::WindowMaximized); } #ifdef Q_OS_MAC emit windowStateChanged(windowState()); #endif break; default: break; } return false; } void Widget::updateIcons() { if (!icon) { return; } const QString assetSuffix = Status::getAssetSuffix(static_cast( ui->statusButton->property("status").toInt())) + (eventIcon ? "_event" : ""); // Some builds of Qt appear to have a bug in icon loading: // QIcon::hasThemeIcon is sometimes unaware that the icon returned // from QIcon::fromTheme was a fallback icon, causing hasThemeIcon to // incorrectly return true. // // In qTox this leads to the tray and window icons using the static qTox logo // icon instead of an icon based on the current presence status. // // This workaround checks for an icon that definitely does not exist to // determine if hasThemeIcon can be trusted. // // On systems with the Qt bug, this workaround will always use our included // icons but user themes will be unable to override them. static bool checkedHasThemeIcon = false; static bool hasThemeIconBug = false; if (!checkedHasThemeIcon) { hasThemeIconBug = QIcon::hasThemeIcon("qtox-asjkdfhawjkeghdfjgh"); checkedHasThemeIcon = true; if (hasThemeIconBug) { qDebug() << "Detected buggy QIcon::hasThemeIcon. Icon overrides from theme will be ignored."; } } QIcon ico; if (!hasThemeIconBug && QIcon::hasThemeIcon("qtox-" + assetSuffix)) { ico = QIcon::fromTheme("qtox-" + assetSuffix); } else { QString color = settings.getLightTrayIcon() ? "light" : "dark"; QString path = ":/img/taskbar/" + color + "/taskbar_" + assetSuffix + ".svg"; QSvgRenderer renderer(path); // Prepare a QImage with desired characteritisc QImage image = QImage(250, 250, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter(&image); renderer.render(&painter); ico = QIcon(QPixmap::fromImage(image)); } setWindowIcon(ico); if (icon) { icon->setIcon(ico); } } Widget::~Widget() { QWidgetList windowList = QApplication::topLevelWidgets(); for (QWidget* window : windowList) { if (window != this) { window->close(); } } Translator::unregister(this); if (icon) { icon->hide(); } for (Group* g : GroupList::getAllGroups()) { removeGroup(g, true); } for (Friend* f : FriendList::getAllFriends()) { removeFriend(f, true); } for (auto form : chatForms) { delete form; } delete profileForm; delete profileInfo; delete addFriendForm; delete groupInviteForm; delete filesForm; delete timer; delete contentLayout; delete settingsWidget; FriendList::clear(); GroupList::clear(); delete trayMenu; delete ui; instance = nullptr; } /** * @brief Switches to the About settings page. */ void Widget::showUpdateDownloadProgress() { onShowSettings(); settingsWidget->showAbout(); } void Widget::moveEvent(QMoveEvent* event) { if (event->type() == QEvent::Move) { saveWindowGeometry(); saveSplitterGeometry(); } QWidget::moveEvent(event); } void Widget::closeEvent(QCloseEvent* event) { if (settings.getShowSystemTray() && settings.getCloseToTray()) { QWidget::closeEvent(event); } else { if (autoAwayActive) { emit statusSet(Status::Status::Online); autoAwayActive = false; } saveWindowGeometry(); saveSplitterGeometry(); QWidget::closeEvent(event); qApp->quit(); } } void Widget::changeEvent(QEvent* event) { if (event->type() == QEvent::WindowStateChange) { if (isMinimized() && settings.getShowSystemTray() && settings.getMinimizeToTray()) { this->hide(); } } } void Widget::resizeEvent(QResizeEvent* event) { saveWindowGeometry(); QMainWindow::resizeEvent(event); } QString Widget::getUsername() { return core->getUsername(); } void Widget::onSelfAvatarLoaded(const QPixmap& pic) { profilePicture->setPixmap(pic); } void Widget::onCoreChanged(Core& core) { connect(&core, &Core::connected, this, &Widget::onConnected); connect(&core, &Core::disconnected, this, &Widget::onDisconnected); connect(&core, &Core::statusSet, this, &Widget::onStatusSet); connect(&core, &Core::usernameSet, this, &Widget::setUsername); connect(&core, &Core::statusMessageSet, this, &Widget::setStatusMessage); connect(&core, &Core::friendAdded, this, &Widget::addFriend); connect(&core, &Core::failedToAddFriend, this, &Widget::addFriendFailed); connect(&core, &Core::friendUsernameChanged, this, &Widget::onFriendUsernameChanged); connect(&core, &Core::friendStatusChanged, this, &Widget::onFriendStatusChanged); connect(&core, &Core::friendStatusMessageChanged, this, &Widget::onFriendStatusMessageChanged); connect(&core, &Core::friendRequestReceived, this, &Widget::onFriendRequestReceived); connect(&core, &Core::friendMessageReceived, this, &Widget::onFriendMessageReceived); connect(&core, &Core::receiptRecieved, this, &Widget::onReceiptReceived); connect(&core, &Core::groupInviteReceived, this, &Widget::onGroupInviteReceived); connect(&core, &Core::groupMessageReceived, this, &Widget::onGroupMessageReceived); connect(&core, &Core::groupPeerlistChanged, this, &Widget::onGroupPeerlistChanged); connect(&core, &Core::groupPeerNameChanged, this, &Widget::onGroupPeerNameChanged); connect(&core, &Core::groupTitleChanged, this, &Widget::onGroupTitleChanged); connect(&core, &Core::groupPeerAudioPlaying, this, &Widget::onGroupPeerAudioPlaying); connect(&core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated); connect(&core, &Core::groupJoined, this, &Widget::onGroupJoined); connect(&core, &Core::friendTypingChanged, this, &Widget::onFriendTypingChanged); connect(&core, &Core::groupSentFailed, this, &Widget::onGroupSendFailed); connect(&core, &Core::usernameSet, this, &Widget::refreshPeerListsLocal); connect(this, &Widget::statusSet, &core, &Core::setStatus); connect(this, &Widget::friendRequested, &core, &Core::requestFriendship); connect(this, &Widget::friendRequestAccepted, &core, &Core::acceptFriendRequest); connect(this, &Widget::changeGroupTitle, &core, &Core::changeGroupTitle); sharedMessageProcessorParams.setPublicKey(core.getSelfPublicKey().toString()); } void Widget::onConnected() { ui->statusButton->setEnabled(true); emit core->statusSet(core->getStatus()); } void Widget::onDisconnected() { ui->statusButton->setEnabled(false); emit core->statusSet(Status::Status::Offline); } void Widget::onFailedToStartCore() { QMessageBox critical(this); critical.setText(tr( "toxcore failed to start, the application will terminate after you close this message.")); critical.setIcon(QMessageBox::Critical); critical.exec(); qApp->exit(EXIT_FAILURE); } void Widget::onBadProxyCore() { settings.setProxyType(Settings::ProxyType::ptNone); QMessageBox critical(this); critical.setText(tr("toxcore failed to start with your proxy settings. " "qTox cannot run; please modify your " "settings and restart.", "popup text")); critical.setIcon(QMessageBox::Critical); critical.exec(); onShowSettings(); } void Widget::onStatusSet(Status::Status status) { ui->statusButton->setProperty("status", static_cast(status)); ui->statusButton->setIcon(prepareIcon(getIconPath(status), icon_size, icon_size)); updateIcons(); } void Widget::onSeparateWindowClicked(bool separate) { onSeparateWindowChanged(separate, true); } void Widget::onSeparateWindowChanged(bool separate, bool clicked) { if (!separate) { QWindowList windowList = QGuiApplication::topLevelWindows(); for (QWindow* window : windowList) { if (window->objectName() == "detachedWindow") { window->close(); } } QWidget* contentWidget = new QWidget(this); contentWidget->setObjectName("contentWidget"); contentLayout = new ContentLayout(contentWidget); ui->mainSplitter->addWidget(contentWidget); setMinimumWidth(775); SplitterRestorer restorer(ui->mainSplitter); restorer.restore(settings.getSplitterState(), size()); onShowSettings(); } else { int width = ui->friendList->size().width(); QSize size; QPoint pos; if (contentLayout) { pos = mapToGlobal(ui->mainSplitter->widget(1)->pos()); size = ui->mainSplitter->widget(1)->size(); } if (contentLayout) { contentLayout->clear(); contentLayout->parentWidget()->setParent(nullptr); // Remove from splitter. contentLayout->parentWidget()->hide(); contentLayout->parentWidget()->deleteLater(); contentLayout->deleteLater(); contentLayout = nullptr; } setMinimumWidth(ui->tooliconsZone->sizeHint().width()); if (clicked) { showNormal(); resize(width, height()); if (settingsWidget) { ContentLayout* contentLayout = createContentDialog((DialogType::SettingDialog)); contentLayout->parentWidget()->resize(size); contentLayout->parentWidget()->move(pos); settingsWidget->show(contentLayout); setActiveToolMenuButton(ActiveToolMenuButton::None); } } setWindowTitle(QString()); setActiveToolMenuButton(ActiveToolMenuButton::None); } } void Widget::setWindowTitle(const QString& title) { if (title.isEmpty()) { QMainWindow::setWindowTitle(QApplication::applicationName()); } else { QString tmp = title; /// <[^>]*> Regexp to remove HTML tags, in case someone used them in title QMainWindow::setWindowTitle(QApplication::applicationName() + QStringLiteral(" - ") + tmp.remove(QRegExp("<[^>]*>"))); } } void Widget::forceShow() { hide(); // Workaround to force minimized window to be restored show(); activateWindow(); } void Widget::onAddClicked() { if (settings.getSeparateWindow()) { if (!addFriendForm->isShown()) { addFriendForm->show(createContentDialog(DialogType::AddDialog)); } setActiveToolMenuButton(ActiveToolMenuButton::None); } else { hideMainForms(nullptr); addFriendForm->show(contentLayout); setWindowTitle(fromDialogType(DialogType::AddDialog)); setActiveToolMenuButton(ActiveToolMenuButton::AddButton); } } void Widget::onGroupClicked() { if (settings.getSeparateWindow()) { if (!groupInviteForm->isShown()) { groupInviteForm->show(createContentDialog(DialogType::GroupDialog)); } setActiveToolMenuButton(ActiveToolMenuButton::None); } else { hideMainForms(nullptr); groupInviteForm->show(contentLayout); setWindowTitle(fromDialogType(DialogType::GroupDialog)); setActiveToolMenuButton(ActiveToolMenuButton::GroupButton); } } void Widget::onTransferClicked() { if (settings.getSeparateWindow()) { if (!filesForm->isShown()) { filesForm->show(createContentDialog(DialogType::TransferDialog)); } setActiveToolMenuButton(ActiveToolMenuButton::None); } else { hideMainForms(nullptr); filesForm->show(contentLayout); setWindowTitle(fromDialogType(DialogType::TransferDialog)); setActiveToolMenuButton(ActiveToolMenuButton::TransferButton); } } void Widget::confirmExecutableOpen(const QFileInfo& file) { static const QStringList dangerousExtensions = {"app", "bat", "com", "cpl", "dmg", "exe", "hta", "jar", "js", "jse", "lnk", "msc", "msh", "msh1", "msh1xml", "msh2", "msh2xml", "mshxml", "msi", "msp", "pif", "ps1", "ps1xml", "ps2", "ps2xml", "psc1", "psc2", "py", "reg", "scf", "sh", "src", "vb", "vbe", "vbs", "ws", "wsc", "wsf", "wsh"}; if (dangerousExtensions.contains(file.suffix())) { bool answer = GUI::askQuestion(tr("Executable file", "popup title"), tr("You have asked qTox to open an executable file. " "Executable files can potentially damage your computer. " "Are you sure want to open this file?", "popup text"), false, true); if (!answer) { return; } // The user wants to run this file, so make it executable and run it QFile(file.filePath()) .setPermissions(file.permissions() | QFile::ExeOwner | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther); } QDesktopServices::openUrl(QUrl::fromLocalFile(file.filePath())); } void Widget::onIconClick(QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) { if (isHidden() || isMinimized()) { if (wasMaximized) { showMaximized(); } else { showNormal(); } activateWindow(); } else if (!isActiveWindow()) { activateWindow(); } else { wasMaximized = isMaximized(); hide(); } } else if (reason == QSystemTrayIcon::Unknown) { if (isHidden()) { forceShow(); } } } void Widget::onShowSettings() { if (settings.getSeparateWindow()) { if (!settingsWidget->isShown()) { settingsWidget->show(createContentDialog(DialogType::SettingDialog)); } setActiveToolMenuButton(ActiveToolMenuButton::None); } else { hideMainForms(nullptr); settingsWidget->show(contentLayout); setWindowTitle(fromDialogType(DialogType::SettingDialog)); setActiveToolMenuButton(ActiveToolMenuButton::SettingButton); } } void Widget::showProfile() // onAvatarClicked, onUsernameClicked { if (settings.getSeparateWindow()) { if (!profileForm->isShown()) { profileForm->show(createContentDialog(DialogType::ProfileDialog)); } setActiveToolMenuButton(ActiveToolMenuButton::None); } else { hideMainForms(nullptr); profileForm->show(contentLayout); setWindowTitle(fromDialogType(DialogType::ProfileDialog)); setActiveToolMenuButton(ActiveToolMenuButton::None); } } void Widget::hideMainForms(GenericChatroomWidget* chatroomWidget) { setActiveToolMenuButton(ActiveToolMenuButton::None); if (contentLayout != nullptr) { contentLayout->clear(); } if (activeChatroomWidget != nullptr) { activeChatroomWidget->setAsInactiveChatroom(); } activeChatroomWidget = chatroomWidget; } void Widget::setUsername(const QString& username) { if (username.isEmpty()) { ui->nameLabel->setText(tr("Your name")); ui->nameLabel->setToolTip(tr("Your name")); } else { ui->nameLabel->setText(username); ui->nameLabel->setToolTip( Qt::convertFromPlainText(username, Qt::WhiteSpaceNormal)); // for overlength names } sharedMessageProcessorParams.onUserNameSet(username); } void Widget::onStatusMessageChanged(const QString& newStatusMessage) { // Keep old status message until Core tells us to set it. core->setStatusMessage(newStatusMessage); } void Widget::setStatusMessage(const QString& statusMessage) { ui->statusLabel->setText(statusMessage); // escape HTML from tooltips and preserve newlines // TODO: move newspace preservance to a generic function ui->statusLabel->setToolTip("

" + statusMessage.toHtmlEscaped() + "

"); } /** * @brief Plays a sound via the audioNotification AudioSink * @param sound Sound to play * @param loop if true, loop the sound until onStopNotification() is called */ void Widget::playNotificationSound(IAudioSink::Sound sound, bool loop) { if (!settings.getAudioOutDevEnabled()) { // don't try to play sounds if audio is disabled return; } if (audioNotification == nullptr) { audioNotification = std::unique_ptr(audio.makeSink()); if (audioNotification == nullptr) { qDebug() << "Failed to allocate AudioSink"; return; } } audioNotification->connectTo_finishedPlaying(this, [this](){ cleanupNotificationSound(); }); audioNotification->playMono16Sound(sound); if (loop) { audioNotification->startLoop(); } } void Widget::cleanupNotificationSound() { audioNotification.reset(); } void Widget::incomingNotification(uint32_t friendnumber) { const auto& friendId = FriendList::id2Key(friendnumber); newFriendMessageAlert(friendId, {}, false); // loop until call answered or rejected playNotificationSound(IAudioSink::Sound::IncomingCall, true); } void Widget::outgoingNotification() { // loop until call answered or rejected playNotificationSound(IAudioSink::Sound::OutgoingCall, true); } void Widget::onCallEnd() { playNotificationSound(IAudioSink::Sound::CallEnd); } /** * @brief Widget::onStopNotification Stop the notification sound. */ void Widget::onStopNotification() { audioNotification.reset(); } /** * @brief Dispatches file to the appropriate chatlog and accepts the transfer if necessary */ void Widget::dispatchFile(ToxFile file) { const auto& friendId = FriendList::id2Key(file.friendId); Friend* f = FriendList::findFriend(friendId); if (!f) { return; } auto pk = f->getPublicKey(); if (file.status == ToxFile::INITIALIZING && file.direction == ToxFile::RECEIVING) { auto sender = (file.direction == ToxFile::SENDING) ? Core::getInstance()->getSelfPublicKey() : pk; const Settings& settings = Settings::getInstance(); QString autoAcceptDir = settings.getAutoAcceptDir(f->getPublicKey()); if (autoAcceptDir.isEmpty() && settings.getAutoSaveEnabled()) { autoAcceptDir = settings.getGlobalAutoAcceptDir(); } auto maxAutoAcceptSize = settings.getMaxAutoAcceptSize(); bool autoAcceptSizeCheckPassed = maxAutoAcceptSize == 0 || maxAutoAcceptSize >= file.filesize; if (!autoAcceptDir.isEmpty() && autoAcceptSizeCheckPassed) { acceptFileTransfer(file, autoAcceptDir); } } const auto senderPk = (file.direction == ToxFile::SENDING) ? core->getSelfPublicKey() : pk; friendChatLogs[pk]->onFileUpdated(senderPk, file); } void Widget::dispatchFileWithBool(ToxFile file, bool) { dispatchFile(file); } void Widget::dispatchFileSendFailed(uint32_t friendId, const QString& fileName) { const auto& friendPk = FriendList::id2Key(friendId); auto chatForm = chatForms.find(friendPk); if (chatForm == chatForms.end()) { return; } chatForm.value()->addSystemInfoMessage(tr("Failed to send file \"%1\"").arg(fileName), ChatMessage::ERROR, QDateTime::currentDateTime()); } void Widget::onRejectCall(uint32_t friendId) { CoreAV* const av = core->getAv(); av->cancelCall(friendId); } void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk) { settings.updateFriendAddress(friendPk.toString()); Friend* newfriend = FriendList::addFriend(friendId, friendPk); auto dialogManager = ContentDialogManager::getInstance(); auto rawChatroom = new FriendChatroom(newfriend, dialogManager); std::shared_ptr chatroom(rawChatroom); const auto compact = settings.getCompactLayout(); auto widget = new FriendWidget(chatroom, compact); connectFriendWidget(*widget); auto history = Nexus::getProfile()->getHistory(); auto messageProcessor = MessageProcessor(sharedMessageProcessorParams); auto friendMessageDispatcher = std::make_shared(*newfriend, std::move(messageProcessor), *core); // Note: We do not have to connect the message dispatcher signals since // ChatHistory hooks them up in a very specific order auto chatHistory = std::make_shared(*newfriend, history, *core, Settings::getInstance(), *friendMessageDispatcher); auto friendForm = new ChatForm(newfriend, *chatHistory, *friendMessageDispatcher); connect(friendForm, &ChatForm::updateFriendActivity, this, &Widget::updateFriendActivity); friendMessageDispatchers[friendPk] = friendMessageDispatcher; friendChatLogs[friendPk] = chatHistory; friendChatrooms[friendPk] = chatroom; friendWidgets[friendPk] = widget; chatForms[friendPk] = friendForm; const auto activityTime = settings.getFriendActivity(friendPk); const auto chatTime = friendForm->getLatestTime(); if (chatTime > activityTime && chatTime.isValid()) { settings.setFriendActivity(friendPk, chatTime); } contactListWidget->addFriendWidget(widget, Status::Status::Offline, settings.getFriendCircleID(friendPk)); auto notifyReceivedCallback = [this, friendPk](const ToxPk& author, const Message& message) { auto isTargeted = std::any_of(message.metadata.begin(), message.metadata.end(), [](MessageMetadata metadata) { return metadata.type == MessageMetadataType::selfMention; }); newFriendMessageAlert(friendPk, message.content); }; auto notifyReceivedConnection = connect(friendMessageDispatcher.get(), &IMessageDispatcher::messageReceived, notifyReceivedCallback); friendAlertConnections.insert(friendPk, notifyReceivedConnection); connect(newfriend, &Friend::aliasChanged, this, &Widget::onFriendAliasChanged); connect(newfriend, &Friend::displayedNameChanged, this, &Widget::onFriendDisplayedNameChanged); connect(friendForm, &ChatForm::incomingNotification, this, &Widget::incomingNotification); connect(friendForm, &ChatForm::outgoingNotification, this, &Widget::outgoingNotification); connect(friendForm, &ChatForm::stopNotification, this, &Widget::onStopNotification); connect(friendForm, &ChatForm::endCallNotification, this, &Widget::onCallEnd); connect(friendForm, &ChatForm::rejectCall, this, &Widget::onRejectCall); connect(widget, &FriendWidget::newWindowOpened, this, &Widget::openNewDialog); connect(widget, &FriendWidget::chatroomWidgetClicked, this, &Widget::onChatroomWidgetClicked); connect(widget, &FriendWidget::chatroomWidgetClicked, friendForm, &ChatForm::focusInput); connect(widget, &FriendWidget::friendHistoryRemoved, friendForm, &ChatForm::clearChatArea); connect(widget, &FriendWidget::copyFriendIdToClipboard, this, &Widget::copyFriendIdToClipboard); connect(widget, &FriendWidget::contextMenuCalled, widget, &FriendWidget::onContextMenuCalled); connect(widget, SIGNAL(removeFriend(const ToxPk&)), this, SLOT(removeFriend(const ToxPk&))); Profile* profile = Nexus::getProfile(); connect(profile, &Profile::friendAvatarSet, widget, &FriendWidget::onAvatarSet); connect(profile, &Profile::friendAvatarRemoved, widget, &FriendWidget::onAvatarRemoved); // Try to get the avatar from the cache QPixmap avatar = Nexus::getProfile()->loadAvatar(friendPk); if (!avatar.isNull()) { friendForm->onAvatarChanged(friendPk, avatar); widget->onAvatarSet(friendPk, avatar); } FilterCriteria filter = getFilterCriteria(); widget->search(ui->searchContactText->text(), filterOffline(filter)); } void Widget::addFriendFailed(const ToxPk&, const QString& errorInfo) { QString info = QString(tr("Couldn't request friendship")); if (!errorInfo.isEmpty()) { info = info + QStringLiteral(": ") + errorInfo; } QMessageBox::critical(nullptr, "Error", info); } void Widget::onFriendStatusChanged(int friendId, Status::Status status) { const auto& friendPk = FriendList::id2Key(friendId); Friend* f = FriendList::findFriend(friendPk); if (!f) { return; } bool isActualChange = f->getStatus() != status; FriendWidget* widget = friendWidgets[f->getPublicKey()]; if (isActualChange) { if (!Status::isOnline(f->getStatus())) { contactListWidget->moveWidget(widget, Status::Status::Online); } else if (status == Status::Status::Offline) { contactListWidget->moveWidget(widget, Status::Status::Offline); } } f->setStatus(status); widget->updateStatusLight(); if (widget->isActive()) { setWindowTitle(widget->getTitle()); } ContentDialogManager::getInstance()->updateFriendStatus(friendPk); } void Widget::onFriendStatusMessageChanged(int friendId, const QString& message) { const auto& friendPk = FriendList::id2Key(friendId); Friend* f = FriendList::findFriend(friendPk); if (!f) { return; } QString str = message; str.replace('\n', ' ').remove('\r').remove(QChar('\0')); f->setStatusMessage(str); friendWidgets[friendPk]->setStatusMsg(str); chatForms[friendPk]->setStatusMessage(str); } void Widget::onFriendDisplayedNameChanged(const QString& displayed) { Friend* f = qobject_cast(sender()); const auto& friendPk = f->getPublicKey(); for (Group* g : GroupList::getAllGroups()) { if (g->getPeerList().contains(friendPk)) { g->updateUsername(friendPk, displayed); } } FriendWidget* friendWidget = friendWidgets[f->getPublicKey()]; if (friendWidget->isActive()) { GUI::setWindowTitle(displayed); } } void Widget::onFriendUsernameChanged(int friendId, const QString& username) { const auto& friendPk = FriendList::id2Key(friendId); Friend* f = FriendList::findFriend(friendPk); if (!f) { return; } QString str = username; str.replace('\n', ' ').remove('\r').remove(QChar('\0')); f->setName(str); } void Widget::onFriendAliasChanged(const ToxPk& friendId, const QString& alias) { Friend* f = qobject_cast(sender()); // TODO(sudden6): don't update the contact list here, make it update itself FriendWidget* friendWidget = friendWidgets[friendId]; Status::Status status = f->getStatus(); contactListWidget->moveWidget(friendWidget, status); FilterCriteria criteria = getFilterCriteria(); bool filter = status == Status::Status::Offline ? filterOffline(criteria) : filterOnline(criteria); friendWidget->searchName(ui->searchContactText->text(), filter); settings.setFriendAlias(friendId, alias); settings.savePersonal(); } void Widget::onChatroomWidgetClicked(GenericChatroomWidget* widget) { openDialog(widget, /* newWindow = */ false); } void Widget::openNewDialog(GenericChatroomWidget* widget) { openDialog(widget, /* newWindow = */ true); } void Widget::openDialog(GenericChatroomWidget* widget, bool newWindow) { widget->resetEventFlags(); widget->updateStatusLight(); GenericChatForm* form; GroupId id; const Friend* frnd = widget->getFriend(); const Group* group = widget->getGroup(); if (frnd) { form = chatForms[frnd->getPublicKey()]; } else { id = group->getPersistentId(); form = groupChatForms[id].data(); } bool chatFormIsSet; ContentDialogManager::getInstance()->focusContact(id); chatFormIsSet = ContentDialogManager::getInstance()->contactWidgetExists(id); if ((chatFormIsSet || form->isVisible()) && !newWindow) { return; } if (settings.getSeparateWindow() || newWindow) { ContentDialog* dialog = nullptr; if (!settings.getDontGroupWindows() && !newWindow) { dialog = ContentDialogManager::getInstance()->current(); } if (dialog == nullptr) { dialog = createContentDialog(); } dialog->show(); if (frnd) { addFriendDialog(frnd, dialog); } else { Group* group = widget->getGroup(); addGroupDialog(group, dialog); } dialog->raise(); dialog->activateWindow(); } else { hideMainForms(widget); if (frnd) { chatForms[frnd->getPublicKey()]->show(contentLayout); } else { groupChatForms[group->getPersistentId()]->show(contentLayout); } widget->setAsActiveChatroom(); setWindowTitle(widget->getTitle()); } } void Widget::onFriendMessageReceived(uint32_t friendnumber, const QString& message, bool isAction) { const auto& friendId = FriendList::id2Key(friendnumber); Friend* f = FriendList::findFriend(friendId); if (!f) { return; } friendMessageDispatchers[f->getPublicKey()]->onMessageReceived(isAction, message); } void Widget::onReceiptReceived(int friendId, ReceiptNum receipt) { const auto& friendKey = FriendList::id2Key(friendId); Friend* f = FriendList::findFriend(friendKey); if (!f) { return; } friendMessageDispatchers[f->getPublicKey()]->onReceiptReceived(receipt); } void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog) { uint32_t friendId = frnd->getId(); const ToxPk& friendPk = frnd->getPublicKey(); ContentDialog* contentDialog = ContentDialogManager::getInstance()->getFriendDialog(friendPk); bool isSeparate = settings.getSeparateWindow(); FriendWidget* widget = friendWidgets[friendPk]; bool isCurrent = activeChatroomWidget == widget; if (!contentDialog && !isSeparate && isCurrent) { onAddClicked(); } auto form = chatForms[friendPk]; auto chatroom = friendChatrooms[friendPk]; FriendWidget* friendWidget = ContentDialogManager::getInstance()->addFriendToDialog(dialog, chatroom, form); friendWidget->setStatusMsg(widget->getStatusMsg()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) auto widgetRemoveFriend = QOverload::of(&Widget::removeFriend); #else auto widgetRemoveFriend = static_cast(&Widget::removeFriend); #endif connect(friendWidget, &FriendWidget::removeFriend, this, widgetRemoveFriend); connect(friendWidget, &FriendWidget::middleMouseClicked, dialog, [=]() { dialog->removeFriend(friendPk); }); connect(friendWidget, &FriendWidget::copyFriendIdToClipboard, this, &Widget::copyFriendIdToClipboard); connect(friendWidget, &FriendWidget::newWindowOpened, this, &Widget::openNewDialog); // Signal transmission from the created `friendWidget` (which shown in // ContentDialog) to the `widget` (which shown in main widget) // FIXME: emit should be removed connect(friendWidget, &FriendWidget::contextMenuCalled, widget, [=](QContextMenuEvent* event) { emit widget->contextMenuCalled(event); }); connect(friendWidget, &FriendWidget::chatroomWidgetClicked, [=](GenericChatroomWidget* w) { Q_UNUSED(w); emit widget->chatroomWidgetClicked(widget); }); connect(friendWidget, &FriendWidget::newWindowOpened, [=](GenericChatroomWidget* w) { Q_UNUSED(w); emit widget->newWindowOpened(widget); }); // FIXME: emit should be removed emit widget->chatroomWidgetClicked(widget); Profile* profile = Nexus::getProfile(); connect(profile, &Profile::friendAvatarSet, friendWidget, &FriendWidget::onAvatarSet); connect(profile, &Profile::friendAvatarRemoved, friendWidget, &FriendWidget::onAvatarRemoved); QPixmap avatar = Nexus::getProfile()->loadAvatar(frnd->getPublicKey()); if (!avatar.isNull()) { friendWidget->onAvatarSet(frnd->getPublicKey(), avatar); } } void Widget::addGroupDialog(Group* group, ContentDialog* dialog) { const GroupId& groupId = group->getPersistentId(); ContentDialog* groupDialog = ContentDialogManager::getInstance()->getGroupDialog(groupId); bool separated = settings.getSeparateWindow(); GroupWidget* widget = groupWidgets[groupId]; bool isCurrentWindow = activeChatroomWidget == widget; if (!groupDialog && !separated && isCurrentWindow) { onAddClicked(); } auto chatForm = groupChatForms[groupId].data(); auto chatroom = groupChatrooms[groupId]; auto groupWidget = ContentDialogManager::getInstance()->addGroupToDialog(dialog, chatroom, chatForm); #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) auto removeGroup = QOverload::of(&Widget::removeGroup); #else auto removeGroup = static_cast(&Widget::removeGroup); #endif connect(groupWidget, &GroupWidget::removeGroup, this, removeGroup); connect(groupWidget, &GroupWidget::chatroomWidgetClicked, chatForm, &GroupChatForm::focusInput); connect(groupWidget, &GroupWidget::middleMouseClicked, dialog, [=]() { dialog->removeGroup(groupId); }); connect(groupWidget, &GroupWidget::chatroomWidgetClicked, chatForm, &ChatForm::focusInput); connect(groupWidget, &GroupWidget::newWindowOpened, this, &Widget::openNewDialog); // Signal transmission from the created `groupWidget` (which shown in // ContentDialog) to the `widget` (which shown in main widget) // FIXME: emit should be removed connect(groupWidget, &GroupWidget::chatroomWidgetClicked, [=](GenericChatroomWidget* w) { Q_UNUSED(w); emit widget->chatroomWidgetClicked(widget); }); connect(groupWidget, &GroupWidget::newWindowOpened, [=](GenericChatroomWidget* w) { Q_UNUSED(w); emit widget->newWindowOpened(widget); }); // FIXME: emit should be removed emit widget->chatroomWidgetClicked(widget); } bool Widget::newFriendMessageAlert(const ToxPk& friendId, const QString& text, bool sound, bool file) { bool hasActive; QWidget* currentWindow; ContentDialog* contentDialog = ContentDialogManager::getInstance()->getFriendDialog(friendId); Friend* f = FriendList::findFriend(friendId); if (contentDialog != nullptr) { currentWindow = contentDialog->window(); hasActive = ContentDialogManager::getInstance()->isContactActive(friendId); } else { if (settings.getSeparateWindow() && settings.getShowWindow()) { if (settings.getDontGroupWindows()) { contentDialog = createContentDialog(); } else { contentDialog = ContentDialogManager::getInstance()->current(); if (!contentDialog) { contentDialog = createContentDialog(); } } addFriendDialog(f, contentDialog); currentWindow = contentDialog->window(); hasActive = ContentDialogManager::getInstance()->isContactActive(friendId); } else { currentWindow = window(); FriendWidget* widget = friendWidgets[friendId]; hasActive = widget == activeChatroomWidget; } } if (newMessageAlert(currentWindow, hasActive, sound)) { FriendWidget* widget = friendWidgets[friendId]; f->setEventFlag(true); widget->updateStatusLight(); ui->friendList->trackWidget(widget); #if DESKTOP_NOTIFICATIONS if (settings.getNotifyHide()) { notifier.notifyMessageSimple(file ? DesktopNotify::MessageType::FRIEND_FILE : DesktopNotify::MessageType::FRIEND); } else { QString title = f->getDisplayedName(); if (file) { title += " - " + tr("File sent"); } notifier.notifyMessagePixmap(title, text, Nexus::getProfile()->loadAvatar(f->getPublicKey())); } #endif if (contentDialog == nullptr) { if (hasActive) { setWindowTitle(widget->getTitle()); } } else { ContentDialogManager::getInstance()->updateFriendStatus(friendId); } return true; } return false; } bool Widget::newGroupMessageAlert(const GroupId& groupId, const ToxPk& authorPk, const QString& message, bool notify) { bool hasActive; QWidget* currentWindow; ContentDialog* contentDialog = ContentDialogManager::getInstance()->getGroupDialog(groupId); Group* g = GroupList::findGroup(groupId); GroupWidget* widget = groupWidgets[groupId]; if (contentDialog != nullptr) { currentWindow = contentDialog->window(); hasActive = ContentDialogManager::getInstance()->isContactActive(groupId); } else { currentWindow = window(); hasActive = widget == activeChatroomWidget; } if (!newMessageAlert(currentWindow, hasActive, true, notify)) { return false; } g->setEventFlag(true); widget->updateStatusLight(); #if DESKTOP_NOTIFICATIONS if (settings.getNotifyHide()) { notifier.notifyMessageSimple(DesktopNotify::MessageType::GROUP); } else { Friend* f = FriendList::findFriend(authorPk); QString title = g->getPeerList().value(authorPk) + " (" + g->getDisplayedName() + ")"; if (!f) { notifier.notifyMessage(title, message); } else { notifier.notifyMessagePixmap(title, message, Nexus::getProfile()->loadAvatar(f->getPublicKey())); } } #endif if (contentDialog == nullptr) { if (hasActive) { setWindowTitle(widget->getTitle()); } } else { ContentDialogManager::getInstance()->updateGroupStatus(groupId); } return true; } QString Widget::fromDialogType(DialogType type) { switch (type) { case DialogType::AddDialog: return tr("Add friend", "title of the window"); case DialogType::GroupDialog: return tr("Group invites", "title of the window"); case DialogType::TransferDialog: return tr("File transfers", "title of the window"); case DialogType::SettingDialog: return tr("Settings", "title of the window"); case DialogType::ProfileDialog: return tr("My profile", "title of the window"); } assert(false); return QString(); } bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool sound, bool notify) { bool inactiveWindow = isMinimized() || !currentWindow->isActiveWindow(); if (!inactiveWindow && isActive) { return false; } if (notify) { if (settings.getShowWindow()) { currentWindow->show(); } if (settings.getNotify()) { if (inactiveWindow) { #if DESKTOP_NOTIFICATIONS if (!settings.getDesktopNotify()) { QApplication::alert(currentWindow); } #else QApplication::alert(currentWindow); #endif eventFlag = true; } bool isBusy = core->getStatus() == Status::Status::Busy; bool busySound = settings.getBusySound(); bool notifySound = settings.getNotifySound(); if (notifySound && sound && (!isBusy || busySound)) { playNotificationSound(IAudioSink::Sound::NewMessage); } } } return true; } void Widget::onFriendRequestReceived(const ToxPk& friendPk, const QString& message) { if (addFriendForm->addFriendRequest(friendPk.toString(), message)) { friendRequestsUpdate(); newMessageAlert(window(), isActiveWindow(), true, true); #if DESKTOP_NOTIFICATIONS if (settings.getNotifyHide()) { notifier.notifyMessageSimple(DesktopNotify::MessageType::FRIEND_REQUEST); } else { notifier.notifyMessage(friendPk.toString() + tr(" sent you a friend request."), message); } #endif } } void Widget::onFileReceiveRequested(const ToxFile& file) { const ToxPk& friendPk = FriendList::id2Key(file.friendId); newFriendMessageAlert(friendPk, file.fileName + " (" + FileTransferWidget::getHumanReadableSize(file.filesize) + ")", true, true); } void Widget::updateFriendActivity(const Friend& frnd) { const ToxPk& pk = frnd.getPublicKey(); const auto oldTime = settings.getFriendActivity(pk); const auto newTime = QDateTime::currentDateTime(); settings.setFriendActivity(pk, newTime); FriendWidget* widget = friendWidgets[frnd.getPublicKey()]; contactListWidget->moveWidget(widget, frnd.getStatus()); contactListWidget->updateActivityTime(oldTime); // update old category widget } void Widget::removeFriend(Friend* f, bool fake) { if (!fake) { RemoveFriendDialog ask(this, f); ask.exec(); if (!ask.accepted()) { return; } if (ask.removeHistory()) { Nexus::getProfile()->getHistory()->removeFriendHistory(f->getPublicKey().toString()); } } const ToxPk friendPk = f->getPublicKey(); auto widget = friendWidgets[friendPk]; widget->setAsInactiveChatroom(); if (widget == activeChatroomWidget) { activeChatroomWidget = nullptr; onAddClicked(); } friendAlertConnections.remove(friendPk); contactListWidget->removeFriendWidget(widget); ContentDialog* lastDialog = ContentDialogManager::getInstance()->getFriendDialog(friendPk); if (lastDialog != nullptr) { lastDialog->removeFriend(friendPk); } FriendList::removeFriend(friendPk, fake); if (!fake) { core->removeFriend(f->getId()); // aliases aren't supported for non-friend peers in groups, revert to basic username for (Group* g : GroupList::getAllGroups()) { if (g->getPeerList().contains(friendPk)) { g->updateUsername(friendPk, f->getUserName()); } } } friendWidgets.remove(friendPk); delete widget; auto chatForm = chatForms[friendPk]; chatForms.remove(friendPk); delete chatForm; delete f; if (contentLayout && contentLayout->mainHead->layout()->isEmpty()) { onAddClicked(); } contactListWidget->reDraw(); } void Widget::removeFriend(const ToxPk& friendId) { removeFriend(FriendList::findFriend(friendId), false); } void Widget::onDialogShown(GenericChatroomWidget* widget) { widget->resetEventFlags(); widget->updateStatusLight(); ui->friendList->updateTracking(widget); resetIcon(); } void Widget::onFriendDialogShown(const Friend* f) { onDialogShown(friendWidgets[f->getPublicKey()]); } void Widget::onGroupDialogShown(Group* g) { const GroupId& groupId = g->getPersistentId(); onDialogShown(groupWidgets[groupId]); } void Widget::toggleFullscreen() { if (windowState().testFlag(Qt::WindowFullScreen)) { setWindowState(windowState() & ~Qt::WindowFullScreen); } else { setWindowState(windowState() | Qt::WindowFullScreen); } } void Widget::onUpdateAvailable() { ui->settingsButton->setProperty("update-available", true); ui->settingsButton->style()->unpolish(ui->settingsButton); ui->settingsButton->style()->polish(ui->settingsButton); } ContentDialog* Widget::createContentDialog() const { ContentDialog* contentDialog = new ContentDialog(); registerContentDialog(*contentDialog); return contentDialog; } void Widget::registerContentDialog(ContentDialog& contentDialog) const { ContentDialogManager::getInstance()->addContentDialog(contentDialog); connect(&contentDialog, &ContentDialog::friendDialogShown, this, &Widget::onFriendDialogShown); connect(&contentDialog, &ContentDialog::groupDialogShown, this, &Widget::onGroupDialogShown); connect(core, &Core::usernameSet, &contentDialog, &ContentDialog::setUsername); connect(&settings, &Settings::groupchatPositionChanged, &contentDialog, &ContentDialog::reorderLayouts); connect(&contentDialog, &ContentDialog::addFriendDialog, this, &Widget::addFriendDialog); connect(&contentDialog, &ContentDialog::addGroupDialog, this, &Widget::addGroupDialog); connect(&contentDialog, &ContentDialog::connectFriendWidget, this, &Widget::connectFriendWidget); #ifdef Q_OS_MAC Nexus& n = Nexus::getInstance(); connect(&contentDialog, &ContentDialog::destroyed, &n, &Nexus::updateWindowsClosed); connect(&contentDialog, &ContentDialog::windowStateChanged, &n, &Nexus::onWindowStateChanged); connect(contentDialog.windowHandle(), &QWindow::windowTitleChanged, &n, &Nexus::updateWindows); n.updateWindows(); #endif } ContentLayout* Widget::createContentDialog(DialogType type) const { class Dialog : public ActivateDialog { public: explicit Dialog(DialogType type, Settings& settings, Core* core) : ActivateDialog(nullptr, Qt::Window) , type(type) , settings(settings) , core{core} { restoreGeometry(settings.getDialogSettingsGeometry()); Translator::registerHandler(std::bind(&Dialog::retranslateUi, this), this); retranslateUi(); setWindowIcon(QIcon(":/img/icons/qtox.svg")); setStyleSheet(Style::getStylesheet("window/general.css")); connect(core, &Core::usernameSet, this, &Dialog::retranslateUi); } ~Dialog() { Translator::unregister(this); } public slots: void retranslateUi() { setWindowTitle(core->getUsername() + QStringLiteral(" - ") + Widget::fromDialogType(type)); } protected: void resizeEvent(QResizeEvent* event) override { settings.setDialogSettingsGeometry(saveGeometry()); QDialog::resizeEvent(event); } void moveEvent(QMoveEvent* event) override { settings.setDialogSettingsGeometry(saveGeometry()); QDialog::moveEvent(event); } private: DialogType type; Settings& settings; Core* core; }; Dialog* dialog = new Dialog(type, settings, core); dialog->setAttribute(Qt::WA_DeleteOnClose); ContentLayout* contentLayoutDialog = new ContentLayout(dialog); dialog->setObjectName("detached"); dialog->setLayout(contentLayoutDialog); dialog->layout()->setMargin(0); dialog->layout()->setSpacing(0); dialog->setMinimumSize(720, 400); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); #ifdef Q_OS_MAC connect(dialog, &Dialog::destroyed, &Nexus::getInstance(), &Nexus::updateWindowsClosed); connect(dialog, &ActivateDialog::windowStateChanged, &Nexus::getInstance(), &Nexus::updateWindowsStates); connect(dialog->windowHandle(), &QWindow::windowTitleChanged, &Nexus::getInstance(), &Nexus::updateWindows); Nexus::getInstance().updateWindows(); #endif return contentLayoutDialog; } void Widget::copyFriendIdToClipboard(const ToxPk& friendId) { Friend* f = FriendList::findFriend(friendId); if (f != nullptr) { QClipboard* clipboard = QApplication::clipboard(); clipboard->setText(friendId.toString(), QClipboard::Clipboard); } } void Widget::onGroupInviteReceived(const GroupInvite& inviteInfo) { const uint32_t friendId = inviteInfo.getFriendId(); const ToxPk& friendPk = FriendList::id2Key(friendId); const Friend* f = FriendList::findFriend(friendPk); updateFriendActivity(*f); const uint8_t confType = inviteInfo.getType(); if (confType == TOX_CONFERENCE_TYPE_TEXT || confType == TOX_CONFERENCE_TYPE_AV) { if (settings.getAutoGroupInvite(f->getPublicKey())) { onGroupInviteAccepted(inviteInfo); } else { if (!groupInviteForm->addGroupInvite(inviteInfo)) { return; } ++unreadGroupInvites; groupInvitesUpdate(); newMessageAlert(window(), isActiveWindow(), true, true); #if DESKTOP_NOTIFICATIONS if (settings.getNotifyHide()) { notifier.notifyMessageSimple(DesktopNotify::MessageType::GROUP_INVITE); } else { notifier.notifyMessagePixmap(f->getDisplayedName() + tr(" invites you to join a group."), {}, Nexus::getProfile()->loadAvatar(f->getPublicKey())); } #endif } } else { qWarning() << "onGroupInviteReceived: Unknown groupchat type:" << confType; return; } } void Widget::onGroupInviteAccepted(const GroupInvite& inviteInfo) { const uint32_t groupId = core->joinGroupchat(inviteInfo); if (groupId == std::numeric_limits::max()) { qWarning() << "onGroupInviteAccepted: Unable to accept group invite"; return; } } void Widget::onGroupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction) { const GroupId& groupId = GroupList::id2Key(groupnumber); Group* g = GroupList::findGroup(groupId); assert(g); ToxPk author = core->getGroupPeerPk(groupnumber, peernumber); groupMessageDispatchers[groupId]->onMessageReceived(author, isAction, message); } void Widget::onGroupPeerlistChanged(uint32_t groupnumber) { const GroupId& groupId = GroupList::id2Key(groupnumber); Group* g = GroupList::findGroup(groupId); assert(g); g->regeneratePeerList(); } void Widget::onGroupPeerNameChanged(uint32_t groupnumber, const ToxPk& peerPk, const QString& newName) { const GroupId& groupId = GroupList::id2Key(groupnumber); Group* g = GroupList::findGroup(groupId); assert(g); const QString setName = FriendList::decideNickname(peerPk, newName); g->updateUsername(peerPk, newName); } void Widget::onGroupTitleChanged(uint32_t groupnumber, const QString& author, const QString& title) { const GroupId& groupId = GroupList::id2Key(groupnumber); Group* g = GroupList::findGroup(groupId); assert(g); GroupWidget* widget = groupWidgets[groupId]; if (widget->isActive()) { GUI::setWindowTitle(title); } g->setTitle(author, title); FilterCriteria filter = getFilterCriteria(); widget->searchName(ui->searchContactText->text(), filterGroups(filter)); } void Widget::titleChangedByUser(const QString& title) { const auto* group = qobject_cast(sender()); assert(group != nullptr); emit changeGroupTitle(group->getId(), title); } void Widget::onGroupPeerAudioPlaying(int groupnumber, ToxPk peerPk) { const GroupId& groupId = GroupList::id2Key(groupnumber); Group* g = GroupList::findGroup(groupId); assert(g); auto form = groupChatForms[groupId].data(); form->peerAudioPlaying(peerPk); } void Widget::removeGroup(Group* g, bool fake) { const auto& groupId = g->getPersistentId(); const auto groupnumber = g->getId(); auto groupWidgetIt = groupWidgets.find(groupId); if (groupWidgetIt == groupWidgets.end()) { qWarning() << "Tried to remove group" << groupnumber << "but GroupWidget doesn't exist"; return; } auto widget = groupWidgetIt.value(); widget->setAsInactiveChatroom(); if (static_cast(widget) == activeChatroomWidget) { activeChatroomWidget = nullptr; onAddClicked(); } GroupList::removeGroup(groupId, fake); ContentDialog* contentDialog = ContentDialogManager::getInstance()->getGroupDialog(groupId); if (contentDialog != nullptr) { contentDialog->removeGroup(groupId); } if (!fake) { core->removeGroup(groupnumber); } contactListWidget->removeGroupWidget(widget); // deletes widget groupWidgets.remove(groupId); auto groupChatFormIt = groupChatForms.find(groupId); if (groupChatFormIt == groupChatForms.end()) { qWarning() << "Tried to remove group" << groupnumber << "but GroupChatForm doesn't exist"; return; } groupChatForms.erase(groupChatFormIt); delete g; if (contentLayout && contentLayout->mainHead->layout()->isEmpty()) { onAddClicked(); } groupAlertConnections.remove(groupId); contactListWidget->reDraw(); } void Widget::removeGroup(const GroupId& groupId) { removeGroup(GroupList::findGroup(groupId)); } Group* Widget::createGroup(uint32_t groupnumber, const GroupId& groupId) { Group* g = GroupList::findGroup(groupId); if (g) { qWarning() << "Group already exists"; return g; } const auto groupName = tr("Groupchat #%1").arg(groupnumber); const bool enabled = core->getGroupAvEnabled(groupnumber); Group* newgroup = GroupList::addGroup(groupnumber, groupId, groupName, enabled, core->getUsername()); auto dialogManager = ContentDialogManager::getInstance(); auto rawChatroom = new GroupChatroom(newgroup, dialogManager); std::shared_ptr chatroom(rawChatroom); const auto compact = settings.getCompactLayout(); auto widget = new GroupWidget(chatroom, compact); auto messageProcessor = MessageProcessor(sharedMessageProcessorParams); auto messageDispatcher = std::make_shared(*newgroup, std::move(messageProcessor), *core, *core, Settings::getInstance()); auto groupChatLog = std::make_shared(*core); connect(messageDispatcher.get(), &IMessageDispatcher::messageReceived, groupChatLog.get(), &SessionChatLog::onMessageReceived); connect(messageDispatcher.get(), &IMessageDispatcher::messageSent, groupChatLog.get(), &SessionChatLog::onMessageSent); connect(messageDispatcher.get(), &IMessageDispatcher::messageComplete, groupChatLog.get(), &SessionChatLog::onMessageComplete); auto notifyReceivedCallback = [this, groupId](const ToxPk& author, const Message& message) { auto isTargeted = std::any_of(message.metadata.begin(), message.metadata.end(), [](MessageMetadata metadata) { return metadata.type == MessageMetadataType::selfMention; }); newGroupMessageAlert(groupId, author, message.content, isTargeted || settings.getGroupAlwaysNotify()); }; auto notifyReceivedConnection = connect(messageDispatcher.get(), &IMessageDispatcher::messageReceived, notifyReceivedCallback); groupAlertConnections.insert(groupId, notifyReceivedConnection); auto form = new GroupChatForm(newgroup, *groupChatLog, *messageDispatcher); connect(&settings, &Settings::nameColorsChanged, form, &GenericChatForm::setColorizedNames); form->setColorizedNames(settings.getEnableGroupChatsColor()); groupMessageDispatchers[groupId] = messageDispatcher; groupChatLogs[groupId] = groupChatLog; groupWidgets[groupId] = widget; groupChatrooms[groupId] = chatroom; groupChatForms[groupId] = QSharedPointer(form); contactListWidget->addGroupWidget(widget); widget->updateStatusLight(); contactListWidget->activateWindow(); connect(widget, &GroupWidget::chatroomWidgetClicked, this, &Widget::onChatroomWidgetClicked); connect(widget, &GroupWidget::newWindowOpened, this, &Widget::openNewDialog); #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) auto widgetRemoveGroup = QOverload::of(&Widget::removeGroup); #else auto widgetRemoveGroup = static_cast(&Widget::removeGroup); #endif connect(widget, &GroupWidget::removeGroup, this, widgetRemoveGroup); connect(widget, &GroupWidget::middleMouseClicked, this, [=]() { removeGroup(groupId); }); connect(widget, &GroupWidget::chatroomWidgetClicked, form, &ChatForm::focusInput); connect(newgroup, &Group::titleChangedByUser, this, &Widget::titleChangedByUser); connect(core, &Core::usernameSet, newgroup, &Group::setSelfName); FilterCriteria filter = getFilterCriteria(); widget->searchName(ui->searchContactText->text(), filterGroups(filter)); return newgroup; } void Widget::onEmptyGroupCreated(uint32_t groupnumber, const GroupId& groupId, const QString& title) { Group* group = createGroup(groupnumber, groupId); if (!group) { return; } if (title.isEmpty()) { // Only rename group if groups are visible. if (groupsVisible()) { groupWidgets[groupId]->editName(); } } else { group->setTitle(QString(), title); } } void Widget::onGroupJoined(int groupId, const GroupId& groupPersistentId) { createGroup(groupId, groupPersistentId); } /** * @brief Used to reset the blinking icon. */ void Widget::resetIcon() { eventIcon = false; eventFlag = false; updateIcons(); } bool Widget::event(QEvent* e) { switch (e->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: focusChatInput(); break; case QEvent::Paint: ui->friendList->updateVisualTracking(); break; case QEvent::WindowActivate: if (activeChatroomWidget) { activeChatroomWidget->resetEventFlags(); activeChatroomWidget->updateStatusLight(); setWindowTitle(activeChatroomWidget->getTitle()); } if (eventFlag) { resetIcon(); } focusChatInput(); #ifdef Q_OS_MAC emit windowStateChanged(windowState()); case QEvent::WindowStateChange: Nexus::getInstance().updateWindowsStates(); #endif break; default: break; } return QMainWindow::event(e); } void Widget::onUserAwayCheck() { #ifdef QTOX_PLATFORM_EXT uint32_t autoAwayTime = settings.getAutoAwayTime() * 60 * 1000; bool online = static_cast(ui->statusButton->property("status").toInt()) == Status::Status::Online; bool away = autoAwayTime && Platform::getIdleTime() >= autoAwayTime; if (online && away) { qDebug() << "auto away activated at" << QTime::currentTime().toString(); emit statusSet(Status::Status::Away); autoAwayActive = true; } else if (autoAwayActive && !away) { qDebug() << "auto away deactivated at" << QTime::currentTime().toString(); emit statusSet(Status::Status::Online); autoAwayActive = false; } #endif } void Widget::onEventIconTick() { if (eventFlag) { eventIcon ^= true; updateIcons(); } } void Widget::onTryCreateTrayIcon() { static int32_t tries = 15; if (!icon && tries--) { if (QSystemTrayIcon::isSystemTrayAvailable()) { icon = std::unique_ptr(new QSystemTrayIcon); updateIcons(); trayMenu = new QMenu(this); // adding activate to the top, avoids accidentally clicking quit trayMenu->addAction(actionShow); trayMenu->addSeparator(); trayMenu->addAction(statusOnline); trayMenu->addAction(statusAway); trayMenu->addAction(statusBusy); trayMenu->addSeparator(); trayMenu->addAction(actionLogout); trayMenu->addAction(actionQuit); icon->setContextMenu(trayMenu); connect(icon.get(), &QSystemTrayIcon::activated, this, &Widget::onIconClick); if (settings.getShowSystemTray()) { icon->show(); setHidden(settings.getAutostartInTray()); } else { show(); } #ifdef Q_OS_MAC Nexus::getInstance().dockMenu->setAsDockMenu(); #endif } else if (!isVisible()) { show(); } } else { disconnect(timer, &QTimer::timeout, this, &Widget::onTryCreateTrayIcon); if (!icon) { qWarning() << "No system tray detected!"; show(); } } } void Widget::setStatusOnline() { if (!ui->statusButton->isEnabled()) { return; } core->setStatus(Status::Status::Online); } void Widget::setStatusAway() { if (!ui->statusButton->isEnabled()) { return; } core->setStatus(Status::Status::Away); } void Widget::setStatusBusy() { if (!ui->statusButton->isEnabled()) { return; } core->setStatus(Status::Status::Busy); } void Widget::onGroupSendFailed(uint32_t groupnumber) { const auto& groupId = GroupList::id2Key(groupnumber); Group* g = GroupList::findGroup(groupId); assert(g); const auto message = tr("Message failed to send"); const auto curTime = QDateTime::currentDateTime(); auto form = groupChatForms[groupId].data(); form->addSystemInfoMessage(message, ChatMessage::INFO, curTime); } void Widget::onFriendTypingChanged(uint32_t friendnumber, bool isTyping) { const auto& friendId = FriendList::id2Key(friendnumber); Friend* f = FriendList::findFriend(friendId); if (!f) { return; } chatForms[f->getPublicKey()]->setFriendTyping(isTyping); } void Widget::onSetShowSystemTray(bool newValue) { if (icon) { icon->setVisible(newValue); } } void Widget::saveWindowGeometry() { settings.setWindowGeometry(saveGeometry()); settings.setWindowState(saveState()); } void Widget::saveSplitterGeometry() { if (!settings.getSeparateWindow()) { settings.setSplitterState(ui->mainSplitter->saveState()); } } void Widget::onSplitterMoved(int pos, int index) { Q_UNUSED(pos); Q_UNUSED(index); saveSplitterGeometry(); } void Widget::cycleContacts(bool forward) { contactListWidget->cycleContacts(activeChatroomWidget, forward); } bool Widget::filterGroups(FilterCriteria index) { switch (index) { case FilterCriteria::Offline: case FilterCriteria::Friends: return true; default: return false; } } bool Widget::filterOffline(FilterCriteria index) { switch (index) { case FilterCriteria::Online: case FilterCriteria::Groups: return true; default: return false; } } bool Widget::filterOnline(FilterCriteria index) { switch (index) { case FilterCriteria::Offline: case FilterCriteria::Groups: return true; default: return false; } } void Widget::clearAllReceipts() { QList frnds = FriendList::getAllFriends(); for (Friend* f : frnds) { friendMessageDispatchers[f->getPublicKey()]->clearOutgoingMessages(); } } void Widget::reloadTheme() { this->setStyleSheet(Style::getStylesheet("window/general.css")); QString statusPanelStyle = Style::getStylesheet("window/statusPanel.css"); ui->tooliconsZone->setStyleSheet(Style::getStylesheet("tooliconsZone/tooliconsZone.css")); ui->statusPanel->setStyleSheet(statusPanelStyle); ui->statusHead->setStyleSheet(statusPanelStyle); ui->friendList->setStyleSheet(Style::getStylesheet("friendList/friendList.css")); ui->statusButton->setStyleSheet(Style::getStylesheet("statusButton/statusButton.css")); contactListWidget->reDraw(); profilePicture->setStyleSheet(Style::getStylesheet("window/profile.css")); if (contentLayout != nullptr) { contentLayout->reloadTheme(); } for (Friend* f : FriendList::getAllFriends()) { friendWidgets[f->getPublicKey()]->reloadTheme(); } for (Group* g : GroupList::getAllGroups()) { groupWidgets[g->getPersistentId()]->reloadTheme(); } for (auto f : FriendList::getAllFriends()) { chatForms[f->getPublicKey()]->reloadTheme(); } for (auto g : GroupList::getAllGroups()) { groupChatForms[g->getPersistentId()]->reloadTheme(); } } void Widget::nextContact() { cycleContacts(true); } void Widget::previousContact() { cycleContacts(false); } // Preparing needed to set correct size of icons for GTK tray backend inline QIcon Widget::prepareIcon(QString path, int w, int h) { #ifdef Q_OS_LINUX QString desktop = getenv("XDG_CURRENT_DESKTOP"); if (desktop.isEmpty()) { desktop = getenv("DESKTOP_SESSION"); } desktop = desktop.toLower(); if (desktop == "xfce" || desktop.contains("gnome") || desktop == "mate" || desktop == "x-cinnamon") { if (w > 0 && h > 0) { QSvgRenderer renderer(path); QPixmap pm(w, h); pm.fill(Qt::transparent); QPainter painter(&pm); renderer.render(&painter, pm.rect()); return QIcon(pm); } } #endif return QIcon(path); } void Widget::searchContacts() { QString searchString = ui->searchContactText->text(); FilterCriteria filter = getFilterCriteria(); contactListWidget->searchChatrooms(searchString, filterOnline(filter), filterOffline(filter), filterGroups(filter)); updateFilterText(); contactListWidget->reDraw(); } void Widget::changeDisplayMode() { filterDisplayGroup->setEnabled(false); if (filterDisplayGroup->checkedAction() == filterDisplayActivity) { contactListWidget->setMode(FriendListWidget::SortingMode::Activity); } else if (filterDisplayGroup->checkedAction() == filterDisplayName) { contactListWidget->setMode(FriendListWidget::SortingMode::Name); } searchContacts(); filterDisplayGroup->setEnabled(true); updateFilterText(); } void Widget::updateFilterText() { QString action = filterDisplayGroup->checkedAction()->text(); QString text = filterGroup->checkedAction()->text(); text = action + QStringLiteral(" | ") + text; ui->searchContactFilterBox->setText(text); } Widget::FilterCriteria Widget::getFilterCriteria() const { QAction* checked = filterGroup->checkedAction(); if (checked == filterOnlineAction) return FilterCriteria::Online; else if (checked == filterOfflineAction) return FilterCriteria::Offline; else if (checked == filterFriendsAction) return FilterCriteria::Friends; else if (checked == filterGroupsAction) return FilterCriteria::Groups; return FilterCriteria::All; } void Widget::searchCircle(CircleWidget& circleWidget) { FilterCriteria filter = getFilterCriteria(); QString text = ui->searchContactText->text(); circleWidget.search(text, true, filterOnline(filter), filterOffline(filter)); } bool Widget::groupsVisible() const { FilterCriteria filter = getFilterCriteria(); return !filterGroups(filter); } void Widget::friendListContextMenu(const QPoint& pos) { QMenu menu(this); QAction* createGroupAction = menu.addAction(tr("Create new group...")); QAction* addCircleAction = menu.addAction(tr("Add new circle...")); QAction* chosenAction = menu.exec(ui->friendList->mapToGlobal(pos)); if (chosenAction == addCircleAction) { contactListWidget->addCircleWidget(); } else if (chosenAction == createGroupAction) { core->createGroup(); } } void Widget::friendRequestsUpdate() { unsigned int unreadFriendRequests = settings.getUnreadFriendRequests(); if (unreadFriendRequests == 0) { delete friendRequestsButton; friendRequestsButton = nullptr; } else if (!friendRequestsButton) { friendRequestsButton = new QPushButton(this); friendRequestsButton->setObjectName("green"); ui->statusLayout->insertWidget(2, friendRequestsButton); connect(friendRequestsButton, &QPushButton::released, [this]() { onAddClicked(); addFriendForm->setMode(AddFriendForm::Mode::FriendRequest); }); } if (friendRequestsButton) { friendRequestsButton->setText(tr("%n New Friend Request(s)", "", unreadFriendRequests)); } } void Widget::groupInvitesUpdate() { if (unreadGroupInvites == 0) { delete groupInvitesButton; groupInvitesButton = nullptr; } else if (!groupInvitesButton) { groupInvitesButton = new QPushButton(this); groupInvitesButton->setObjectName("green"); ui->statusLayout->insertWidget(2, groupInvitesButton); connect(groupInvitesButton, &QPushButton::released, this, &Widget::onGroupClicked); } if (groupInvitesButton) { groupInvitesButton->setText(tr("%n New Group Invite(s)", "", unreadGroupInvites)); } } void Widget::groupInvitesClear() { unreadGroupInvites = 0; groupInvitesUpdate(); } void Widget::setActiveToolMenuButton(ActiveToolMenuButton newActiveButton) { ui->addButton->setChecked(newActiveButton == ActiveToolMenuButton::AddButton); ui->addButton->setDisabled(newActiveButton == ActiveToolMenuButton::AddButton); ui->groupButton->setChecked(newActiveButton == ActiveToolMenuButton::GroupButton); ui->groupButton->setDisabled(newActiveButton == ActiveToolMenuButton::GroupButton); ui->transferButton->setChecked(newActiveButton == ActiveToolMenuButton::TransferButton); ui->transferButton->setDisabled(newActiveButton == ActiveToolMenuButton::TransferButton); ui->settingsButton->setChecked(newActiveButton == ActiveToolMenuButton::SettingButton); ui->settingsButton->setDisabled(newActiveButton == ActiveToolMenuButton::SettingButton); } void Widget::retranslateUi() { ui->retranslateUi(this); setUsername(core->getUsername()); setStatusMessage(core->getStatusMessage()); filterDisplayName->setText(tr("By Name")); filterDisplayActivity->setText(tr("By Activity")); filterAllAction->setText(tr("All")); filterOnlineAction->setText(tr("Online")); filterOfflineAction->setText(tr("Offline")); filterFriendsAction->setText(tr("Friends")); filterGroupsAction->setText(tr("Groups")); ui->searchContactText->setPlaceholderText(tr("Search Contacts")); updateFilterText(); ui->searchContactText->setPlaceholderText(tr("Search Contacts")); statusOnline->setText(tr("Online", "Button to set your status to 'Online'")); statusAway->setText(tr("Away", "Button to set your status to 'Away'")); statusBusy->setText(tr("Busy", "Button to set your status to 'Busy'")); actionLogout->setText(tr("Logout", "Tray action menu to logout user")); actionQuit->setText(tr("Exit", "Tray action menu to exit tox")); actionShow->setText(tr("Show", "Tray action menu to show qTox window")); if (!settings.getSeparateWindow() && (settingsWidget && settingsWidget->isShown())) { setWindowTitle(fromDialogType(DialogType::SettingDialog)); } friendRequestsUpdate(); groupInvitesUpdate(); #ifdef Q_OS_MAC Nexus::getInstance().retranslateUi(); filterMenu->menuAction()->setText(tr("Filter...")); fileMenu->setText(tr("File")); editMenu->setText(tr("Edit")); contactMenu->setText(tr("Contacts")); changeStatusMenu->menuAction()->setText(tr("Change Status")); editProfileAction->setText(tr("Edit Profile")); logoutAction->setText(tr("Log out")); addContactAction->setText(tr("Add Contact...")); nextConversationAction->setText(tr("Next Conversation")); previousConversationAction->setText(tr("Previous Conversation")); #endif } void Widget::focusChatInput() { if (activeChatroomWidget) { if (const Friend* f = activeChatroomWidget->getFriend()) { chatForms[f->getPublicKey()]->focusInput(); } else if (Group* g = activeChatroomWidget->getGroup()) { groupChatForms[g->getPersistentId()]->focusInput(); } } } void Widget::refreshPeerListsLocal(const QString& username) { for (Group* g : GroupList::getAllGroups()) { g->updateUsername(core->getSelfPublicKey(), username); } } void Widget::connectCircleWidget(CircleWidget& circleWidget) { connect(&circleWidget, &CircleWidget::searchCircle, this, &Widget::searchCircle); connect(&circleWidget, &CircleWidget::newContentDialog, this, &Widget::registerContentDialog); } void Widget::connectFriendWidget(FriendWidget& friendWidget) { connect(&friendWidget, &FriendWidget::searchCircle, this, &Widget::searchCircle); connect(&friendWidget, &FriendWidget::updateFriendActivity, this, &Widget::updateFriendActivity); } qTox/src/widget/widget.h000066400000000000000000000315531415623743500155400ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef WIDGET_H #define WIDGET_H #include "ui_mainwindow.h" #include #include #include #include #include "genericchatitemwidget.h" #include "src/audio/iaudiocontrol.h" #include "src/audio/iaudiosink.h" #include "src/core/core.h" #include "src/core/groupid.h" #include "src/core/toxfile.h" #include "src/core/toxid.h" #include "src/core/toxpk.h" #include "src/model/friendmessagedispatcher.h" #include "src/model/groupmessagedispatcher.h" #if DESKTOP_NOTIFICATIONS #include "src/platform/desktop_notifications/desktopnotify.h" #endif #define PIXELS_TO_ACT 7 namespace Ui { class MainWindow; } class AddFriendForm; class AlSink; class Camera; class ChatForm; class CircleWidget; class ContentDialog; class ContentLayout; class Core; class FilesForm; class Friend; class FriendChatroom; class FriendListWidget; class FriendWidget; class GenericChatroomWidget; class Group; class GroupChatForm; class GroupChatroom; class GroupInvite; class GroupInviteForm; class GroupWidget; class MaskablePixmapWidget; class ProfileForm; class ProfileInfo; class QActionGroup; class QMenu; class QPushButton; class QSplitter; class QTimer; class SettingsWidget; class SystemTrayIcon; class VideoSurface; class UpdateCheck; class Settings; class IChatLog; class ChatHistory; class Widget final : public QMainWindow { Q_OBJECT private: enum class ActiveToolMenuButton { AddButton, GroupButton, TransferButton, SettingButton, None, }; enum class DialogType { AddDialog, TransferDialog, SettingDialog, ProfileDialog, GroupDialog }; enum class FilterCriteria { All = 0, Online, Offline, Friends, Groups }; public: explicit Widget(IAudioControl& audio, QWidget* parent = nullptr); ~Widget() override; void init(); void setCentralWidget(QWidget* widget, const QString& widgetName); QString getUsername(); Camera* getCamera(); static Widget* getInstance(IAudioControl* audio = nullptr); void showUpdateDownloadProgress(); void addFriendDialog(const Friend* frnd, ContentDialog* dialog); void addGroupDialog(Group* group, ContentDialog* dialog); bool newFriendMessageAlert(const ToxPk& friendId, const QString& text, bool sound = true, bool file = false); bool newGroupMessageAlert(const GroupId& groupId, const ToxPk& authorPk, const QString& message, bool notify); bool getIsWindowMinimized(); void updateIcons(); static QString fromDialogType(DialogType type); ContentDialog* createContentDialog() const; ContentLayout* createContentDialog(DialogType type) const; static void confirmExecutableOpen(const QFileInfo& file); void clearAllReceipts(); void reloadTheme(); static inline QIcon prepareIcon(QString path, int w = 0, int h = 0); bool groupsVisible() const; void resetIcon(); public slots: void onShowSettings(); void onSeparateWindowClicked(bool separate); void onSeparateWindowChanged(bool separate, bool clicked); void setWindowTitle(const QString& title); void forceShow(); void onConnected(); void onDisconnected(); void onStatusSet(Status::Status status); void onFailedToStartCore(); void onBadProxyCore(); void onSelfAvatarLoaded(const QPixmap& pic); void setUsername(const QString& username); void setStatusMessage(const QString& statusMessage); void addFriend(uint32_t friendId, const ToxPk& friendPk); void addFriendFailed(const ToxPk& userId, const QString& errorInfo = QString()); void onFriendStatusChanged(int friendId, Status::Status status); void onFriendStatusMessageChanged(int friendId, const QString& message); void onFriendDisplayedNameChanged(const QString& displayed); void onFriendUsernameChanged(int friendId, const QString& username); void onFriendAliasChanged(const ToxPk& friendId, const QString& alias); void onFriendMessageReceived(uint32_t friendnumber, const QString& message, bool isAction); void onReceiptReceived(int friendId, ReceiptNum receipt); void onFriendRequestReceived(const ToxPk& friendPk, const QString& message); void onFileReceiveRequested(const ToxFile& file); void onEmptyGroupCreated(uint32_t groupnumber, const GroupId& groupId, const QString& title); void onGroupJoined(int groupNum, const GroupId& groupId); void onGroupInviteReceived(const GroupInvite& inviteInfo); void onGroupInviteAccepted(const GroupInvite& inviteInfo); void onGroupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); void onGroupPeerlistChanged(uint32_t groupnumber); void onGroupPeerNameChanged(uint32_t groupnumber, const ToxPk& peerPk, const QString& newName); void onGroupTitleChanged(uint32_t groupnumber, const QString& author, const QString& title); void titleChangedByUser(const QString& title); void onGroupPeerAudioPlaying(int groupnumber, ToxPk peerPk); void onGroupSendFailed(uint32_t groupnumber); void onFriendTypingChanged(uint32_t friendnumber, bool isTyping); void nextContact(); void previousContact(); void onFriendDialogShown(const Friend* f); void onGroupDialogShown(Group* g); void toggleFullscreen(); void refreshPeerListsLocal(const QString& username); void onUpdateAvailable(); void onCoreChanged(Core& core); signals: void friendRequestAccepted(const ToxPk& friendPk); void friendRequested(const ToxId& friendAddress, const QString& message); void statusSet(Status::Status status); void statusSelected(Status::Status status); void usernameChanged(const QString& username); void changeGroupTitle(uint32_t groupnumber, const QString& title); void statusMessageChanged(const QString& statusMessage); void resized(); void windowStateChanged(Qt::WindowStates states); private slots: void onAddClicked(); void onGroupClicked(); void onTransferClicked(); void showProfile(); void openNewDialog(GenericChatroomWidget* widget); void onChatroomWidgetClicked(GenericChatroomWidget* widget); void onStatusMessageChanged(const QString& newStatusMessage); void removeFriend(const ToxPk& friendId); void copyFriendIdToClipboard(const ToxPk& friendId); void removeGroup(const GroupId& groupId); void setStatusOnline(); void setStatusAway(); void setStatusBusy(); void onIconClick(QSystemTrayIcon::ActivationReason); void onUserAwayCheck(); void onEventIconTick(); void onTryCreateTrayIcon(); void onSetShowSystemTray(bool newValue); void onSplitterMoved(int pos, int index); void friendListContextMenu(const QPoint& pos); void friendRequestsUpdate(); void groupInvitesUpdate(); void groupInvitesClear(); void onDialogShown(GenericChatroomWidget* widget); void outgoingNotification(); void onCallEnd(); void incomingNotification(uint32_t friendId); void onRejectCall(uint32_t friendId); void onStopNotification(); void dispatchFile(ToxFile file); void dispatchFileWithBool(ToxFile file, bool); void dispatchFileSendFailed(uint32_t friendId, const QString& fileName); void connectCircleWidget(CircleWidget& circleWidget); void connectFriendWidget(FriendWidget& friendWidget); void searchCircle(CircleWidget& circleWidget); void updateFriendActivity(const Friend& frnd); void registerContentDialog(ContentDialog& contentDialog) const; private: // QMainWindow overrides bool eventFilter(QObject* obj, QEvent* event) final override; bool event(QEvent* e) final override; void closeEvent(QCloseEvent* event) final override; void changeEvent(QEvent* event) final override; void resizeEvent(QResizeEvent* event) final override; void moveEvent(QMoveEvent* event) final override; bool newMessageAlert(QWidget* currentWindow, bool isActive, bool sound = true, bool notify = true); void setActiveToolMenuButton(ActiveToolMenuButton newActiveButton); void hideMainForms(GenericChatroomWidget* chatroomWidget); Group* createGroup(uint32_t groupnumber, const GroupId& groupId); void removeFriend(Friend* f, bool fake = false); void removeGroup(Group* g, bool fake = false); void saveWindowGeometry(); void saveSplitterGeometry(); void cycleContacts(bool forward); void searchContacts(); void changeDisplayMode(); void updateFilterText(); FilterCriteria getFilterCriteria() const; static bool filterGroups(FilterCriteria index); static bool filterOnline(FilterCriteria index); static bool filterOffline(FilterCriteria index); void retranslateUi(); void focusChatInput(); void openDialog(GenericChatroomWidget* widget, bool newWindow); void playNotificationSound(IAudioSink::Sound sound, bool loop = false); void cleanupNotificationSound(); private: std::unique_ptr icon; QMenu* trayMenu; QAction* statusOnline; QAction* statusAway; QAction* statusBusy; QAction* actionLogout; QAction* actionQuit; QAction* actionShow; QMenu* filterMenu; QActionGroup* filterGroup; QAction* filterAllAction; QAction* filterOnlineAction; QAction* filterOfflineAction; QAction* filterFriendsAction; QAction* filterGroupsAction; QActionGroup* filterDisplayGroup; QAction* filterDisplayName; QAction* filterDisplayActivity; Ui::MainWindow* ui; QSplitter* centralLayout; QPoint dragPosition; ContentLayout* contentLayout; AddFriendForm* addFriendForm; GroupInviteForm* groupInviteForm; ProfileInfo* profileInfo; ProfileForm* profileForm; QPointer settingsWidget; std::unique_ptr updateCheck; // ownership should be moved outside Widget once non-singleton FilesForm* filesForm; static Widget* instance; GenericChatroomWidget* activeChatroomWidget; FriendListWidget* contactListWidget; MaskablePixmapWidget* profilePicture; bool notify(QObject* receiver, QEvent* event); bool autoAwayActive = false; QTimer* timer; bool eventFlag; bool eventIcon; bool wasMaximized = false; QPushButton* friendRequestsButton; QPushButton* groupInvitesButton; unsigned int unreadGroupInvites; int icon_size; IAudioControl& audio; std::unique_ptr audioNotification = nullptr; Settings& settings; QMap friendWidgets; // Shared pointer because qmap copies stuff all over the place QMap> friendMessageDispatchers; // Stop gap method of linking our friend messages back to a group id. // Eventual goal is to have a notification manager that works on // Messages hooked up to message dispatchers but we aren't there // yet QMap friendAlertConnections; QMap> friendChatLogs; QMap> friendChatrooms; QMap chatForms; QMap groupWidgets; QMap> groupMessageDispatchers; // Stop gap method of linking our group messages back to a group id. // Eventual goal is to have a notification manager that works on // Messages hooked up to message dispatchers but we aren't there // yet QMap groupAlertConnections; QMap> groupChatLogs; QMap> groupChatrooms; QMap> groupChatForms; Core* core = nullptr; MessageProcessor::SharedParams sharedMessageProcessorParams; #if DESKTOP_NOTIFICATIONS DesktopNotify notifier; #endif #ifdef Q_OS_MAC QAction* fileMenu; QAction* editMenu; QAction* contactMenu; QMenu* changeStatusMenu; QAction* editProfileAction; QAction* logoutAction; QAction* addContactAction; QAction* nextConversationAction; QAction* previousConversationAction; #endif }; bool toxActivateEventHandler(const QByteArray& data); #endif // WIDGET_H qTox/test-pr.sh000077500000000000000000000034401415623743500137610ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © 2016 Zetok Zalbavar # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Script for testing pull requests. Works only when there are no merge # conflicts. Assumes that working dir is a qTox git repo. # # usage: # ./$script $pr_number $optional_message # # # $pr_number – number of the PR as shown on GH # $optional_message – message that is going to be put in merge commit, # before the appended shortlog. # set -e -o pipefail readonly PR="${1###}" # make sure to add newlines to the message, otherwise merge message # will not look well if [[ ! -z $2 ]] then readonly OPT_MSG=" $2 " fi source_functions() { local fns_file="tools/lib/PR_bash.source" source $fns_file } main() { local remote_name="upstream" local merge_branch="test" source_functions exit_if_not_pr $PR add_remote "https" get_sources merge git -c user.email='<>' -c user.name='qTox testing' \ merge --no-gpg-sign \ && after_merge_msg $merge_branch \ || after_merge_failure_msg $merge_branch } main qTox/test/000077500000000000000000000000001415623743500130025ustar00rootroot00000000000000qTox/test/chatlog/000077500000000000000000000000001415623743500144235ustar00rootroot00000000000000qTox/test/chatlog/textformatter_test.cpp000066400000000000000000000507121415623743500211030ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/chatlog/textformatter.h" #include #include #include #include #define PAIR_FORMAT(input, output) {QStringLiteral(input), QStringLiteral(output)} using StringPair = QPair; static const StringPair TAGS[] { PAIR_FORMAT("", ""), PAIR_FORMAT("", ""), PAIR_FORMAT("", ""), PAIR_FORMAT("", ""), PAIR_FORMAT("", ""), }; enum StyleType { BOLD, ITALIC, UNDERLINE, STRIKE, CODE, }; /** * @brief The MarkdownToTags struct maps sequence of markdown symbols to HTML tags according this * sequence */ struct MarkdownToTags { QString markdownSequence; StringPair htmlTags; }; static const QVector SINGLE_SIGN_MARKDOWN { {QStringLiteral("*"), TAGS[StyleType::BOLD]}, {QStringLiteral("/"), TAGS[StyleType::ITALIC]}, {QStringLiteral("_"), TAGS[StyleType::UNDERLINE]}, {QStringLiteral("~"), TAGS[StyleType::STRIKE]}, {QStringLiteral("`"), TAGS[StyleType::CODE]}, }; static const QVector DOUBLE_SIGN_MARKDOWN { {QStringLiteral("**"), TAGS[StyleType::BOLD]}, {QStringLiteral("//"), TAGS[StyleType::ITALIC]}, {QStringLiteral("__"), TAGS[StyleType::UNDERLINE]}, {QStringLiteral("~~"), TAGS[StyleType::STRIKE]}, }; static const QVector MULTI_SIGN_MARKDOWN { {QStringLiteral("```"), TAGS[StyleType::CODE]}, }; /** * @brief Creates single container from two */ template static Container concat(const Container& first, const Container& last) { Container result; result.reserve(first.size() + last.size()); result.append(first); result.append(last); return result; } static const QVector ALL_MARKDOWN_TYPES = concat(concat(SINGLE_SIGN_MARKDOWN, DOUBLE_SIGN_MARKDOWN), MULTI_SIGN_MARKDOWN); static const QVector SINGLE_AND_DOUBLE_MARKDOWN = concat(SINGLE_SIGN_MARKDOWN, DOUBLE_SIGN_MARKDOWN); // any markdown type must work for this data the same way static const QVector COMMON_WORK_CASES { PAIR_FORMAT("%1a%1", "%2%1a%1%3"), PAIR_FORMAT("%1aa%1", "%2%1aa%1%3"), PAIR_FORMAT("%1aaa%1", "%2%1aaa%1%3"), // Must allow same formatting more than one time PAIR_FORMAT("%1aaa%1 %1aaa%1", "%2%1aaa%1%3 %2%1aaa%1%3"), }; static const QVector SINGLE_SIGN_WORK_CASES { PAIR_FORMAT("a %1a%1", "a %2%1a%1%3"), PAIR_FORMAT("%1a%1 a", "%2%1a%1%3 a"), PAIR_FORMAT("a %1a%1 a", "a %2%1a%1%3 a"), // "Lazy" matching PAIR_FORMAT("%1aaa%1 aaa%1", "%2%1aaa%1%3 aaa%4"), }; // only double-sign markdown must work for this data static const QVector DOUBLE_SIGN_WORK_CASES { // Must apply formatting to strings which contain reserved symbols PAIR_FORMAT("%1aaa%2%1", "%3%1aaa%2%1%4"), PAIR_FORMAT("%1%2aaa%1", "%3%1%2aaa%1%4"), PAIR_FORMAT("%1aaa%2aaa%1", "%3%1aaa%2aaa%1%4"), PAIR_FORMAT("%1%2%2aaa%1", "%3%1%2%2aaa%1%4"), PAIR_FORMAT("%1aaa%2%2%1", "%3%1aaa%2%2%1%4"), PAIR_FORMAT("%1aaa%2%2aaa%1", "%3%1aaa%2%2aaa%1%4"), }; // only multi-sign markdown must work for this data static const QVector MULTI_SIGN_WORK_CASES { PAIR_FORMAT("%1int main()\n{ return 0;\n}%1", "%2%1" "int main()\n{ return 0;\n}" "%1%3"), // allows new line and spaces to go right after ``` sequence PAIR_FORMAT("%1\nint main()\n{ return 0;\n}\n%1", "%2%1" "\nint main()\n{ return 0;\n}\n" "%1%3"), PAIR_FORMAT("%1 int main()\n{ return 0;\n} %1", "%2%1" " int main()\n{ return 0;\n} " "%1%3"), }; // any type of markdown must fail for this data static const QVector COMMON_EXCEPTIONS { // No empty formatting string QStringLiteral("%1%1"), // Formatting string must be enclosed by whitespace symbols, newlines or message start/end QStringLiteral("a%1aa%1a"), QStringLiteral("%1aa%1a"), QStringLiteral("a%1aa%1"), QStringLiteral("a %1aa%1a"), QStringLiteral("a%1aa%1 a"), QStringLiteral("a\n%1aa%1a"), QStringLiteral("a%1aa%1\na"), }; static const QVector SINGLE_AND_DOUBLE_SIGN_EXCEPTIONS { // Formatting text must not start/end with whitespace symbols QStringLiteral("%1 %1"), QStringLiteral("%1 a%1"), QStringLiteral("%1a %1"), // No newlines QStringLiteral("%1\n%1"), QStringLiteral("%1aa\n%1"), QStringLiteral("%1\naa%1"), QStringLiteral("%1aa\naa%1"), }; // only single-sign markdown must fail for this data static const QVector SINGLE_SIGN_EXCEPTIONS { // Reserved symbols within formatting string are disallowed QStringLiteral("%1aa%1a%1"), QStringLiteral("%1aa%1%1"), QStringLiteral("%1%1aa%1"), QStringLiteral("%1%1%1"), }; static const QVector MIXED_FORMATTING_SPECIAL_CASES { // Must allow mixed formatting if there is no tag overlap in result PAIR_FORMAT("aaa *aaa /aaa/ aaa*", "aaa aaa aaa aaa"), PAIR_FORMAT("aaa *aaa /aaa* aaa/", "aaa *aaa aaa* aaa"), }; #define MAKE_LINK(url) "" url "" #define MAKE_WWW_LINK(url) "" url "" static const QVector> URL_CASES { PAIR_FORMAT("https://github.com/qTox/qTox/issues/4233", MAKE_LINK("https://github.com/qTox/qTox/issues/4233")), PAIR_FORMAT("www.youtube.com", MAKE_WWW_LINK("www.youtube.com")), PAIR_FORMAT("https://url.com/some*url/some*more*url/", MAKE_LINK("https://url.com/some*url/some*more*url/")), PAIR_FORMAT("https://url.com/some_url/some_more_url/", MAKE_LINK("https://url.com/some_url/some_more_url/")), PAIR_FORMAT("https://url.com/some~url/some~more~url/", MAKE_LINK("https://url.com/some~url/some~more~url/")), // Test case from issue #4275 PAIR_FORMAT("http://www.metacritic.com/game/pc/mass-effect-andromeda\n" "http://www.metacritic.com/game/playstation-4/mass-effect-andromeda\n" "http://www.metacritic.com/game/xbox-one/mass-effect-andromeda", MAKE_LINK("http://www.metacritic.com/game/pc/mass-effect-andromeda") "\n" MAKE_LINK("http://www.metacritic.com/game/playstation-4/mass-effect-andromeda") "\n" MAKE_LINK("http://www.metacritic.com/game/xbox-one/mass-effect-andromeda")), PAIR_FORMAT("http://site.com/part1/part2 " "http://site.com/part3 and one more time " "www.site.com/part1/part2", MAKE_LINK("http://site.com/part1/part2") " " MAKE_LINK("http://site.com/part3") " and one more time " MAKE_WWW_LINK("www.site.com/part1/part2")), PAIR_FORMAT("https://127.0.0.1/asd\n" "https://ABCD:EF01:2345:6789:ABCD:EF01:2345:6789/\n" "ftp://2001:DB8::8:800:200C:417A/\n" "http://::1/\n" "http://::/\n" "https://127.0.0.1:8080/asd " "https://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:8080/ " "ftp://[2001:DB8::8:800:200C:417A]:21/ " "http://[::1]:22/ " "http://[::]:20/ ", MAKE_LINK("https://127.0.0.1/asd") "\n" MAKE_LINK("https://ABCD:EF01:2345:6789:ABCD:EF01:2345:6789/") "\n" MAKE_LINK("ftp://2001:DB8::8:800:200C:417A/") "\n" MAKE_LINK("http://::1/") "\n" MAKE_LINK("http://::/") "\n" MAKE_LINK("https://127.0.0.1:8080/asd") " " MAKE_LINK("https://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:8080/") " " MAKE_LINK("ftp://[2001:DB8::8:800:200C:417A]:21/") " " MAKE_LINK("http://[::1]:22/") " " MAKE_LINK("http://[::]:20/") " " ), // Test case from issue #4853 (include unicode, ending brackets that are part of URL) PAIR_FORMAT("https://ja.wikipedia.org/wiki/印章", MAKE_LINK("https://ja.wikipedia.org/wiki/印章")), PAIR_FORMAT("https://en.wikipedia.org/wiki/Seal_(East_Asia)", MAKE_LINK("https://en.wikipedia.org/wiki/Seal_(East_Asia)")), // Test cases from issue #4295 (exclude surrounding quotes, brackets, ending punctuation) PAIR_FORMAT("(http://www.google.com)", "(" MAKE_LINK("http://www.google.com") ")"), PAIR_FORMAT(""http://www.google.com"", """ MAKE_LINK("http://www.google.com") """), PAIR_FORMAT("http://www.google.com.", MAKE_LINK("http://www.google.com") "."), PAIR_FORMAT("http://www.google.com,", MAKE_LINK("http://www.google.com") ","), PAIR_FORMAT("http://www.google.com?", MAKE_LINK("http://www.google.com") "?"), PAIR_FORMAT("https://google.com?gfe_rd=cr", MAKE_LINK("https://google.com?gfe_rd=cr")), PAIR_FORMAT("["https://en.wikipedia.org/wiki/Seal_(East_Asia)"]?", "["" MAKE_LINK("https://en.wikipedia.org/wiki/Seal_(East_Asia)") ""]?") }; #undef PAIR_FORMAT using MarkdownFunction = QString (*)(const QString&, bool); using InputProcessor = std::function; using OutputProcessor = std::function; /** * @brief Testing cases where markdown must work * @param applyMarkdown Function which is used to apply markdown * @param markdownToTags Which markdown type to test * @param testData Test data - string pairs "Source message - Message after formatting" * @param showSymbols True if it is supposed to leave markdown symbols after formatting, false * otherwise * @param processInput Test data is a template, which must be expanded with concrete markdown * symbols, everytime in different way. This function determines how to expand source message * depending on user need * @param processOutput Same as previous parameter but is applied to markdown output */ static void workCasesTest(MarkdownFunction applyMarkdown, const QVector& markdownToTags, const QVector& testData, bool showSymbols, InputProcessor processInput = nullptr, OutputProcessor processOutput = nullptr) { for (const MarkdownToTags& mtt: markdownToTags) { for (const StringPair& data: testData) { const QString input = processInput != nullptr ? processInput(data.first, mtt) : data.first; QString output = processOutput != nullptr ? processOutput(data.second, mtt, showSymbols) : data.second; QString result = applyMarkdown(input, showSymbols); QVERIFY(output == result); } } } /** * @brief Testing cases where markdown must not to work * @param applyMarkdown Function which is used to apply markdown * @param markdownToTags Which markdown type to test * @param exceptions Collection of "source message - markdown result" pairs representing cases * where markdown must not to work * @param showSymbols True if it is supposed to leave markdown symbols after formatting, false * otherwise */ static void exceptionsTest(MarkdownFunction applyMarkdown, const QVector& markdownToTags, const QVector& exceptions, bool showSymbols) { for (const MarkdownToTags& mtt: markdownToTags) { for (const QString& e: exceptions) { QString processedException = e.arg(mtt.markdownSequence); QVERIFY(processedException == applyMarkdown(processedException, showSymbols)); } } } /** * @brief Testing some uncommon work cases * @param applyMarkdown Function which is used to apply markdown * @param pairs Collection of "source message - markdown result" pairs representing cases where * markdown must not to work */ static void specialCasesTest(MarkdownFunction applyMarkdown, const QVector& pairs) { for (const auto& p : pairs) { QString result = applyMarkdown(p.first, false); QVERIFY(p.second == result); } } using UrlHighlightFunction = QString(*)(const QString&); /** * @brief Function for testing URL highlighting * @param data Test data - map of "URL - HTML-wrapped URL" */ static void urlHighlightTest(UrlHighlightFunction function, const QVector>& data) { for (const QPair& p : data) { QString result = function(p.first); QVERIFY(p.second == result); } } class TestTextFormatter : public QObject { Q_OBJECT private slots: void commonWorkCasesShowSymbols(); void commonWorkCasesHideSymbols(); void singleSignWorkCasesShowSymbols(); void singleSignWorkCasesHideSymbols(); void doubleSignWorkCasesShowSymbols(); void doubleSignWorkCasesHideSymbols(); void multiSignWorkCasesHideSymbols(); void multiSignWorkCasesShowSymbols(); void commonExceptionsShowSymbols(); void commonExceptionsHideSymbols(); void singleSignExceptionsShowSymbols(); void singleSignExceptionsHideSymbols(); void singleAndDoubleMarkdownExceptionsShowSymbols(); void singleAndDoubleMarkdownExceptionsHideSymbols(); void mixedFormattingSpecialCases(); void urlTest(); private: const MarkdownFunction markdownFunction = applyMarkdown; UrlHighlightFunction urlHighlightFunction = highlightURI; }; static QString commonWorkCasesProcessInput(const QString& str, const MarkdownToTags& mtt) { return str.arg(mtt.markdownSequence); } static QString commonWorkCasesProcessOutput(const QString& str, const MarkdownToTags& mtt, bool showSymbols) { const StringPair& tags = mtt.htmlTags; return str.arg(showSymbols ? mtt.markdownSequence : QString{}).arg(tags.first).arg(tags.second); } void TestTextFormatter::commonWorkCasesShowSymbols() { workCasesTest(markdownFunction, ALL_MARKDOWN_TYPES, COMMON_WORK_CASES, true, commonWorkCasesProcessInput, commonWorkCasesProcessOutput); } void TestTextFormatter::commonWorkCasesHideSymbols() { workCasesTest(markdownFunction, ALL_MARKDOWN_TYPES, COMMON_WORK_CASES, false, commonWorkCasesProcessInput, commonWorkCasesProcessOutput); } static QString singleSignWorkCasesProcessInput(const QString& str, const MarkdownToTags& mtt) { return str.arg(mtt.markdownSequence); } static QString singleSignWorkCasesProcessOutput(const QString& str, const MarkdownToTags& mtt, bool showSymbols) { const StringPair& tags = mtt.htmlTags; return str.arg(showSymbols ? mtt.markdownSequence : "") .arg(tags.first) .arg(tags.second) .arg(mtt.markdownSequence); } void TestTextFormatter::singleSignWorkCasesShowSymbols() { workCasesTest(markdownFunction, SINGLE_SIGN_MARKDOWN, SINGLE_SIGN_WORK_CASES, true, singleSignWorkCasesProcessInput, singleSignWorkCasesProcessOutput); } void TestTextFormatter::singleSignWorkCasesHideSymbols() { workCasesTest(markdownFunction, SINGLE_SIGN_MARKDOWN, SINGLE_SIGN_WORK_CASES, false, singleSignWorkCasesProcessInput, singleSignWorkCasesProcessOutput); } static QString doubleSignWorkCasesProcessInput(const QString& str, const MarkdownToTags& mtt) { return str.arg(mtt.markdownSequence).arg(mtt.markdownSequence[0]); } static QString doubleSignWorkCasesProcessOutput(const QString& str, const MarkdownToTags& mtt, bool showSymbols) { const StringPair& tags = mtt.htmlTags; return str.arg(showSymbols ? mtt.markdownSequence : "") .arg(mtt.markdownSequence[0]) .arg(tags.first) .arg(tags.second); } void TestTextFormatter::doubleSignWorkCasesShowSymbols() { workCasesTest(markdownFunction, DOUBLE_SIGN_MARKDOWN, DOUBLE_SIGN_WORK_CASES, true, doubleSignWorkCasesProcessInput, doubleSignWorkCasesProcessOutput); } void TestTextFormatter::doubleSignWorkCasesHideSymbols() { workCasesTest(markdownFunction, DOUBLE_SIGN_MARKDOWN, DOUBLE_SIGN_WORK_CASES, false, doubleSignWorkCasesProcessInput, doubleSignWorkCasesProcessOutput); } static QString multiSignWorkCasesProcessInput(const QString& str, const MarkdownToTags& mtt) { return str.arg(mtt.markdownSequence); } static QString multiSignWorkCasesProcessOutput(const QString& str, const MarkdownToTags& mtt, bool showSymbols) { const auto tags = mtt.htmlTags; return str.arg(showSymbols ? mtt.markdownSequence : "", tags.first, tags.second); } void TestTextFormatter::multiSignWorkCasesHideSymbols() { workCasesTest(markdownFunction, MULTI_SIGN_MARKDOWN, MULTI_SIGN_WORK_CASES, false, multiSignWorkCasesProcessInput, multiSignWorkCasesProcessOutput); } void TestTextFormatter::multiSignWorkCasesShowSymbols() { workCasesTest(markdownFunction, MULTI_SIGN_MARKDOWN, MULTI_SIGN_WORK_CASES, true, multiSignWorkCasesProcessInput, multiSignWorkCasesProcessOutput); } void TestTextFormatter::commonExceptionsShowSymbols() { exceptionsTest(markdownFunction, ALL_MARKDOWN_TYPES, COMMON_EXCEPTIONS, true); } void TestTextFormatter::commonExceptionsHideSymbols() { exceptionsTest(markdownFunction, ALL_MARKDOWN_TYPES, COMMON_EXCEPTIONS, false); } void TestTextFormatter::singleSignExceptionsShowSymbols() { exceptionsTest(markdownFunction, SINGLE_SIGN_MARKDOWN, SINGLE_SIGN_EXCEPTIONS, true); } void TestTextFormatter::singleSignExceptionsHideSymbols() { exceptionsTest(markdownFunction, SINGLE_SIGN_MARKDOWN, SINGLE_SIGN_EXCEPTIONS, false); } void TestTextFormatter::singleAndDoubleMarkdownExceptionsShowSymbols() { exceptionsTest(markdownFunction, SINGLE_AND_DOUBLE_MARKDOWN, SINGLE_AND_DOUBLE_SIGN_EXCEPTIONS, true); } void TestTextFormatter::singleAndDoubleMarkdownExceptionsHideSymbols() { exceptionsTest(markdownFunction, SINGLE_AND_DOUBLE_MARKDOWN, SINGLE_AND_DOUBLE_SIGN_EXCEPTIONS, false); } void TestTextFormatter::mixedFormattingSpecialCases() { specialCasesTest(markdownFunction, MIXED_FORMATTING_SPECIAL_CASES); } void TestTextFormatter::urlTest() { urlHighlightTest(urlHighlightFunction, URL_CASES); } QTEST_GUILESS_MAIN(TestTextFormatter) #include "textformatter_test.moc" qTox/test/core/000077500000000000000000000000001415623743500137325ustar00rootroot00000000000000qTox/test/core/contactid_test.cpp000066400000000000000000000053541415623743500174540ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/contactid.h" #include "src/core/toxpk.h" #include "src/core/toxid.h" #include "src/core/groupid.h" #include #include #include #include const uint8_t testPkArray[32] = {0xC7, 0x71, 0x9C, 0x68, 0x08, 0xC1, 0x4B, 0x77, 0x34, 0x80, 0x04, 0x95, 0x6D, 0x1D, 0x98, 0x04, 0x6C, 0xE0, 0x9A, 0x34, 0x37, 0x0E, 0x76, 0x08, 0x15, 0x0E, 0xAD, 0x74, 0xC3, 0x81, 0x5D, 0x30}; const QString testStr = QStringLiteral("C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30"); const QByteArray testPk = QByteArray::fromHex(testStr.toLatin1()); const QString echoStr = QStringLiteral("76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39"); const QByteArray echoPk = QByteArray::fromHex(echoStr.toLatin1()); class TestContactId : public QObject { Q_OBJECT private slots: void toStringTest(); void equalTest(); void clearTest(); void copyTest(); void dataTest(); void sizeTest(); }; void TestContactId::toStringTest() { ToxPk pk(testPk); QVERIFY(testStr == pk.toString()); } void TestContactId::equalTest() { ToxPk pk1(testPk); ToxPk pk2(testPk); ToxPk pk3(echoPk); QVERIFY(pk1 == pk2); QVERIFY(pk1 != pk3); QVERIFY(!(pk1 != pk2)); } void TestContactId::clearTest() { ToxPk empty; ToxPk pk(testPk); QVERIFY(empty.isEmpty()); QVERIFY(!pk.isEmpty()); } void TestContactId::copyTest() { ToxPk src(testPk); ToxPk copy = src; QVERIFY(copy == src); } void TestContactId::dataTest() { ToxPk pk(testPk); QVERIFY(testPk == pk.getByteArray()); for (int i = 0; i < pk.getSize(); i++) { QVERIFY(testPkArray[i] == pk.getData()[i]); } } void TestContactId::sizeTest() { ToxPk pk; GroupId id; QVERIFY(pk.getSize() == TOX_PUBLIC_KEY_SIZE); QVERIFY(id.getSize() == TOX_CONFERENCE_UID_SIZE); } QTEST_GUILESS_MAIN(TestContactId) #include "contactid_test.moc" qTox/test/core/core_test.cpp000066400000000000000000000105051415623743500164260ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/core.h" #include "src/core/toxoptions.h" #include "src/core/icoresettings.h" #include "src/net/bootstrapnodeupdater.h" #include #include #include #include #include #include Q_DECLARE_METATYPE(QList); class MockSettings : public QObject, public ICoreSettings { Q_OBJECT public: MockSettings() { Q_INIT_RESOURCE(res); qRegisterMetaType>("QList"); } bool getEnableIPv6() const override { return false; } void setEnableIPv6(bool) override { } bool getForceTCP() const override { return false; } void setForceTCP(bool) override { } bool getEnableLanDiscovery() const override { return false; } void setEnableLanDiscovery(bool) override { } QString getProxyAddr() const override { return Addr; } void setProxyAddr(const QString &Addr) override { this->Addr = Addr; } ProxyType getProxyType() const override { return type; } void setProxyType(ProxyType type) override { this->type = type; } quint16 getProxyPort() const override { return port; } void setProxyPort(quint16 port) override { this->port = port; } QNetworkProxy getProxy() const override { return QNetworkProxy(QNetworkProxy::ProxyType::NoProxy); } SIGNAL_IMPL(MockSettings, enableIPv6Changed, bool enabled) SIGNAL_IMPL(MockSettings, forceTCPChanged, bool enabled) SIGNAL_IMPL(MockSettings, enableLanDiscoveryChanged, bool enabled) SIGNAL_IMPL(MockSettings, proxyTypeChanged, ICoreSettings::ProxyType type) SIGNAL_IMPL(MockSettings, proxyAddressChanged, const QString& address) SIGNAL_IMPL(MockSettings, proxyPortChanged, quint16 port) private: QList dhtServerList; QString Addr; ProxyType type; quint16 port; }; class TestCore : public QObject { Q_OBJECT private slots: void startup_without_proxy(); void startup_with_invalid_proxy(); private: /* Test Variables */ Core::ToxCoreErrors* err = nullptr; MockSettings* settings; QByteArray savedata{}; ToxCorePtr test_core; }; namespace { const int timeout = 90000; //90 seconds timeout allowed for test } void TestCore::startup_without_proxy() { settings = new MockSettings(); // No proxy settings->setProxyAddr(""); settings->setProxyPort(0); settings->setProxyType(MockSettings::ProxyType::ptNone); test_core = Core::makeToxCore(savedata, settings, err); if(test_core == nullptr) { QFAIL("ToxCore initialisation failed"); } QSignalSpy spyCore(test_core.get(), &Core::connected); test_core->start(); QVERIFY(spyCore.wait(timeout)); //wait 90seconds QCOMPARE(spyCore.count(), 1); // make sure the signal was emitted exactly one time } void TestCore::startup_with_invalid_proxy() { settings = new MockSettings(); // Test invalid proxy SOCKS5 settings->setProxyAddr("Test"); settings->setProxyPort(9985); settings->setProxyType(MockSettings::ProxyType::ptSOCKS5); test_core = Core::makeToxCore(savedata, settings, err); if(test_core != nullptr) { QFAIL("ToxCore initialisation passed with invalid SOCKS5 proxy address"); } // Test invalid proxy HTTP settings->setProxyAddr("Test"); settings->setProxyPort(9985); settings->setProxyType(MockSettings::ProxyType::ptHTTP); test_core = Core::makeToxCore(savedata, settings, err); if(test_core != nullptr) { QFAIL("ToxCore initialisation passed with invalid HTTP proxy address"); } } QTEST_GUILESS_MAIN(TestCore) #include "core_test.moc" qTox/test/core/toxid_test.cpp000066400000000000000000000047071415623743500166340ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/toxid.h" #include #include #include const QString corrupted = QStringLiteral("C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30C8BA3AB9BEBA"); const QString testToxId = QStringLiteral("C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30C8BA3AB9BEB9"); const QString publicKey = QStringLiteral("C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30"); const QString echoToxId = QStringLiteral("76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6"); class TestToxId : public QObject { Q_OBJECT private slots: void toStringTest(); void equalTest(); void notEqualTest(); void clearTest(); void copyTest(); void validationTest(); }; void TestToxId::toStringTest() { ToxId toxId(testToxId); QVERIFY(testToxId == toxId.toString()); } void TestToxId::equalTest() { ToxId toxId1(testToxId); ToxId toxId2(publicKey); QVERIFY(toxId1 == toxId2); QVERIFY(!(toxId1 != toxId2)); } void TestToxId::notEqualTest() { ToxId toxId1(testToxId); ToxId toxId2(echoToxId); QVERIFY(toxId1 != toxId2); } void TestToxId::clearTest() { ToxId empty; ToxId test(testToxId); QVERIFY(empty != test); test.clear(); QVERIFY(empty == test); } void TestToxId::copyTest() { ToxId src(testToxId); ToxId copy = src; QVERIFY(copy == src); } void TestToxId::validationTest() { QVERIFY(ToxId(testToxId).isValid()); QVERIFY(!ToxId(publicKey).isValid()); QVERIFY(!ToxId(corrupted).isValid()); QString deadbeef = "DEADBEEF"; QVERIFY(!ToxId(deadbeef).isValid()); } QTEST_GUILESS_MAIN(TestToxId) #include "toxid_test.moc" qTox/test/core/toxstring_test.cpp000066400000000000000000000162261415623743500175450ustar00rootroot00000000000000/* Copyright © 2018-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/toxstring.h" #include #include #include class TestToxString : public QObject { Q_OBJECT private slots: void QStringTest(); void QByteArrayTest(); void uint8_tTest(); void emptyQStrTest(); void emptyQByteTest(); void emptyUINT8Test(); void nullptrUINT8Test(); private: /* Test Strings */ //"My Test String" - test text static const QString testStr; static const QByteArray testByte; static const uint8_t* testUINT8; static const size_t lengthUINT8; //"" - empty test text static const QString emptyStr; static const QByteArray emptyByte; static const uint8_t* emptyUINT8; static const size_t emptyLength; }; /* Test Strings */ //"My Test String" - test text const QString TestToxString::testStr = QStringLiteral("My Test String"); const QByteArray TestToxString::testByte = QByteArrayLiteral("My Test String"); const uint8_t* TestToxString::testUINT8 = reinterpret_cast("My Test String"); const size_t TestToxString::lengthUINT8 = 14; //"" - empty test text const QString TestToxString::emptyStr = QStringLiteral(""); const QByteArray TestToxString::emptyByte = QByteArrayLiteral(""); const uint8_t* TestToxString::emptyUINT8 = reinterpret_cast(""); const size_t TestToxString::emptyLength = 0; /** * @brief Use QString as input data, check output: QString, QByteArray, size_t and uint8_t * QVERIFY(expected == result); */ void TestToxString::QStringTest() { //Create Test Object with QString constructor ToxString test(testStr); //Check QString QString test_string = test.getQString(); QVERIFY(testStr == test_string); //Check QByteArray QByteArray test_byte = test.getBytes(); QVERIFY(testByte == test_byte); //Check uint8_t pointer const uint8_t* test_int = test.data(); size_t test_size = test.size(); QVERIFY(lengthUINT8 == test_size); for (int i = 0; i <= lengthUINT8; i++) { QVERIFY(testUINT8[i] == test_int[i]); } } /** * @brief Use QByteArray as input data, check output: QString, QByteArray, size_t and uint8_t * QVERIFY(expected == result); */ void TestToxString::QByteArrayTest() { //Create Test Object with QByteArray constructor ToxString test(testByte); //Check QString QString test_string = test.getQString(); QVERIFY(testStr == test_string); //Check QByteArray QByteArray test_byte = test.getBytes(); QVERIFY(testByte == test_byte); //Check uint8_t pointer const uint8_t* test_int = test.data(); size_t test_size = test.size(); QVERIFY(lengthUINT8 == test_size); for (int i = 0; i <= lengthUINT8; i++) { QVERIFY(testUINT8[i] == test_int[i]); } } /** * @brief Use uint8t* and size_t as input data, check output: QString, QByteArray, size_t and uint8_t * QVERIFY(expected == result); */ void TestToxString::uint8_tTest() { //Create Test Object with uint8_t constructor ToxString test(testUINT8, lengthUINT8); //Check QString QString test_string = test.getQString(); QVERIFY(testStr == test_string); //Check QByteArray QByteArray test_byte = test.getBytes(); QVERIFY(testByte == test_byte); //Check uint8_t pointer const uint8_t* test_int = test.data(); size_t test_size = test.size(); QVERIFY(lengthUINT8 == test_size); for (int i = 0; i <= lengthUINT8; i++) { QVERIFY(testUINT8[i] == test_int[i]); } } /** * @brief Use empty QString as input data, check output: QString, QByteArray, size_t and uint8_t * QVERIFY(expected == result); */ void TestToxString::emptyQStrTest() { //Create Test Object with QString constructor ToxString test(emptyStr); //Check QString QString test_string = test.getQString(); QVERIFY(emptyStr == test_string); //Check QByteArray QByteArray test_byte = test.getBytes(); QVERIFY(emptyByte == test_byte); //Check uint8_t pointer const uint8_t* test_int = test.data(); size_t test_size = test.size(); QVERIFY(emptyLength == test_size); for (int i = 0; i <= emptyLength; i++) { QVERIFY(emptyUINT8[i] == test_int[i]); } } /** * @brief Use empty QByteArray as input data, check output: QString, QByteArray, size_t and uint8_t * QVERIFY(expected == result); */ void TestToxString::emptyQByteTest() { //Create Test Object with QByteArray constructor ToxString test(emptyByte); //Check QString QString test_string = test.getQString(); QVERIFY(emptyStr == test_string); //Check QByteArray QByteArray test_byte = test.getBytes(); QVERIFY(emptyByte == test_byte); //Check uint8_t pointer const uint8_t* test_int = test.data(); size_t test_size = test.size(); QVERIFY(emptyLength == test_size); for (int i = 0; i <= emptyLength; i++) { QVERIFY(emptyUINT8[i] == test_int[i]); } } /** * @brief Use empty uint8_t as input data, check output: QString, QByteArray, size_t and uint8_t * QVERIFY(expected == result); */ void TestToxString::emptyUINT8Test() { //Create Test Object with uint8_t constructor ToxString test(emptyUINT8, emptyLength); //Check QString QString test_string = test.getQString(); QVERIFY(emptyStr == test_string); //Check QByteArray QByteArray test_byte = test.getBytes(); QVERIFY(emptyByte == test_byte); //Check uint8_t pointer const uint8_t* test_int = test.data(); size_t test_size = test.size(); QVERIFY(emptyLength == test_size); for (int i = 0; i <= emptyLength; i++) { QVERIFY(emptyUINT8[i] == test_int[i]); } } /** * @brief Use nullptr and size_t 5 as input data, check output: QString, QByteArray, size_t and uint8_t * QVERIFY(expected == result); */ void TestToxString::nullptrUINT8Test() { //Create Test Object with uint8_t constructor ToxString test(nullptr, 5);//nullptr and length = 5 //Check QString QString test_string = test.getQString(); QVERIFY(emptyStr == test_string); //Check QByteArray QByteArray test_byte = test.getBytes(); QVERIFY(emptyByte == test_byte); //Check uint8_t pointer const uint8_t* test_int = test.data(); size_t test_size = test.size(); QVERIFY(emptyLength == test_size); for (int i = 0; i <= emptyLength; i++) { QVERIFY(emptyUINT8[i] == test_int[i]); } } QTEST_GUILESS_MAIN(TestToxString) #include "toxstring_test.moc" qTox/test/model/000077500000000000000000000000001415623743500141025ustar00rootroot00000000000000qTox/test/model/friendmessagedispatcher_test.cpp000066400000000000000000000171061415623743500225350ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/icorefriendmessagesender.h" #include "src/model/friend.h" #include "src/model/friendmessagedispatcher.h" #include "src/model/message.h" #include #include #include class MockFriendMessageSender : public ICoreFriendMessageSender { public: bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override { if (canSend) { numSentActions++; receipt = receiptNum; receiptNum.get() += 1; } return canSend; } bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override { if (canSend) { numSentMessages++; receipt = receiptNum; receiptNum.get() += 1; } return canSend; } bool canSend = true; ReceiptNum receiptNum{0}; size_t numSentActions = 0; size_t numSentMessages = 0; }; class TestFriendMessageDispatcher : public QObject { Q_OBJECT public: TestFriendMessageDispatcher(); private slots: void init(); void testSignals(); void testMessageSending(); void testOfflineMessages(); void testFailedMessage(); void onMessageSent(DispatchedMessageId id, Message message) { auto it = outgoingMessages.find(id); QVERIFY(it == outgoingMessages.end()); outgoingMessages.emplace(id, std::move(message)); } void onMessageComplete(DispatchedMessageId id) { auto it = outgoingMessages.find(id); QVERIFY(it != outgoingMessages.end()); outgoingMessages.erase(it); } void onMessageReceived(const ToxPk& sender, Message message) { receivedMessages.push_back(std::move(message)); } private: // All unique_ptrs to make construction/init() easier to manage std::unique_ptr f; std::unique_ptr messageSender; std::unique_ptr sharedProcessorParams; std::unique_ptr messageProcessor; std::unique_ptr friendMessageDispatcher; std::map outgoingMessages; std::deque receivedMessages; }; TestFriendMessageDispatcher::TestFriendMessageDispatcher() {} /** * @brief Test initialization. Resets all member variables for a fresh test state */ void TestFriendMessageDispatcher::init() { f = std::unique_ptr(new Friend(0, ToxPk())); f->setStatus(Status::Status::Online); messageSender = std::unique_ptr(new MockFriendMessageSender()); sharedProcessorParams = std::unique_ptr(new MessageProcessor::SharedParams()); messageProcessor = std::unique_ptr(new MessageProcessor(*sharedProcessorParams)); friendMessageDispatcher = std::unique_ptr( new FriendMessageDispatcher(*f, *messageProcessor, *messageSender)); connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageSent, this, &TestFriendMessageDispatcher::onMessageSent); connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageComplete, this, &TestFriendMessageDispatcher::onMessageComplete); connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageReceived, this, &TestFriendMessageDispatcher::onMessageReceived); outgoingMessages = std::map(); receivedMessages = std::deque(); } /** * @brief Tests that the signals emitted by the dispatcher are all emitted at the correct times */ void TestFriendMessageDispatcher::testSignals() { auto startReceiptNum = messageSender->receiptNum; auto sentIds = friendMessageDispatcher->sendMessage(false, "test"); auto endReceiptNum = messageSender->receiptNum; // We should have received some message ids in our callbacks QVERIFY(sentIds.first == sentIds.second); QVERIFY(outgoingMessages.find(sentIds.first) != outgoingMessages.end()); QVERIFY(startReceiptNum.get() != endReceiptNum.get()); QVERIFY(outgoingMessages.size() == 1); QVERIFY(outgoingMessages.begin()->second.isAction == false); QVERIFY(outgoingMessages.begin()->second.content == "test"); for (auto i = startReceiptNum; i < endReceiptNum; ++i.get()) { friendMessageDispatcher->onReceiptReceived(i); } // If our completion ids were hooked up right this should be empty QVERIFY(outgoingMessages.empty()); // If signals are emitted correctly we should have one message in our received message buffer QVERIFY(receivedMessages.empty()); friendMessageDispatcher->onMessageReceived(false, "test2"); QVERIFY(!receivedMessages.empty()); QVERIFY(receivedMessages.front().isAction == false); QVERIFY(receivedMessages.front().content == "test2"); } /** * @brief Tests that sent messages actually go through to core */ void TestFriendMessageDispatcher::testMessageSending() { friendMessageDispatcher->sendMessage(false, "Test"); QVERIFY(messageSender->numSentMessages == 1); QVERIFY(messageSender->numSentActions == 0); friendMessageDispatcher->sendMessage(true, "Test"); QVERIFY(messageSender->numSentMessages == 1); QVERIFY(messageSender->numSentActions == 1); } /** * @brief Tests that messages dispatched while a friend is offline are sent later */ void TestFriendMessageDispatcher::testOfflineMessages() { f->setStatus(Status::Status::Offline); auto firstReceipt = messageSender->receiptNum; friendMessageDispatcher->sendMessage(false, "test"); friendMessageDispatcher->sendMessage(false, "test2"); friendMessageDispatcher->sendMessage(true, "test3"); QVERIFY(messageSender->numSentActions == 0); QVERIFY(messageSender->numSentMessages == 0); QVERIFY(outgoingMessages.size() == 3); f->setStatus(Status::Status::Online); QVERIFY(messageSender->numSentActions == 1); QVERIFY(messageSender->numSentMessages == 2); QVERIFY(outgoingMessages.size() == 3); auto lastReceipt = messageSender->receiptNum; for (auto i = firstReceipt; i < lastReceipt; ++i.get()) { friendMessageDispatcher->onReceiptReceived(i); } QVERIFY(messageSender->numSentActions == 1); QVERIFY(messageSender->numSentMessages == 2); QVERIFY(outgoingMessages.size() == 0); } /** * @brief Tests that messages that failed to send due to toxcore are resent later */ void TestFriendMessageDispatcher::testFailedMessage() { messageSender->canSend = false; friendMessageDispatcher->sendMessage(false, "test"); QVERIFY(messageSender->numSentMessages == 0); messageSender->canSend = true; f->setStatus(Status::Status::Offline); f->setStatus(Status::Status::Online); QVERIFY(messageSender->numSentMessages == 1); } QTEST_GUILESS_MAIN(TestFriendMessageDispatcher) #include "friendmessagedispatcher_test.moc" qTox/test/model/groupmessagedispatcher_test.cpp000066400000000000000000000224721415623743500224240ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/icoregroupmessagesender.h" #include "src/model/group.h" #include "src/model/groupmessagedispatcher.h" #include "src/model/message.h" #include "src/persistence/settings.h" #include #include #include #include class MockGroupMessageSender : public ICoreGroupMessageSender { public: void sendGroupAction(int groupId, const QString& action) override { numSentActions++; } void sendGroupMessage(int groupId, const QString& message) override { numSentMessages++; } size_t numSentActions = 0; size_t numSentMessages = 0; }; /** * Mock 1 peer at group number 0 */ class MockGroupQuery : public ICoreGroupQuery { public: GroupId getGroupPersistentId(uint32_t groupNumber) const override { return GroupId(0); } uint32_t getGroupNumberPeers(int groupId) const override { if (emptyGroup) { return 1; } return 2; } QString getGroupPeerName(int groupId, int peerId) const override { return QString("peer") + peerId; } ToxPk getGroupPeerPk(int groupId, int peerId) const override { uint8_t id[TOX_PUBLIC_KEY_SIZE] = {static_cast(peerId)}; return ToxPk(id); } QStringList getGroupPeerNames(int groupId) const override { if (emptyGroup) { return QStringList({QString("me")}); } return QStringList({QString("me"), QString("other")}); } bool getGroupAvEnabled(int groupId) const override { return false; } void setAsEmptyGroup() { emptyGroup = true; } void setAsFunctionalGroup() { emptyGroup = false; } private: bool emptyGroup = false; }; class MockCoreIdHandler : public ICoreIdHandler { public: ToxId getSelfId() const override { std::terminate(); return ToxId(); } ToxPk getSelfPublicKey() const override { static uint8_t id[TOX_PUBLIC_KEY_SIZE] = {0}; return ToxPk(id); } QString getUsername() const override { return "me"; } }; class MockGroupSettings : public IGroupSettings { public: QStringList getBlackList() const override { return blacklist; } void setBlackList(const QStringList& blist) override { blacklist = blist; } bool getGroupAlwaysNotify() const override { return false; } void setGroupAlwaysNotify(bool newValue) override {} private: QStringList blacklist; }; class TestGroupMessageDispatcher : public QObject { Q_OBJECT public: TestGroupMessageDispatcher(); private slots: void init(); void testSignals(); void testMessageSending(); void testEmptyGroup(); void testSelfReceive(); void testBlacklist(); void onMessageSent(DispatchedMessageId id, Message message) { auto it = outgoingMessages.find(id); QVERIFY(it == outgoingMessages.end()); outgoingMessages.emplace(id); sentMessages.push_back(std::move(message)); } void onMessageComplete(DispatchedMessageId id) { auto it = outgoingMessages.find(id); QVERIFY(it != outgoingMessages.end()); outgoingMessages.erase(it); } void onMessageReceived(const ToxPk& sender, Message message) { receivedMessages.push_back(std::move(message)); } private: // All unique_ptrs to make construction/init() easier to manage std::unique_ptr groupSettings; std::unique_ptr groupQuery; std::unique_ptr coreIdHandler; std::unique_ptr g; std::unique_ptr messageSender; std::unique_ptr sharedProcessorParams; std::unique_ptr messageProcessor; std::unique_ptr groupMessageDispatcher; std::set outgoingMessages; std::deque sentMessages; std::deque receivedMessages; }; TestGroupMessageDispatcher::TestGroupMessageDispatcher() {} /** * @brief Test initialization. Resets all members to initial state */ void TestGroupMessageDispatcher::init() { groupSettings = std::unique_ptr(new MockGroupSettings()); groupQuery = std::unique_ptr(new MockGroupQuery()); coreIdHandler = std::unique_ptr(new MockCoreIdHandler()); g = std::unique_ptr( new Group(0, GroupId(0), "TestGroup", false, "me", *groupQuery, *coreIdHandler)); messageSender = std::unique_ptr(new MockGroupMessageSender()); sharedProcessorParams = std::unique_ptr(new MessageProcessor::SharedParams()); messageProcessor = std::unique_ptr(new MessageProcessor(*sharedProcessorParams)); groupMessageDispatcher = std::unique_ptr( new GroupMessageDispatcher(*g, *messageProcessor, *coreIdHandler, *messageSender, *groupSettings)); connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageSent, this, &TestGroupMessageDispatcher::onMessageSent); connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageComplete, this, &TestGroupMessageDispatcher::onMessageComplete); connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageReceived, this, &TestGroupMessageDispatcher::onMessageReceived); outgoingMessages = std::set(); sentMessages = std::deque(); receivedMessages = std::deque(); } /** * @brief Tests that the signals emitted by the dispatcher are all emitted at the correct times */ void TestGroupMessageDispatcher::testSignals() { groupMessageDispatcher->sendMessage(false, "test"); // For groups we pair our sent and completed signals since we have no receiver reports QVERIFY(outgoingMessages.size() == 0); QVERIFY(!sentMessages.empty()); QVERIFY(sentMessages.front().isAction == false); QVERIFY(sentMessages.front().content == "test"); // If signals are emitted correctly we should have one message in our received message buffer QVERIFY(receivedMessages.empty()); groupMessageDispatcher->onMessageReceived(ToxPk(), false, "test2"); QVERIFY(!receivedMessages.empty()); QVERIFY(receivedMessages.front().isAction == false); QVERIFY(receivedMessages.front().content == "test2"); } /** * @brief Tests that sent messages actually go through to core */ void TestGroupMessageDispatcher::testMessageSending() { groupMessageDispatcher->sendMessage(false, "Test"); QVERIFY(messageSender->numSentMessages == 1); QVERIFY(messageSender->numSentActions == 0); groupMessageDispatcher->sendMessage(true, "Test"); QVERIFY(messageSender->numSentMessages == 1); QVERIFY(messageSender->numSentActions == 1); } /** * @brief Tests that if we are the only member in a group we do _not_ send messages to core. Toxcore * isn't too happy if we send messages and we're the only one in the group */ void TestGroupMessageDispatcher::testEmptyGroup() { groupQuery->setAsEmptyGroup(); g->regeneratePeerList(); groupMessageDispatcher->sendMessage(false, "Test"); groupMessageDispatcher->sendMessage(true, "Test"); QVERIFY(messageSender->numSentMessages == 0); QVERIFY(messageSender->numSentActions == 0); } /** * @brief Tests that we do not emit any signals if we receive a message from ourself. Toxcore will send us back messages we sent */ void TestGroupMessageDispatcher::testSelfReceive() { uint8_t selfId[TOX_PUBLIC_KEY_SIZE] = {0}; groupMessageDispatcher->onMessageReceived(ToxPk(selfId), false, "Test"); QVERIFY(receivedMessages.size() == 0); uint8_t id[TOX_PUBLIC_KEY_SIZE] = {1}; groupMessageDispatcher->onMessageReceived(ToxPk(id), false, "Test"); QVERIFY(receivedMessages.size() == 1); } /** * @brief Tests that messages from blacklisted peers do not get propogated from the dispatcher */ void TestGroupMessageDispatcher::testBlacklist() { uint8_t id[TOX_PUBLIC_KEY_SIZE] = {1}; auto otherPk = ToxPk(id); groupMessageDispatcher->onMessageReceived(otherPk, false, "Test"); QVERIFY(receivedMessages.size() == 1); groupSettings->setBlackList({otherPk.toString()}); groupMessageDispatcher->onMessageReceived(otherPk, false, "Test"); QVERIFY(receivedMessages.size() == 1); } // Cannot be guiless due to a settings instance in GroupMessageDispatcher QTEST_GUILESS_MAIN(TestGroupMessageDispatcher) #include "groupmessagedispatcher_test.moc" qTox/test/model/messageprocessor_test.cpp000066400000000000000000000124021415623743500212300ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/model/message.h" #include #include #include #include namespace { bool messageHasSelfMention(const Message& message) { return std::any_of(message.metadata.begin(), message.metadata.end(), [](MessageMetadata meta) { return meta.type == MessageMetadataType::selfMention; }); } } // namespace class TestMessageProcessor : public QObject { Q_OBJECT public: TestMessageProcessor(){}; private slots: void testSelfMention(); void testOutgoingMessage(); void testIncomingMessage(); }; /** * @brief Tests detection of username */ void TestMessageProcessor::testSelfMention() { MessageProcessor::SharedParams sharedParams; const QLatin1String testUserName{"MyUserName"}; const QLatin1String testToxPk{ "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"}; sharedParams.onUserNameSet(testUserName); sharedParams.setPublicKey(testToxPk); auto messageProcessor = MessageProcessor(sharedParams); messageProcessor.enableMentions(); for (const auto& str : {testUserName, testToxPk}) { // Using my name or public key should match auto processedMessage = messageProcessor.processIncomingMessage(false, str % " hi"); QVERIFY(messageHasSelfMention(processedMessage)); // Action messages should match too processedMessage = messageProcessor.processIncomingMessage(true, str % " hi"); QVERIFY(messageHasSelfMention(processedMessage)); // Too much text shouldn't match processedMessage = messageProcessor.processIncomingMessage(false, str % "2"); QVERIFY(!messageHasSelfMention(processedMessage)); // Unless it's a colon processedMessage = messageProcessor.processIncomingMessage(false, str % ": test"); QVERIFY(messageHasSelfMention(processedMessage)); // remove last character QString chopped = str; chopped.chop(1); // Too little text shouldn't match processedMessage = messageProcessor.processIncomingMessage(false, chopped); QVERIFY(!messageHasSelfMention(processedMessage)); // make lower case QString lower = QString(str).toLower(); // The regex should be case insensitive processedMessage = messageProcessor.processIncomingMessage(false, lower % " hi"); QVERIFY(messageHasSelfMention(processedMessage)); } // New user name changes should be detected sharedParams.onUserNameSet("NewUserName"); auto processedMessage = messageProcessor.processIncomingMessage(false, "NewUserName: hi"); QVERIFY(messageHasSelfMention(processedMessage)); // Special characters should be removed sharedParams.onUserNameSet("New\nUserName"); processedMessage = messageProcessor.processIncomingMessage(false, "NewUserName: hi"); QVERIFY(messageHasSelfMention(processedMessage)); // Regression tests for: https://github.com/qTox/qTox/issues/2119 { // Empty usernames should not match sharedParams.onUserNameSet(""); processedMessage = messageProcessor.processIncomingMessage(false, ""); QVERIFY(!messageHasSelfMention(processedMessage)); // Empty usernames matched on everything, ensure this is not the case processedMessage = messageProcessor.processIncomingMessage(false, "a"); QVERIFY(!messageHasSelfMention(processedMessage)); } } /** * @brief Tests behavior of the processor for outgoing messages */ void TestMessageProcessor::testOutgoingMessage() { auto sharedParams = MessageProcessor::SharedParams(); auto messageProcessor = MessageProcessor(sharedParams); QString testStr; for (size_t i = 0; i < tox_max_message_length() + 50; ++i) { testStr += "a"; } auto messages = messageProcessor.processOutgoingMessage(false, testStr); // The message processor should split our messages QVERIFY(messages.size() == 2); } /** * @brief Tests behavior of the processor for incoming messages */ void TestMessageProcessor::testIncomingMessage() { // Nothing too special happening on the incoming side if we aren't looking for self mentions auto sharedParams = MessageProcessor::SharedParams(); auto messageProcessor = MessageProcessor(sharedParams); auto message = messageProcessor.processIncomingMessage(false, "test"); QVERIFY(message.isAction == false); QVERIFY(message.content == "test"); QVERIFY(message.timestamp.isValid()); } QTEST_GUILESS_MAIN(TestMessageProcessor) #include "messageprocessor_test.moc" qTox/test/model/sessionchatlog_test.cpp000066400000000000000000000103351415623743500206740ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/model/ichatlog.h" #include "src/model/imessagedispatcher.h" #include "src/model/sessionchatlog.h" #include namespace { Message createMessage(const QString& content) { Message message; message.content = content; message.isAction = false; message.timestamp = QDateTime::currentDateTime(); return message; } class MockCoreIdHandler : public ICoreIdHandler { public: ToxId getSelfId() const override { std::terminate(); return ToxId(); } ToxPk getSelfPublicKey() const override { static uint8_t id[TOX_PUBLIC_KEY_SIZE] = {5}; return ToxPk(id); } QString getUsername() const override { std::terminate(); return QString(); } }; } // namespace class TestSessionChatLog : public QObject { Q_OBJECT public: TestSessionChatLog(){}; private slots: void init(); void testSanity(); private: MockCoreIdHandler idHandler; std::unique_ptr chatLog; }; /** * @brief Test initialiation, resets the chatlog */ void TestSessionChatLog::init() { chatLog = std::unique_ptr(new SessionChatLog(idHandler)); } /** * @brief Quick sanity test that the chatlog is working as expected. Tests basic insertion, retrieval, and searching of messages */ void TestSessionChatLog::testSanity() { /* ChatLogIdx(0) */ chatLog->onMessageSent(DispatchedMessageId(0), createMessage("test")); /* ChatLogIdx(1) */ chatLog->onMessageSent(DispatchedMessageId(1), createMessage("test test")); /* ChatLogIdx(2) */ chatLog->onMessageReceived(ToxPk(), createMessage("test2")); /* ChatLogIdx(3) */ chatLog->onFileUpdated(ToxPk(), ToxFile()); /* ChatLogIdx(4) */ chatLog->onMessageSent(DispatchedMessageId(2), createMessage("test3")); /* ChatLogIdx(5) */ chatLog->onMessageSent(DispatchedMessageId(3), createMessage("test4")); /* ChatLogIdx(6) */ chatLog->onMessageSent(DispatchedMessageId(4), createMessage("test")); /* ChatLogIdx(7) */ chatLog->onMessageReceived(ToxPk(), createMessage("test5")); QVERIFY(chatLog->getNextIdx() == ChatLogIdx(8)); QVERIFY(chatLog->at(ChatLogIdx(3)).getContentType() == ChatLogItem::ContentType::fileTransfer); QVERIFY(chatLog->at(ChatLogIdx(7)).getContentType() == ChatLogItem::ContentType::message); auto searchPos = SearchPos{ChatLogIdx(1), 0}; auto searchResult = chatLog->searchForward(searchPos, "test", ParameterSearch()); QVERIFY(searchResult.found); QVERIFY(searchResult.len == 4); QVERIFY(searchResult.pos.logIdx == ChatLogIdx(1)); QVERIFY(searchResult.start == 0); searchPos = searchResult.pos; searchResult = chatLog->searchForward(searchPos, "test", ParameterSearch()); QVERIFY(searchResult.found); QVERIFY(searchResult.len == 4); QVERIFY(searchResult.pos.logIdx == ChatLogIdx(1)); QVERIFY(searchResult.start == 5); searchPos = searchResult.pos; searchResult = chatLog->searchForward(searchPos, "test", ParameterSearch()); QVERIFY(searchResult.found); QVERIFY(searchResult.len == 4); QVERIFY(searchResult.pos.logIdx == ChatLogIdx(2)); QVERIFY(searchResult.start == 0); searchPos = searchResult.pos; searchResult = chatLog->searchBackward(searchPos, "test", ParameterSearch()); QVERIFY(searchResult.found); QVERIFY(searchResult.len == 4); QVERIFY(searchResult.pos.logIdx == ChatLogIdx(1)); QVERIFY(searchResult.start == 5); } QTEST_GUILESS_MAIN(TestSessionChatLog) #include "sessionchatlog_test.moc" qTox/test/net/000077500000000000000000000000001415623743500135705ustar00rootroot00000000000000qTox/test/net/bsu_test.cpp000066400000000000000000000041461415623743500161310ustar00rootroot00000000000000/* Copyright © 2018-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/net/bootstrapnodeupdater.h" #include #include #include // Needed to make this type known to Qt Q_DECLARE_METATYPE(QList); class TestBootstrapNodesUpdater : public QObject { Q_OBJECT public: TestBootstrapNodesUpdater(); private slots: void testOnline(); void testLocal(); }; TestBootstrapNodesUpdater::TestBootstrapNodesUpdater() { qRegisterMetaType>("QList"); // Contains the builtin nodes list Q_INIT_RESOURCE(res); } void TestBootstrapNodesUpdater::testOnline() { QNetworkProxy proxy{QNetworkProxy::ProxyType::NoProxy}; BootstrapNodeUpdater updater{proxy}; QSignalSpy spy(&updater, &BootstrapNodeUpdater::availableBootstrapNodes); updater.requestBootstrapNodes(); spy.wait(10000); // increase wait time for speradic CI failures with slow nodes server QCOMPARE(spy.count(), 1); // make sure the signal was emitted exactly one time QList result = qvariant_cast>(spy.at(0).at(0)); QVERIFY(result.size() > 0); // some data should be returned } void TestBootstrapNodesUpdater::testLocal() { QList defaultNodes = BootstrapNodeUpdater::loadDefaultBootstrapNodes(); QVERIFY(defaultNodes.size() > 0); } QTEST_GUILESS_MAIN(TestBootstrapNodesUpdater) #include "bsu_test.moc" qTox/test/persistence/000077500000000000000000000000001415623743500153265ustar00rootroot00000000000000qTox/test/persistence/dbschema_test.cpp000066400000000000000000000377321415623743500206530ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/persistence/db/rawdatabase.h" // normally we should only test public API instead of implementation, but there's no reason to expose db schema // upgrade externally, and the complexity of each version upgrade benefits from being individually testable #include "src/persistence/history.cpp" #include #include #include #include struct SqliteMasterEntry { QString name; QString sql; }; bool operator==(const SqliteMasterEntry& lhs, const SqliteMasterEntry& rhs) { return lhs.name == rhs.name && lhs.sql == rhs.sql; } class TestDbSchema : public QObject { Q_OBJECT private slots: void initTestCase(); void testCreation(); void testIsNewDb(); void test0to1(); void test1to2(); void test2to3(); void test3to4(); void cleanupTestCase(); private: bool initSucess{false}; void createSchemaAtVersion(std::shared_ptr, const std::vector& schema); void verifyDb(std::shared_ptr db, const std::vector& expectedSql); }; const QString testFileList[] = { "testCreation.db", "testIsNewDbTrue.db", "testIsNewDbFalse.db", "test0to1.db", "test1to2.db", "test2to3.db", "test3to4.db" }; // db schemas can be select with "SELECT name, sql FROM sqlite_master;" on the database. const std::vector schema0 { {"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"}, {"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"}, {"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL)"}, {"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"} }; // added file transfer history const std::vector schema1 { {"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"}, {"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"}, {"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"}, {"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER)"}, {"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"} }; // move stuck faux offline messages do a table of "broken" messages const std::vector schema2 { {"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"}, {"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"}, {"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"}, {"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER)"}, {"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"}, {"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY)"} }; // move stuck 0-length action messages to the existing "broken_messages" table. Not a real schema upgrade. const auto schema3 = schema2; // create index in history table on chat_id to improve query speed. Not a real schema upgrade. const std::vector schema4 { {"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"}, {"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"}, {"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"}, {"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER)"}, {"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"}, {"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY)"}, {"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"} }; void TestDbSchema::initTestCase() { for (const auto& path : testFileList) { QVERIFY(!QFileInfo{path}.exists()); } initSucess = true; } void TestDbSchema::cleanupTestCase() { if (!initSucess) { qWarning() << "init failed, skipping cleanup to avoid loss of data"; return; } for (const auto& path : testFileList) { QFile::remove(path); } } void TestDbSchema::verifyDb(std::shared_ptr db, const std::vector& expectedSql) { QVERIFY(db->execNow(RawDatabase::Query(QStringLiteral( "SELECT name, sql FROM sqlite_master;"), [&](const QVector& row) { const QString tableName = row[0].toString(); if (row[1].isNull()) { // implicit indexes are automatically created for primary key constraints and unique constraints // so their existence is already covered by the table creation SQL return; } QString tableSql = row[1].toString(); // table and column names can be quoted. UPDATE TEABLE automatically quotes the new names, but this // has no functional impact on the schema. Strip quotes for comparison so that our created schema // matches schema made from UPDATE TABLEs. const QString unquotedTableSql = tableSql.remove("\""); SqliteMasterEntry entry{tableName, unquotedTableSql}; QVERIFY(std::find(expectedSql.begin(), expectedSql.end(), entry) != expectedSql.end()); }))); } void TestDbSchema::createSchemaAtVersion(std::shared_ptr db, const std::vector& schema) { QVector queries; for (auto const& entry : schema) { queries += entry.sql; } QVERIFY(db->execNow(queries)); } void TestDbSchema::testCreation() { QVector queries; auto db = std::shared_ptr{new RawDatabase{"testCreation.db", {}, {}}}; QVERIFY(createCurrentSchema(*db)); verifyDb(db, schema4); } void TestDbSchema::testIsNewDb() { auto db = std::shared_ptr{new RawDatabase{"testIsNewDbTrue.db", {}, {}}}; bool success = false; bool newDb = isNewDb(db, success); QVERIFY(success); QVERIFY(newDb == true); db = std::shared_ptr{new RawDatabase{"testIsNewDbFalse.db", {}, {}}}; createSchemaAtVersion(db, schema0); newDb = isNewDb(db, success); QVERIFY(success); QVERIFY(newDb == false); } void TestDbSchema::test0to1() { auto db = std::shared_ptr{new RawDatabase{"test0to1.db", {}, {}}}; createSchemaAtVersion(db, schema0); QVERIFY(dbSchema0to1(*db)); verifyDb(db, schema1); } void TestDbSchema::test1to2() { /* Due to a long standing bug, faux offline message have been able to become stuck going back years. Because of recent fixes to history loading, faux offline messages will correctly all be sent on connection, but this causes an issue of long stuck messages suddenly being delivered to a friend, out of context, creating a confusing interaction. To work around this, this upgrade moves any faux offline messages in a chat that are older than the last successfully delivered message, indicating they were stuck, to a new table, `broken_messages`, preventing them from ever being sent in the future. https://github.com/qTox/qTox/issues/5776 */ auto db = std::shared_ptr{new RawDatabase{"test1to2.db", {}, {}}}; createSchemaAtVersion(db, schema1); const QString myPk = "AC18841E56CCDEE16E93E10E6AB2765BE54277D67F1372921B5B418A6B330D3D"; const QString friend1Pk = "FE34BC6D87B66E958C57BBF205F9B79B62BE0AB8A4EFC1F1BB9EC4D0D8FB0663"; const QString friend2Pk = "2A1CBCE227549459C0C20F199DB86AD9BCC436D35BAA1825FFD4B9CA3290D200"; QVector queries; queries += QString("INSERT INTO peers (id, public_key) VALUES (%1, '%2')").arg(0).arg(myPk); queries += QString("INSERT INTO peers (id, public_key) VALUES (%1, '%2')").arg(1).arg(friend1Pk); queries += QString("INSERT INTO peers (id, public_key) VALUES (%1, '%2')").arg(2).arg(friend2Pk); // friend 1 // first message in chat is pending - but the second is delivered. This message is "broken" queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (1, 1, 1, ?, 0)", {"first message in chat, pending and stuck"}}; queries += {"INSERT INTO faux_offline_pending (id) VALUES (" " last_insert_rowid()" ");"}; // second message is delivered, causing the first to be considered broken queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (2, 2, 1, ?, 0)", {"second message in chat, delivered"}}; // third message is pending - this is a normal pending message. It should be untouched. queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (3, 3, 1, ?, 0)", {"third message in chat, pending"}}; queries += {"INSERT INTO faux_offline_pending (id) VALUES (" " last_insert_rowid()" ");"}; // friend 2 // first message is delivered. queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (4, 4, 2, ?, 2)", {"first message by friend in chat, delivered"}}; // second message is also delivered. queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (5, 5, 2, ?, 0)", {"first message by us in chat, delivered"}}; // third message is pending, but not broken since there are no delivered messages after it. queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (6, 6, 2, ?, 0)", {"last message in chat, by us, pending"}}; queries += {"INSERT INTO faux_offline_pending (id) VALUES (" " last_insert_rowid()" ");"}; QVERIFY(db->execNow(queries)); QVERIFY(dbSchema1to2(*db)); verifyDb(db, schema2); long brokenCount = -1; RawDatabase::Query brokenCountQuery = {"SELECT COUNT(*) FROM broken_messages;", [&](const QVector& row) { brokenCount = row[0].toLongLong(); }}; QVERIFY(db->execNow(brokenCountQuery)); QVERIFY(brokenCount == 1); // only friend 1's first message is "broken" int fauxOfflineCount = -1; RawDatabase::Query fauxOfflineCountQuery = {"SELECT COUNT(*) FROM faux_offline_pending;", [&](const QVector& row) { fauxOfflineCount = row[0].toLongLong(); }}; QVERIFY(db->execNow(fauxOfflineCountQuery)); // both friend 1's third message and friend 2's third message should still be pending. //The broken message should no longer be pending. QVERIFY(fauxOfflineCount == 2); int totalHisoryCount = -1; RawDatabase::Query totalHistoryCountQuery = {"SELECT COUNT(*) FROM history;", [&](const QVector& row) { totalHisoryCount = row[0].toLongLong(); }}; QVERIFY(db->execNow(totalHistoryCountQuery)); QVERIFY(totalHisoryCount == 6); // all messages should still be in history. } void TestDbSchema::test2to3() { auto db = std::shared_ptr{new RawDatabase{"test2to3.db", {}, {}}}; createSchemaAtVersion(db, schema2); // since we don't enforce foreign key contraints in the db, we can stick in IDs to other tables // to avoid generating proper entries for peers and aliases tables, since they aren't actually // relevant for the test. QVector queries; // pending message, should be moved out queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (1, 1, 0, ?, 0)", {"/me "}}; queries += {"INSERT INTO faux_offline_pending (id) VALUES (" " last_insert_rowid()" ");"}; // non pending message with the content "/me ". Maybe it was sent by a friend using a different client. queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (2, 2, 0, ?, 2)", {"/me "}}; // non pending message sent by us queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (3, 3, 0, ?, 1)", {"a normal message"}}; // pending normal message sent by us queries += RawDatabase::Query{ "INSERT INTO history (id, timestamp, chat_id, message, sender_alias) VALUES (4, 3, 0, ?, 1)", {"a normal faux offline message"}}; queries += {"INSERT INTO faux_offline_pending (id) VALUES (" " last_insert_rowid()" ");"}; QVERIFY(db->execNow(queries)); QVERIFY(dbSchema2to3(*db)); long brokenCount = -1; RawDatabase::Query brokenCountQuery = {"SELECT COUNT(*) FROM broken_messages;", [&](const QVector& row) { brokenCount = row[0].toLongLong(); }}; QVERIFY(db->execNow(brokenCountQuery)); QVERIFY(brokenCount == 1); int fauxOfflineCount = -1; RawDatabase::Query fauxOfflineCountQuery = {"SELECT COUNT(*) FROM faux_offline_pending;", [&](const QVector& row) { fauxOfflineCount = row[0].toLongLong(); }}; QVERIFY(db->execNow(fauxOfflineCountQuery)); QVERIFY(fauxOfflineCount == 1); int totalHisoryCount = -1; RawDatabase::Query totalHistoryCountQuery = {"SELECT COUNT(*) FROM history;", [&](const QVector& row) { totalHisoryCount = row[0].toLongLong(); }}; QVERIFY(db->execNow(totalHistoryCountQuery)); QVERIFY(totalHisoryCount == 4); verifyDb(db, schema3); } void TestDbSchema::test3to4() { auto db = std::shared_ptr{new RawDatabase{"test3to4.db", {}, {}}}; createSchemaAtVersion(db, schema3); QVERIFY(dbSchema3to4(*db)); verifyDb(db, schema4); } QTEST_GUILESS_MAIN(TestDbSchema) #include "dbschema_test.moc" qTox/test/persistence/offlinemsgengine_test.cpp000066400000000000000000000143331415623743500224140ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/core.h" #include "src/model/friend.h" #include "src/model/status.h" #include "src/persistence/offlinemsgengine.h" #include struct MockFriendMessageSender : public QObject, public ICoreFriendMessageSender { Q_OBJECT public: MockFriendMessageSender(Friend* f) : f(f){}; bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override { return false; } bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override { if (Status::isOnline(f->getStatus())) { receipt.get() = receiptNum++; if (!dropReceipts) { msgs.push_back(message); emit receiptReceived(receipt); } numMessagesSent++; } else { numMessagesFailed++; } return Status::isOnline(f->getStatus()); } signals: void receiptReceived(ReceiptNum receipt); public: Friend* f; bool dropReceipts = false; size_t numMessagesSent = 0; size_t numMessagesFailed = 0; int receiptNum = 0; std::vector msgs; }; class TestOfflineMsgEngine : public QObject { Q_OBJECT private slots: void testReceiptResolution(); void testOfflineFriend(); void testSentUnsentCoordination(); void testCallback(); }; class OfflineMsgEngineFixture { public: OfflineMsgEngineFixture() : f(0, ToxPk(QByteArray(32, 0))) , friendMessageSender(&f) , offlineMsgEngine(&f, &friendMessageSender) { f.setStatus(Status::Status::Online); QObject::connect(&friendMessageSender, &MockFriendMessageSender::receiptReceived, &offlineMsgEngine, &OfflineMsgEngine::onReceiptReceived); } Friend f; MockFriendMessageSender friendMessageSender; OfflineMsgEngine offlineMsgEngine; }; void completionFn() {} void TestOfflineMsgEngine::testReceiptResolution() { OfflineMsgEngineFixture fixture; Message msg{false, QString(), QDateTime()}; ReceiptNum receipt; fixture.friendMessageSender.sendMessage(0, msg.content, receipt); fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn); // We should have no offline messages to deliver if we resolved our receipt // correctly fixture.offlineMsgEngine.deliverOfflineMsgs(); fixture.offlineMsgEngine.deliverOfflineMsgs(); fixture.offlineMsgEngine.deliverOfflineMsgs(); QVERIFY(fixture.friendMessageSender.numMessagesSent == 1); // If we drop receipts we should keep trying to send messages every time we // "deliverOfflineMsgs" fixture.friendMessageSender.dropReceipts = true; fixture.friendMessageSender.sendMessage(0, msg.content, receipt); fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn); fixture.offlineMsgEngine.deliverOfflineMsgs(); fixture.offlineMsgEngine.deliverOfflineMsgs(); fixture.offlineMsgEngine.deliverOfflineMsgs(); QVERIFY(fixture.friendMessageSender.numMessagesSent == 5); // And once we stop dropping and try one more time we should run out of // messages to send again fixture.friendMessageSender.dropReceipts = false; fixture.offlineMsgEngine.deliverOfflineMsgs(); fixture.offlineMsgEngine.deliverOfflineMsgs(); fixture.offlineMsgEngine.deliverOfflineMsgs(); QVERIFY(fixture.friendMessageSender.numMessagesSent == 6); } void TestOfflineMsgEngine::testOfflineFriend() { OfflineMsgEngineFixture fixture; Message msg{false, QString(), QDateTime()}; fixture.f.setStatus(Status::Status::Offline); fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn); fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn); fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn); fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn); fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn); fixture.f.setStatus(Status::Status::Online); fixture.offlineMsgEngine.deliverOfflineMsgs(); QVERIFY(fixture.friendMessageSender.numMessagesFailed == 0); QVERIFY(fixture.friendMessageSender.numMessagesSent == 5); } void TestOfflineMsgEngine::testSentUnsentCoordination() { OfflineMsgEngineFixture fixture; Message msg{false, QString("a"), QDateTime()}; ReceiptNum receipt; fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn); msg.content = "b"; fixture.friendMessageSender.dropReceipts = true; fixture.friendMessageSender.sendMessage(0, msg.content, receipt); fixture.friendMessageSender.dropReceipts = false; fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn); msg.content = "c"; fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn); fixture.offlineMsgEngine.deliverOfflineMsgs(); auto expectedResponseOrder = std::vector{"a", "b", "c"}; QVERIFY(fixture.friendMessageSender.msgs == expectedResponseOrder); } void TestOfflineMsgEngine::testCallback() { OfflineMsgEngineFixture fixture; size_t numCallbacks = 0; auto callback = [&numCallbacks] { numCallbacks++; }; Message msg{false, QString(), QDateTime()}; ReceiptNum receipt; fixture.friendMessageSender.sendMessage(0, msg.content, receipt); fixture.offlineMsgEngine.addSentMessage(receipt, msg, callback); fixture.offlineMsgEngine.addUnsentMessage(msg, callback); fixture.offlineMsgEngine.deliverOfflineMsgs(); QVERIFY(numCallbacks == 2); } QTEST_GUILESS_MAIN(TestOfflineMsgEngine) #include "offlinemsgengine_test.moc" qTox/test/persistence/paths_test.cpp000066400000000000000000000131731415623743500202150ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/persistence/paths.h" #include #include #include #include class TestPaths : public QObject { Q_OBJECT private slots: void constructAuto(); void constructPortable(); void constructNonPortable(); void checkPathsNonPortable(); void checkPathsPortable(); private: static void verifyqToxPath(const QString& testPath, const QString& basePath, const QString& subPath); }; namespace { const QLatin1String globalSettingsFile{"qtox.ini"}; const QLatin1String profileFolder{"profiles"}; const QLatin1String themeFolder{"themes"}; const QLatin1String avatarsFolder{"avatars"}; const QLatin1String transfersFolder{"transfers"}; const QLatin1String screenshotsFolder{"screenshots"}; const QString sep{QDir::separator()}; } /** * @brief Verifies construction in auto mode */ void TestPaths::constructAuto() { Paths * paths = Paths::makePaths(Paths::Portable::Auto); // auto detection should succeed QVERIFY(paths != nullptr); // the test environment should not provide a `qtox.ini` QVERIFY(paths->isPortable() == false); } /** * @brief Verifies construction in portable mode */ void TestPaths::constructPortable() { Paths * paths = Paths::makePaths(Paths::Portable::Portable); // portable construction should succeed even though qtox.ini doesn't exist QVERIFY(paths != nullptr); QVERIFY(paths->isPortable() == true); } /** * @brief Verifies construction in non-portable mode */ void TestPaths::constructNonPortable() { Paths * paths = Paths::makePaths(Paths::Portable::NonPortable); // Non portable should succeed QVERIFY(paths != nullptr); // the test environment should not provide a `qtox.ini` QVERIFY(paths->isPortable() == false); } /** * @brief Helper to verify qTox specific paths */ void TestPaths::verifyqToxPath(const QString& testPath, const QString& basePath, const QString& subPath) { const QString expectPath = basePath % sep % subPath; QVERIFY(testPath == expectPath); } /** * @brief Check generated paths against expected values in non-portable mode */ void TestPaths::checkPathsNonPortable() { Paths * paths = Paths::makePaths(Paths::Portable::NonPortable); QVERIFY(paths != nullptr); // Need non-portable environment to match our test cases QVERIFY(paths->isPortable() == false); // TODO(sudden6): write robust tests for Tox paths given by Tox Client Standard QString avatarDir{paths->getAvatarsDir()}; QString toxSaveDir{paths->getToxSaveDir()}; // avatars directory must be one level below toxsave to follow TCS QVERIFY(avatarDir.contains(toxSaveDir)); const QString appData{QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)}; const QString appConfig{QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)}; verifyqToxPath(paths->getProfilesDir(), appData, profileFolder % sep); verifyqToxPath(paths->getGlobalSettingsPath(), appConfig, globalSettingsFile); verifyqToxPath(paths->getScreenshotsDir(), appData, screenshotsFolder % sep); verifyqToxPath(paths->getTransfersDir(), appData, transfersFolder % sep); QStringList themeFolders{}; themeFolders += QStandardPaths::locate(QStandardPaths::AppDataLocation, themeFolder, QStandardPaths::LocateDirectory); // look for themes beside the qTox binary with lowest priority const QString curPath{qApp->applicationDirPath()}; themeFolders += curPath % sep % themeFolder % sep; QVERIFY(paths->getThemeDirs() == themeFolders); } /** * @brief Check generated paths against expected values in portable mode */ void TestPaths::checkPathsPortable() { Paths * paths = Paths::makePaths(Paths::Portable::Portable); QVERIFY(paths != nullptr); // Need portable environment to match our test cases QVERIFY(paths->isPortable() == true); const QString basePath{qApp->applicationDirPath()}; const QString avatarDir{paths->getAvatarsDir()}; verifyqToxPath(avatarDir, basePath, profileFolder % sep % avatarsFolder % sep); const QString toxSaveDir{paths->getToxSaveDir()}; verifyqToxPath(toxSaveDir, basePath, profileFolder % sep); // avatars directory must be one level below toxsave to follow TCS QVERIFY(avatarDir.contains(toxSaveDir)); verifyqToxPath(paths->getProfilesDir(), basePath, profileFolder % sep); verifyqToxPath(paths->getGlobalSettingsPath(), basePath, globalSettingsFile); verifyqToxPath(paths->getScreenshotsDir(), basePath, screenshotsFolder % sep); verifyqToxPath(paths->getTransfersDir(), basePath, transfersFolder % sep); // look for themes beside the qTox binary with lowest priority const QString curPath{qApp->applicationDirPath()}; QStringList themeFolders{curPath % sep % themeFolder % sep}; QVERIFY(paths->getThemeDirs() == themeFolders); } QTEST_GUILESS_MAIN(TestPaths) #include "paths_test.moc" qTox/test/platform/000077500000000000000000000000001415623743500146265ustar00rootroot00000000000000qTox/test/platform/posixsignalnotifier_test.cpp000066400000000000000000000060571415623743500225010ustar00rootroot00000000000000/* Copyright © 2017-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/platform/posixsignalnotifier.h" #include #include #include #include #include class TestPosixSignalNotifier : public QObject { Q_OBJECT private slots: void checkUsrSignalHandling(); void checkIgnoreExtraSignals(); void checkTermSignalsHandling(); }; constexpr int TIMEOUT = 10; void TestPosixSignalNotifier::checkUsrSignalHandling() { PosixSignalNotifier& psn = PosixSignalNotifier::globalInstance(); psn.watchSignal(SIGUSR1); QSignalSpy spy(&psn, &PosixSignalNotifier::activated); kill(getpid(), SIGUSR1); for (int i = 0; i < TIMEOUT && spy.count() != 1; ++i) { QCoreApplication::processEvents(); sleep(1); } QCOMPARE(spy.count(), 1); const QList& args = spy.first(); QCOMPARE(args.first().toInt(), SIGUSR1); } void sighandler(int) { } void TestPosixSignalNotifier::checkIgnoreExtraSignals() { PosixSignalNotifier& psn = PosixSignalNotifier::globalInstance(); psn.watchSignal(SIGUSR1); QSignalSpy spy(&psn, &PosixSignalNotifier::activated); // To avoid kiiling signal(SIGUSR2, sighandler); kill(getpid(), SIGUSR2); for (int i = 0; i < TIMEOUT; ++i) { QCoreApplication::processEvents(); sleep(1); } QCOMPARE(spy.count(), 0); } void TestPosixSignalNotifier::checkTermSignalsHandling() { PosixSignalNotifier& psn = PosixSignalNotifier::globalInstance(); psn.watchCommonTerminatingSignals(); QSignalSpy spy(&psn, &PosixSignalNotifier::activated); const std::initializer_list termSignals = {SIGHUP, SIGINT, SIGQUIT, SIGTERM}; for (int signal : termSignals) { QCoreApplication::processEvents(); kill(getpid(), signal); sleep(1); } for (int i = 0; i < TIMEOUT && static_cast(spy.size()) != termSignals.size(); i++) { QCoreApplication::processEvents(); sleep(1); } QCOMPARE(static_cast(spy.size()), termSignals.size()); for (size_t i = 0; i < termSignals.size(); ++i) { const QList& args = spy.at(static_cast(i)); int signal = *(termSignals.begin() + i); QCOMPARE(args.first().toInt(), signal); } } QTEST_GUILESS_MAIN(TestPosixSignalNotifier) #include "posixsignalnotifier_test.moc" qTox/themes/000077500000000000000000000000001415623743500133105ustar00rootroot00000000000000qTox/themes/dark/000077500000000000000000000000001415623743500142315ustar00rootroot00000000000000qTox/themes/dark/acceptCall/000077500000000000000000000000001415623743500162645ustar00rootroot00000000000000qTox/themes/dark/acceptCall/acceptCall.svg000066400000000000000000000023761415623743500210500ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/addFriendForm/000077500000000000000000000000001415623743500167355ustar00rootroot00000000000000qTox/themes/dark/addFriendForm/toxId.css000066400000000000000000000000571415623743500205400ustar00rootroot00000000000000QLineEdit { background-color: #8a3f3a; } qTox/themes/dark/chatArea/000077500000000000000000000000001415623743500157415ustar00rootroot00000000000000qTox/themes/dark/chatArea/chatArea.css000066400000000000000000000002201415623743500201550ustar00rootroot00000000000000QTextEdit { background: @groundBase; color: @mainText; } QGraphicsView { border: none; } QWidget { background: @groundBase; } qTox/themes/dark/chatArea/chatHead.css000066400000000000000000000007401415623743500201550ustar00rootroot00000000000000QLineEdit { color: @mainText; background: @groundBase; border: 0px; } #nameLabel { color: @mainText; font: @mediumBold; font-size:12px; } #statusLabel { color: @statusActive; font: @medium; font-size:12px; } #peersLabel { color: @statusActive; font: @medium; font-size:12px; } QLabel[peerType="our"] { color: green; } QLabel[peerType="muted"] { color: darkred; } QLabel[playingAudio="true"] { font-weight: bold; } qTox/themes/dark/chatArea/error.svg000066400000000000000000000035731415623743500176230ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatArea/info.svg000066400000000000000000000035731415623743500174250ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatArea/innerStyle.css000066400000000000000000000006341415623743500206120ustar00rootroot00000000000000body { font: @baseFont; } p { white-space: pre-wrap; } .action { color: @action; font-style: italic; font-weight: bold; } .typing { color: #8d8d8d; } .quote { color: #279419; } .alert { margin-left: 0px; margin-right: 0px; background-color: @orange; } .alert_name { background-color: @orange; font: @bigBold; } a { color: @link; font-weight: bold } qTox/themes/dark/chatArea/scrollBarDownArrow.svg000066400000000000000000000015761415623743500222610ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/chatArea/scrollBarLeftArrow.svg000066400000000000000000000016101415623743500222310ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/chatArea/scrollBarRightArrow.svg000066400000000000000000000016061415623743500224210ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/chatArea/scrollBarUpArrow.svg000066400000000000000000000016011415623743500217230ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/chatArea/spinner.svg000066400000000000000000000026301415623743500201410ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatArea/symbols.svg000066400000000000000000000221721415623743500201560ustar00rootroot00000000000000 image/svg+xml ! i qTox/themes/dark/chatArea/typing.svg000066400000000000000000000043041415623743500177750ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/000077500000000000000000000000001415623743500157745ustar00rootroot00000000000000qTox/themes/dark/chatForm/buttons.css000066400000000000000000000062311415623743500202060ustar00rootroot00000000000000/* Message edit */ QAbstractButton#emoteButton { background-image: url("@getImagePath(chatForm/emoteButton.svg)"); border-top-right-radius: 5px; width: 24px; height: 24px; } QAbstractButton#fileButton { background-image: url("@getImagePath(chatForm/fileButton.svg)"); border-bottom-right-radius: 5px; width: 24px; height: 24px; } QAbstractButton#screenshotButton { background-image: url("@getImagePath(chatForm/screenshotButton.svg)"); border-top-left-radius: 5px; width: 24px; height: 24px; } QAbstractButton#sendButton { background-image: url("@getImagePath(chatForm/sendButton.svg)"); border-radius: 5px; width: 50px; height: 50px; } /* Header */ QAbstractButton#volButton { border-image: url("@getImagePath(chatForm/volButton.svg)"); border-radius: 5px; width: 22px; height: 18px; } QAbstractButton#micButton { border-image: url("@getImagePath(chatForm/micButton.svg)"); border-radius: 5px; width: 22px; height: 18px; } QAbstractButton#videoButton { background-image: url("@getImagePath(chatForm/videoButton.svg)"); border-radius: 5px; width: 50px; height: 40px; } QAbstractButton#callButton { background-image: url("@getImagePath(chatForm/callButton.svg)"); border-radius: 5px; width: 50px; height: 40px; } /* SearchLine */ QAbstractButton#searchSettingsButton { background-image: url("@getImagePath(chatForm/searchSettingsButton.svg)"); border-radius: 5px; width: 35px; height: 35px; } QAbstractButton#searchHideButton { background-image: url("@getImagePath(chatForm/searchHideButton.svg)"); border-radius: 5px; width: 35px; height: 35px; } QAbstractButton#searchUpButton { background-image: url("@getImagePath(chatForm/searchUpButton.svg)"); border-radius: 5px; width: 35px; height: 35px; } QAbstractButton#searchDownButton { background-image: url("@getImagePath(chatForm/searchDownButton.svg)"); border-radius: 5px; width: 35px; height: 35px; } QAbstractButton#choiceDateButton { background-image: url("@getImagePath(chatForm/searchCalendarButton.svg)"); border-radius: 5px; width: 45px; height: 35px; } QAbstractButton#startButton { border-radius: 5px; width: 60px; height: 35px; color: #fff } /* Common */ QAbstractButton { background-repeat: none; background-position: center; border: none; } QAbstractButton:disabled { color: gray; background-color: #515151; } QAbstractButton:focus { outline: none; } QAbstractButton[state="green"] { background-color: #529449; } QAbstractButton[state="green"]:hover { background-color: #589f4e; } QAbstractButton[state="green"]:pressed { background-color: #437a3c; } QAbstractButton[state="red"] { background-color: #9e2525; } QAbstractButton[state="red"]:hover { background-color: #bd2c2c; } QAbstractButton[state="red"]:pressed { background-color: #8f2121; } QAbstractButton[state="yellow"] { background-color: #e6a850; } QAbstractButton[state="yellow"]:hover { background-color: #e6bd5d; } QAbstractButton[state="yellow"]:pressed { background-color: #e6843e; } qTox/themes/dark/chatForm/callButton.svg000066400000000000000000000025301415623743500206240ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/emoteButton.svg000066400000000000000000000031661415623743500210300ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/exitFullScreenButton.svg000066400000000000000000000031521415623743500226460ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/fileButton.svg000066400000000000000000000101771415623743500206360ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/chatForm/fullScreenButtons.css000066400000000000000000000030461415623743500221720ustar00rootroot00000000000000QFrame { background-color: rgba(50, 50, 50, 0.6); border-radius: 20px; } QAbstractButton { background-color: transparent; background-repeat: none; background-position: center; border: none; border-radius: 5px; } QAbstractButton:hover { background-color: rgba(255,255,255, 0.2); } QAbstractButton:pressed { background-color: rgba(0,0,0, 0.2); } QAbstractButton#volButtonFullScreen { background-image: url("@getImagePath(chatForm/volButton.svg)"); width: 11px; height: 9px; padding: 12px; } QAbstractButton#micButtonFullScreen { background-image: url("@getImagePath(chatForm/micButton.svg)"); width: 11px; height: 9px; padding: 12px; } QAbstractButton#videoButtonFullScreen { background-image: url("@getImagePath(chatForm/videoButtonRed.svg)"); width: 37px; height: 33px; } QAbstractButton#videoPreviewButton { background-image: url("@getImagePath(chatForm/videoPreview.svg)"); width: 13px; height: 13px; padding: 10px; } QAbstractButton#exitFullScreenButton { background-image: url("@getImagePath(chatForm/exitFullScreenButton.svg)"); width: 30px; height: 30px; padding: 1px; } QAbstractButton#volButtonFullScreen[state="red"] { background-image: url("@getImagePath(chatForm/volButtonRed.svg)"); } QAbstractButton#micButtonFullScreen[state="red"] { background-image: url("@getImagePath(chatForm/micButtonRed.svg)"); } QAbstractButton#videoPreviewButton[state="red"] { background-image: url("@getImagePath(chatForm/videoPreviewRed.svg)"); } qTox/themes/dark/chatForm/labels.css000066400000000000000000000001671415623743500177540ustar00rootroot00000000000000QLabel { color: @mainText; } QLabel:disabled { color: #2d2b2b; } QLabel[state="red"] { color: #e84747; } qTox/themes/dark/chatForm/micButton.svg000066400000000000000000000041101415623743500204550ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/micButtonRed.svg000066400000000000000000000110611415623743500211130ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/screenshotButton.svg000066400000000000000000000007421415623743500220710ustar00rootroot00000000000000 image/svg+xml Layer 1 qTox/themes/dark/chatForm/searchCalendarButton.svg000066400000000000000000000075051415623743500226170ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/searchDownButton.svg000066400000000000000000000044251415623743500220130ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/searchHideButton.svg000066400000000000000000000042511415623743500217520ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/searchSettingsButton.svg000066400000000000000000000104011415623743500226730ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/searchUpButton.svg000066400000000000000000000044251415623743500214700ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/sendButton.svg000066400000000000000000000026041415623743500206440ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/chatForm/videoButton.svg000066400000000000000000000021271415623743500210210ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/chatForm/videoButtonRed.svg000066400000000000000000000024261415623743500214560ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/videoPreview.svg000066400000000000000000000042641415623743500211730ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/videoPreviewRed.svg000066400000000000000000000041641415623743500216250ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/volButton.svg000066400000000000000000000036341415623743500205170ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/chatForm/volButtonRed.svg000066400000000000000000000042331415623743500211460ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/contentDialog/000077500000000000000000000000001415623743500170235ustar00rootroot00000000000000qTox/themes/dark/contentDialog/contentDialog.css000066400000000000000000000002201415623743500223210ustar00rootroot00000000000000QSplitter:handle { color: @groundBase; background-color: @groundBase; } QWidget { background: @groundBase; color: @mainText; } qTox/themes/dark/emoteButton/000077500000000000000000000000001415623743500165365ustar00rootroot00000000000000qTox/themes/dark/emoteButton/emoteButton.css000066400000000000000000000006451415623743500215620ustar00rootroot00000000000000QPushButton { background-color: #6bc260; background-image: url("@getImagePath(emoteButton/emoteButton.svg)"); background-repeat: none; background-position: center; border-top-right-radius: 5px; border: none; width: 24px; height: 24px; } QPushButton:hover { background-color: #79c76f; } QPushButton:pressed { background-color: #51b244; } QPushButton:focus { outline: none; } qTox/themes/dark/emoteButton/emoteButton.svg000066400000000000000000000031661415623743500215720ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/emoticonWidget/000077500000000000000000000000001415623743500172125ustar00rootroot00000000000000qTox/themes/dark/emoticonWidget/dot_page.svg000066400000000000000000000020061415623743500215130ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/emoticonWidget/dot_page_current.svg000066400000000000000000000020061415623743500232550ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/emoticonWidget/dot_page_hover.svg000066400000000000000000000020061415623743500227160ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/emoticonWidget/emoticonWidget.css000066400000000000000000000013341415623743500227060ustar00rootroot00000000000000QPushButton { background-color: transparent; background-repeat: none; border: none; width: 24px; height: 24px; } QRadioButton::indicator { width: 10px; height: 10px; } QRadioButton::indicator:unchecked { image: url(@getImagePath(emoticonWidget/dot_page.svg)); } QRadioButton::indicator:unchecked:hover { image: url(@getImagePath(emoticonWidget/dot_page_hover.svg)); } QRadioButton::indicator:unchecked:pressed { image: url(@getImagePath(emoticonWidget/dot_page_hover.svg)); } QRadioButton::indicator:checked { image: url(@getImagePath(emoticonWidget/dot_page_current.svg)); } QMenu { background-color: #363434; /* sets background of the menu */ border: 0px solid; } qTox/themes/dark/fileButton/000077500000000000000000000000001415623743500163445ustar00rootroot00000000000000qTox/themes/dark/fileButton/fileButton.css000066400000000000000000000007531415623743500211760ustar00rootroot00000000000000QPushButton { background-color: #6bc260; background-image: url("@getImagePath(fileButton/fileButton.svg)"); background-repeat: none; background-position: center; border-bottom-right-radius: 5px; border: none; width: 24px; height: 24px; } QAbstractButton:hover { background-color: #79c76f; } QPushButton:pressed { background-color: #51b244; } QPushButton[enabled="false"] { background-color: #919191; } QPushButton:focus { outline: none; } qTox/themes/dark/fileButton/fileButton.svg000066400000000000000000000101771415623743500212060ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/fileTransferInstance/000077500000000000000000000000001415623743500203425ustar00rootroot00000000000000qTox/themes/dark/fileTransferInstance/arrow_white.svg000066400000000000000000000015701415623743500234200ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/fileTransferInstance/browse.svg000066400000000000000000000124141415623743500223660ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/fileTransferInstance/dir.svg000066400000000000000000000042101415623743500216360ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/fileTransferInstance/filetransferWidget.css000066400000000000000000000006451415623743500247110ustar00rootroot00000000000000[fontColor="white"] QLabel { color: @mainText; font: @big; } [fontColor="black"] QLabel { color: @statusActive; font: @big; } QPushButton { margin: 0; border: none; } QProgressBar { border: 2px solid @statusActive; border-radius: 0px; background-color: @statusActive; } QProgressBar::chunk { background-color: @transferMiddle; width: 1px; } qTox/themes/dark/fileTransferInstance/no.svg000066400000000000000000000033331415623743500215010ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/fileTransferInstance/pause.svg000066400000000000000000000035221415623743500222020ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/fileTransferInstance/yes.svg000066400000000000000000000033001415623743500216570ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/fileTransferWidget/000077500000000000000000000000001415623743500200215ustar00rootroot00000000000000qTox/themes/dark/fileTransferWidget/fileDone.svg000066400000000000000000000022101415623743500222620ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/friendList/000077500000000000000000000000001415623743500163345ustar00rootroot00000000000000qTox/themes/dark/friendList/friendList.css000066400000000000000000000023251415623743500211530ustar00rootroot00000000000000QScrollArea { background: @themeMedium; } QScrollBar:vertical { background: @themeMedium; width: 16px; padding: 0px 3px 0px 3px; } QScrollBar:handle:vertical { background: @themeDark; min-height: 20px; border-radius: 5px; margin: 3px 0px 3px 0px; } QScrollBar:handle:vertical:hover { background: @themeMediumDark; } QScrollBar:handle:vertical:pressed { background: @themeDark; } QScrollBar:add-line:vertical { height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar:sub-line:vertical { height: 0px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar:add-page:vertical, QScrollBar::sub-page:vertical { background: none; } QWidget#circleWidgetContainer > QFrame#line { color: white; } QWidget#circleWidgetContainer { background-color: @themeMedium; } QWidget#circleWidgetContainer:hover { background-color: @themeLight; } QWidget#circleWidgetContainer QLineEdit { background-color: @themeLight; } QWidget#circleWidgetContainer > QLabel#status { font: @small; color: @mainText; } QWidget#circleWidgetContainer > QLabel#name { font: @big; color: #a3a3a3; } QLabel { color: white; } qTox/themes/dark/genericChatForm/000077500000000000000000000000001415623743500172715ustar00rootroot00000000000000qTox/themes/dark/genericChatForm/genericChatForm.css000066400000000000000000000000511415623743500230370ustar00rootroot00000000000000QWidget { background: @groundBase; } qTox/themes/dark/loginScreen/000077500000000000000000000000001415623743500165015ustar00rootroot00000000000000qTox/themes/dark/loginScreen/loginScreen.css000066400000000000000000000020701415623743500214620ustar00rootroot00000000000000#LoginScreen { background-color: #1c1c1c; } #line { color: #757575; } #stackedWidget { background-color: #100f0f; } #labelNewProfile, #labelLoadProfile { border-style: solid; border-width: 15px 0 15px 15px; border-color: #100f0f #100f0f #100f0f #1c1c1c; } #labelNewProfile { margin-top:125px; margin-bottom:45px; } #labelLoadProfile { margin-top:165px; margin-bottom:5px; } LoginScreen > QPushButton, QStackedWidget QPushButton { background: transparent; border: none; color: white; font-size: 9pt; font-weight: bold; } #loginButton, #importButton, #createAccountButton { border-radius:5px; padding:5px; background-color: #529449; } #loginButton:hover, #importButton:hover, #createAccountButton:hover { background: #589f4e; } QMessageBox, QLabel { color: @mainText; background: #100f0f; } QLabel#label_7 { background: #1c1c1c; } QCheckBox, QProgressBar, QComboBox, QLineEdit, QListView { color: @mainText; background: @groundBase; } QCheckBox:disabled { color: gray; } qTox/themes/dark/msgEdit/000077500000000000000000000000001415623743500156255ustar00rootroot00000000000000qTox/themes/dark/msgEdit/msgEdit.css000066400000000000000000000010011415623743500177230ustar00rootroot00000000000000QTextEdit { border-color: @groundExtra; border-style: solid; border-width: 1px 0 1px 1px; background: @groundBase; } QTextEdit:hover { border-color: #d7d4d1; } QTextEdit:pressed { border-color: #46465b; } QTextEdit#group { /*border-radius: 0 6px 6px 0; would use to round corners in groupchat, but Qt's implementation seems to be bugged*/ border: 1px solid #c4c1bd; } QTextEdit#group:hover { border-color: #d7d4d1; } QTextEdit#group:pressed { border-color: #46465b; } qTox/themes/dark/notificationEdge/000077500000000000000000000000001415623743500175045ustar00rootroot00000000000000qTox/themes/dark/notificationEdge/notificationEdge.css000066400000000000000000000001611415623743500234670ustar00rootroot00000000000000NotificationEdgeWidget { background-color: #6bc260; } NotificationEdgeWidget > QLabel { color: white; } qTox/themes/dark/palette.ini000066400000000000000000000006141415623743500163710ustar00rootroot00000000000000[colors] transferGood="#42783b" transferWait="#958931" transferBad="#963a3a" transferMiddle="#707070" mainText="#c3c3c3" nameActive="#c3c3c3" statusActive="#d1d1d1" groundExtra="#d1d1d1" groundBase="#201f1f" orange="#713400" themeDark="#1c1c1c" themeMediumDark="#2a2a2a" themeMedium="#100f0f" themeLight="#201f1f" action="#546eff" link="#d292ff" searchHighlighted="#7e2a00" selectText = "#515151"qTox/themes/dark/rejectCall/000077500000000000000000000000001415623743500163015ustar00rootroot00000000000000qTox/themes/dark/rejectCall/rejectCall.svg000066400000000000000000000024521415623743500210750ustar00rootroot00000000000000 image/svg+xml qTox/themes/dark/screenshotButton/000077500000000000000000000000001415623743500176025ustar00rootroot00000000000000qTox/themes/dark/screenshotButton/screenshotButton.css000066400000000000000000000007571415623743500236760ustar00rootroot00000000000000QPushButton { background-color: #6bc260; background-image: url("@getImagePath(screenshotButton/screenshotButton.svg)"); background-repeat: none; background-position: center; border-top-left-radius: 5px; border: none; width: 24px; height: 24px; } QPushButton:hover { background-color: #79c76f; } QPushButton:pressed { background-color: #51b244; } QPushButton[enabled="false"] { background-color: #919191; } QPushButton:focus { outline: none; } qTox/themes/dark/screenshotButton/screenshotButton.svg000066400000000000000000000007421415623743500236770ustar00rootroot00000000000000 image/svg+xml Layer 1 qTox/themes/dark/sendButton/000077500000000000000000000000001415623743500163565ustar00rootroot00000000000000qTox/themes/dark/sendButton/sendButton.css000066400000000000000000000006311415623743500212150ustar00rootroot00000000000000QPushButton { background-color: #6bc260; background-image: url("@getImagePath(sendButton/sendButton.svg)"); background-repeat: none; background-position: center; border: none; border-radius: 5px; width: 50px; height: 50px; } QPushButton:hover { background-color: #79c76f; } QPushButton:pressed { background-color: #51b244; } QPushButton:focus { outline: none; } qTox/themes/dark/sendButton/sendButton.svg000066400000000000000000000026041415623743500212260ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/settings/000077500000000000000000000000001415623743500160715ustar00rootroot00000000000000qTox/themes/dark/settings/checkboxChecked.svg000066400000000000000000000033371415623743500216550ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/settings/checkboxCheckedDisabled.svg000066400000000000000000000033601415623743500233010ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/settings/mainHead.css000066400000000000000000000001331415623743500203060ustar00rootroot00000000000000QWidget .QLabel, QWidget .QLineEdit { color: @mainText; background: @groundBase; } qTox/themes/dark/statusButton/000077500000000000000000000000001415623743500167505ustar00rootroot00000000000000qTox/themes/dark/statusButton/menu_indicator.svg000066400000000000000000000015361415623743500224760ustar00rootroot00000000000000 image/svg+xmlqTox/themes/dark/statusButton/statusButton.css000066400000000000000000000014161415623743500222030ustar00rootroot00000000000000QPushButton { background: none; background-color: @themeMediumDark; border: none; border-radius: 6px; width: 20px; height: 40px; } QPushButton:default { background-color: @themeMediumDark; } /*Bugged in Qt, but it's probably better to leave enabled so that users can tell it's clickable*/ QPushButton:hover { background-color: @themeMedium; } QPushButton:pressed { background-color: @themeMediumDark; } QPushButton:focus { outline: none; } QPushButton::menu-indicator {image: none;} QPushButton::menu-indicator:pressed, QPushButton::menu-indicator:open { image: url("@getImagePath(statusButton/menu_indicator.svg)"); subcontrol-origin: padding; subcontrol-position: bottom center; position: relative; bottom: 2px; } qTox/themes/dark/tooliconsZone/000077500000000000000000000000001415623743500170765ustar00rootroot00000000000000qTox/themes/dark/tooliconsZone/tooliconsZone.css000066400000000000000000000007601415623743500224600ustar00rootroot00000000000000QPushButton[update-available=true] { background-color: #115508; border: none; } QPushButton:hover[update-available=true] { background-color: #19780b; border: none; } QPushButton { background-color: @themeDark; border: none; } QPushButton:hover { background-color: @themeMediumDark; border: none; } QPushButton:checked { background-color: @themeMedium; border: none; } QPushButton:pressed { background-color: @themeMediumLight; border: none; } qTox/themes/dark/window/000077500000000000000000000000001415623743500155405ustar00rootroot00000000000000qTox/themes/dark/window/general.css000066400000000000000000000107021415623743500176670ustar00rootroot00000000000000QToolTip { /* explicit border width is required https://bugreports.qt.io/browse/QTBUG-41313 */ border: 0px; color: black; background: #ffffdc; } QDialog { background: @groundBase; } QWidget#contentWidget { background: @groundBase; } QTabWidget { background-color: #100f0f; } QTabBar::tab:selected { background: #100f0f; color: @mainText; } QTabBar::tab:!selected { background: #444242; color: #8e8e8e; } QLabel { color: @mainText; } QListView { color: @mainText; } QTextEdit, QPlainTextEdit { border-color: #514f4f; border-style: solid; border-width: 1px 0 1px 1px; background: @groundBase; color: @mainText; } QListWidget { background-color: @groundBase; } QMessageBox { background-color: @groundBase; } QCheckBox { color: @mainText; } QCheckBox::indicator { width: 12px; height: 12px; border: 2px solid #514f4f; border-radius: 2px; } QCheckBox::indicator:checked { image: url("@getImagePath(settings/checkboxChecked.svg)"); } QCheckBox::indicator:checked:pressed, QCheckBox::indicator:unchecked:pressed { background: #514f4f; } QCheckBox:disabled { color: grey; } QCheckBox:indicator:disabled { border: 2px solid #373535; border-radius: 2px; } QCheckBox:indicator:checked:disabled { image: url("@getImagePath(settings/checkboxCheckedDisabled.svg)"); } QSpinBox, QDoubleSpinBox { background: @groundBase; color: @mainText; } QSpinBox:disabled, QDoubleSpinBox:disabled { background: #262424; color: grey; } QGroupBox { color: @mainText; background-color: @groundBase; font: @bigBold; } QComboBox { color: @mainText; background-color: @groundBase; } QComboBox QAbstractItemView { background-color: @groundBase; } QLineEdit { background: @groundBase; color: @mainText; } QLineEdit:disabled { background-color: #262424; color: grey; } QScrollArea { background-color: @groundBase; } QScrollArea > QWidget > QWidget { background: transparent; } QScrollArea::corner { background-color: @groundBase; border: none; } QScrollBar:vertical { background: transparent; width: 12px; margin-top: 2px; margin-bottom: 2px; } /* using last is a bit of a hack, but QTabBar otherwise doesn't allow selecting single tabs */ QTabBar::tab:last:!selected[update-available=true] { background-color: #115508; } QPushButton#updateAvailableButton { background-color: #115508; } QScrollBar::handle:vertical { background-color: #343232; min-height: 20px; border-radius: 3px; margin-left: 2px; } QScrollBar::handle:vertical:hover { background-color: #3e3c3c; } QScrollBar::handle:vertical:pressed { background-color: #474545; } QScrollBar::add-line:vertical { background-color: white; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { background-color: white; height: 0px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar:QScrollBar::down-arrow:vertical { width: 10; height: 10px; background-color: white; } QScrollBar:QScrollBar::up-arrow:vertical { width: 10px; height: 10px; background-color: white; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; } QScrollBar:horizontal { background: transparent; height: 10px; margin: 0 2px 0 2px; } QScrollBar::handle:horizontal { background-color: #343232; min-width: 20px; border-radius: 2px; } QScrollBar::handle:horizontal:hover { background-color: #3e3c3c; } QScrollBar::handle:horizontal:pressed { background-color: #474545; } QScrollBar::add-line:horizontal { background-color: white; width: 0px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { background-color: white; width: 0px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar:QScrollBar::down-arrow:horizontal { width: 10; height: 10px; background-color: white; } QScrollBar:QScrollBar::up-arrow:horizontal { width: 10px; height: 10px; background-color: white; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; } QRadioButton { background: @groundBase; color: @mainText; } QPushButton { background: #323030; color: @mainText; } QSplitter:handle { color: @groundBase; background-color: @groundBase; } qTox/themes/dark/window/profile.css000066400000000000000000000001121415623743500177040ustar00rootroot00000000000000#selfAvatar:hover { border-radius: 6px; background-color: #ccc; } qTox/themes/dark/window/statusPanel.css000066400000000000000000000042761415623743500205660ustar00rootroot00000000000000QLineEdit { background: none; background-color: @themeMedium; color: white; border: 0px; border-radius: 4px; } QToolButton { background: none; background-color: @themeMedium; color: white; border-style: none; border-radius: 4px; } QToolButton:pressed { background-color: @themeMediumDark; border-radius: 4px; color: white; } QToolButton::menu-indicator { image: none } QPushButton#green { background: none; background-color: #6bc260; color: white; border-style: none; border-radius: 4px; padding: 4px; margin: 4px 8px; } QPushButton#green:hover { background-color: #79c76f; } QPushButton#green:pressed { background-color: #51b244; } /** Uncomment this after https://github.com/qTox/qTox/pull/1640 is merged! QComboBox:down-arrow { image: url(ui/css/down_arrow.png); } **/ QListView { background-color: @themeLight; border-style: none; border-radius: 4px; } #statusPanel { background-color: @themeDark; } #statusPanel > #statusHead { background-color: @themeDark; } #statusPanel > #statusHead > #nameLabel { background-color: @themeDark; font: @extraBig; color: @nameActive; } #statusPanel > #statusHead > #statusLabel { background-color: @themeDark; font: @medium; color: @groundExtra; } #statusPanel > #statusHead > #statusButton { background: none; background-color: @themeMedium; border: none; border-radius: 6px; width: 20px; height: 40px; } /*Bugged in Qt, but it's probably better to leave enabled so that users can tell it's clickable*/ #statusPanel > #statusHead > #statusButton:hover { background-color: @themeLight; } #statusPanel > #statusHead > #statusButton:pressed { background-color: @themeMedium; } #statusPanel > #statusHead > #statusButton:focus { outline: none; } #statusPanel > #statusHead > #statusButton::menu-indicator {image: none;} #statusPanel > #statusHead > #statusButton::menu-indicator:pressed, #statusPanel > #statusHead > #statusButton::menu-indicator:open { image: url("@getImagePath(statusButton/menu_indicator.svg)"); subcontrol-origin: padding; subcontrol-position: bottom center; position: relative; bottom: 2px; } qTox/themes/dark/window/window.css000066400000000000000000000041211415623743500175570ustar00rootroot00000000000000Widget#activeWindow { background-color: #DFDFDF; border: 2px solid #ABABAB; } Widget#inactiveWindow { background-color: #f4f4f4; border: 2px solid #ABABAB; } /* Widget#titleBar { border: 10px solid #ABABAB; border-top: 0px solid transparent; background-color: #DFDFDF; } Widget#titleBarInactive { border: 5px solid #ABABAB; border-top: 0px solid transparent; background-color: #F1F1F1; }*/ QToolButton#tbMenu { border: 0px solid transparent; background-color: transparent; } QPushButton#minimizeButton { border: 0px solid transparent; background-color: transparent; image: url("@getImagePath(window/minimizeButton.png)"); } QPushButton#minimizeButton:hover {image: url("@getImagePath(window/minimizeButtonHover.png)");} QPushButton#minimizeButton:pressed {image: url("@getImagePath(window/minimizeButtonPressed.png)");} QPushButton#minimizeButton:focus {outline: none;} QPushButton#maximizeButton { border: 0px solid transparent; background-color: transparent; image: url("@getImagePath(window/maximizeButton.png"); } QPushButton#maximizeButton:hover {image: url("@getImagePath(window/maximizeButtonHover.png)");} QPushButton#maximizeButton:pressed {image: url("@getImagePath(window/maximizeButtonPressed.png)");} QPushButton#maximizeButton:focus {outline: none;} QPushButton#closeButton { border: 0px solid transparent; background-color: transparent; image: url("@getImagePath(window/closeButton.png"); } QPushButton#closeButton:hover {image: url("@getImagePath(window/closeButtonHover.png)");} QPushButton#closeButton:pressed {image: url("@getImagePath(window/closeButtonPressed.png)");} QPushButton#closeButton:focus {outline: none;} QPushButton#restoreButton { border: 0px solid transparent; background-color: transparent; image: url("@getImagePath(window/restoreButton.png)"); } QPushButton#restoreButton:hover {image: url("@getImagePath(window/restoreButtonHover.png)");} QPushButton#restoreButton:pressed {image: url("@getImagePath(window/restoreButtonPressed.png)");} QPushButton#restoreButton:focus {outline: none;} qTox/themes/default/000077500000000000000000000000001415623743500147345ustar00rootroot00000000000000qTox/themes/default/acceptCall/000077500000000000000000000000001415623743500167675ustar00rootroot00000000000000qTox/themes/default/acceptCall/acceptCall.svg000066400000000000000000000023761415623743500215530ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/addFriendForm/000077500000000000000000000000001415623743500174405ustar00rootroot00000000000000qTox/themes/default/addFriendForm/toxId.css000066400000000000000000000000571415623743500212430ustar00rootroot00000000000000QLineEdit { background-color: #FFC1C1; } qTox/themes/default/chatArea/000077500000000000000000000000001415623743500164445ustar00rootroot00000000000000qTox/themes/default/chatArea/chatArea.css000066400000000000000000000001341415623743500206640ustar00rootroot00000000000000QTextEdit { background: white; color: black; } QGraphicsView { border: none; } qTox/themes/default/chatArea/chatHead.css000066400000000000000000000007321415623743500206610ustar00rootroot00000000000000QLineEdit { color: @mainText; background: white; border: 0px; } #nameLabel { color: @mainText; font: @mediumBold; font-size:12px; } #statusLabel { color: @statusActive; font: @medium; font-size:12px; } #peersLabel { color: @statusActive; font: @medium; font-size:12px; } QLabel[peerType="our"] { color: green; } QLabel[peerType="muted"] { color: darkred; } QLabel[playingAudio="true"] { font-weight: bold; } qTox/themes/default/chatArea/error.svg000066400000000000000000000035731415623743500203260ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatArea/info.svg000066400000000000000000000035731415623743500201300ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatArea/innerStyle.css000066400000000000000000000005571415623743500213210ustar00rootroot00000000000000body { font: @baseFont; } p { white-space: pre-wrap; } .action { color: @action; font-style: italic; } .typing { color: #4e4e4e; } .quote { color: #279419; } .alert { margin-left: 0px; margin-right: 0px; background-color: @orange; } .alert_name { background-color: @orange; font: @bigBold; } a { color: @link; } qTox/themes/default/chatArea/scrollBarDownArrow.svg000066400000000000000000000015761415623743500227640ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/chatArea/scrollBarLeftArrow.svg000066400000000000000000000016101415623743500227340ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/chatArea/scrollBarRightArrow.svg000066400000000000000000000016061415623743500231240ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/chatArea/scrollBarUpArrow.svg000066400000000000000000000016011415623743500224260ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/chatArea/spinner.svg000066400000000000000000000026301415623743500206440ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatArea/symbols.svg000066400000000000000000000221721415623743500206610ustar00rootroot00000000000000 image/svg+xml ! i qTox/themes/default/chatArea/typing.svg000066400000000000000000000042271415623743500205040ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/000077500000000000000000000000001415623743500164775ustar00rootroot00000000000000qTox/themes/default/chatForm/buttons.css000066400000000000000000000062311415623743500207110ustar00rootroot00000000000000/* Message edit */ QAbstractButton#emoteButton { background-image: url("@getImagePath(chatForm/emoteButton.svg)"); border-top-right-radius: 5px; width: 24px; height: 24px; } QAbstractButton#fileButton { background-image: url("@getImagePath(chatForm/fileButton.svg)"); border-bottom-right-radius: 5px; width: 24px; height: 24px; } QAbstractButton#screenshotButton { background-image: url("@getImagePath(chatForm/screenshotButton.svg)"); border-top-left-radius: 5px; width: 24px; height: 24px; } QAbstractButton#sendButton { background-image: url("@getImagePath(chatForm/sendButton.svg)"); border-radius: 5px; width: 50px; height: 50px; } /* Header */ QAbstractButton#volButton { border-image: url("@getImagePath(chatForm/volButton.svg)"); border-radius: 5px; width: 22px; height: 18px; } QAbstractButton#micButton { border-image: url("@getImagePath(chatForm/micButton.svg)"); border-radius: 5px; width: 22px; height: 18px; } QAbstractButton#videoButton { background-image: url("@getImagePath(chatForm/videoButton.svg)"); border-radius: 5px; width: 50px; height: 40px; } QAbstractButton#callButton { background-image: url("@getImagePath(chatForm/callButton.svg)"); border-radius: 5px; width: 50px; height: 40px; } /* SearchLine */ QAbstractButton#searchSettingsButton { background-image: url("@getImagePath(chatForm/searchSettingsButton.svg)"); border-radius: 5px; width: 35px; height: 35px; } QAbstractButton#searchHideButton { background-image: url("@getImagePath(chatForm/searchHideButton.svg)"); border-radius: 5px; width: 35px; height: 35px; } QAbstractButton#searchUpButton { background-image: url("@getImagePath(chatForm/searchUpButton.svg)"); border-radius: 5px; width: 35px; height: 35px; } QAbstractButton#searchDownButton { background-image: url("@getImagePath(chatForm/searchDownButton.svg)"); border-radius: 5px; width: 35px; height: 35px; } QAbstractButton#choiceDateButton { background-image: url("@getImagePath(chatForm/searchCalendarButton.svg)"); border-radius: 5px; width: 45px; height: 35px; } QAbstractButton#startButton { border-radius: 5px; width: 60px; height: 35px; color: #fff } /* Common */ QAbstractButton { background-repeat: none; background-position: center; border: none; } QAbstractButton:disabled { color: gray; background-color: #919191; } QAbstractButton:focus { outline: none; } QAbstractButton[state="green"] { background-color: #6bc260; } QAbstractButton[state="green"]:hover { background-color: #79c76f; } QAbstractButton[state="green"]:pressed { background-color: #51b244; } QAbstractButton[state="red"] { background-color: #c84e4e; } QAbstractButton[state="red"]:hover { background-color: #e87474; } QAbstractButton[state="red"]:pressed { background-color: #df3b3b; } QAbstractButton[state="yellow"] { background-color: #e6e465; } QAbstractButton[state="yellow"]:hover { background-color: #e8e774; } QAbstractButton[state="yellow"]:pressed { background-color: #e3e155; } qTox/themes/default/chatForm/callButton.svg000066400000000000000000000025301415623743500213270ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/emoteButton.svg000066400000000000000000000031661415623743500215330ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/exitFullScreenButton.svg000066400000000000000000000031521415623743500233510ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/fileButton.svg000066400000000000000000000101771415623743500213410ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/chatForm/fullScreenButtons.css000066400000000000000000000030461415623743500226750ustar00rootroot00000000000000QFrame { background-color: rgba(50, 50, 50, 0.6); border-radius: 20px; } QAbstractButton { background-color: transparent; background-repeat: none; background-position: center; border: none; border-radius: 5px; } QAbstractButton:hover { background-color: rgba(255,255,255, 0.2); } QAbstractButton:pressed { background-color: rgba(0,0,0, 0.2); } QAbstractButton#volButtonFullScreen { background-image: url("@getImagePath(chatForm/volButton.svg)"); width: 11px; height: 9px; padding: 12px; } QAbstractButton#micButtonFullScreen { background-image: url("@getImagePath(chatForm/micButton.svg)"); width: 11px; height: 9px; padding: 12px; } QAbstractButton#videoButtonFullScreen { background-image: url("@getImagePath(chatForm/videoButtonRed.svg)"); width: 37px; height: 33px; } QAbstractButton#videoPreviewButton { background-image: url("@getImagePath(chatForm/videoPreview.svg)"); width: 13px; height: 13px; padding: 10px; } QAbstractButton#exitFullScreenButton { background-image: url("@getImagePath(chatForm/exitFullScreenButton.svg)"); width: 30px; height: 30px; padding: 1px; } QAbstractButton#volButtonFullScreen[state="red"] { background-image: url("@getImagePath(chatForm/volButtonRed.svg)"); } QAbstractButton#micButtonFullScreen[state="red"] { background-image: url("@getImagePath(chatForm/micButtonRed.svg)"); } QAbstractButton#videoPreviewButton[state="red"] { background-image: url("@getImagePath(chatForm/videoPreviewRed.svg)"); } qTox/themes/default/chatForm/labels.css000066400000000000000000000001571415623743500204560ustar00rootroot00000000000000QLabel { color: #000; } QLabel:disabled { color: #ddd; } QLabel[state="red"] { color: #e84747; } qTox/themes/default/chatForm/micButton.svg000066400000000000000000000041101415623743500211600ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/micButtonRed.svg000066400000000000000000000110611415623743500216160ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/screenshotButton.svg000066400000000000000000000007421415623743500225740ustar00rootroot00000000000000 image/svg+xml Layer 1 qTox/themes/default/chatForm/searchCalendarButton.svg000066400000000000000000000075051415623743500233220ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/searchDownButton.svg000066400000000000000000000044251415623743500225160ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/searchHideButton.svg000066400000000000000000000042511415623743500224550ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/searchSettingsButton.svg000066400000000000000000000104011415623743500233760ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/searchUpButton.svg000066400000000000000000000044251415623743500221730ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/sendButton.svg000066400000000000000000000026041415623743500213470ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/chatForm/videoButton.svg000066400000000000000000000021271415623743500215240ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/chatForm/videoButtonRed.svg000066400000000000000000000024261415623743500221610ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/videoPreview.svg000066400000000000000000000042641415623743500216760ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/videoPreviewRed.svg000066400000000000000000000041641415623743500223300ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/volButton.svg000066400000000000000000000036341415623743500212220ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/chatForm/volButtonRed.svg000066400000000000000000000042331415623743500216510ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/contentDialog/000077500000000000000000000000001415623743500175265ustar00rootroot00000000000000qTox/themes/default/contentDialog/contentDialog.css000066400000000000000000000002041415623743500230260ustar00rootroot00000000000000QSplitter:handle { color: white; background-color: white; } QWidget { background: @groundBase; color: @mainText; } qTox/themes/default/emoteButton/000077500000000000000000000000001415623743500172415ustar00rootroot00000000000000qTox/themes/default/emoteButton/emoteButton.css000066400000000000000000000006451415623743500222650ustar00rootroot00000000000000QPushButton { background-color: #6bc260; background-image: url("@getImagePath(emoteButton/emoteButton.svg)"); background-repeat: none; background-position: center; border-top-right-radius: 5px; border: none; width: 24px; height: 24px; } QPushButton:hover { background-color: #79c76f; } QPushButton:pressed { background-color: #51b244; } QPushButton:focus { outline: none; } qTox/themes/default/emoteButton/emoteButton.svg000066400000000000000000000031661415623743500222750ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/emoticonWidget/000077500000000000000000000000001415623743500177155ustar00rootroot00000000000000qTox/themes/default/emoticonWidget/dot_page.svg000066400000000000000000000020061415623743500222160ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/emoticonWidget/dot_page_current.svg000066400000000000000000000020061415623743500237600ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/emoticonWidget/dot_page_hover.svg000066400000000000000000000020061415623743500234210ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/emoticonWidget/emoticonWidget.css000066400000000000000000000013421415623743500234100ustar00rootroot00000000000000QPushButton { background-color: transparent; background-repeat: none; border: none; width: 24px; height: 24px; } QRadioButton::indicator { width: 10px; height: 10px; } QRadioButton::indicator:unchecked { image: url(@getImagePath(emoticonWidget/dot_page.svg)); } QRadioButton::indicator:unchecked:hover { image: url(@getImagePath(emoticonWidget/dot_page_hover.svg)); } QRadioButton::indicator:unchecked:pressed { image: url(@getImagePath(emoticonWidget/dot_page_hover.svg)); } QRadioButton::indicator:checked { image: url(@getImagePath(emoticonWidget/dot_page_current.svg)); } QMenu { background-color: @statusActive; /* sets background of the menu */ border: 0px solid; } qTox/themes/default/fileButton/000077500000000000000000000000001415623743500170475ustar00rootroot00000000000000qTox/themes/default/fileButton/fileButton.css000066400000000000000000000007531415623743500217010ustar00rootroot00000000000000QPushButton { background-color: #6bc260; background-image: url("@getImagePath(fileButton/fileButton.svg)"); background-repeat: none; background-position: center; border-bottom-right-radius: 5px; border: none; width: 24px; height: 24px; } QAbstractButton:hover { background-color: #79c76f; } QPushButton:pressed { background-color: #51b244; } QPushButton[enabled="false"] { background-color: #919191; } QPushButton:focus { outline: none; } qTox/themes/default/fileButton/fileButton.svg000066400000000000000000000101771415623743500217110ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/fileTransferInstance/000077500000000000000000000000001415623743500210455ustar00rootroot00000000000000qTox/themes/default/fileTransferInstance/arrow_white.svg000066400000000000000000000015701415623743500241230ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/fileTransferInstance/browse.svg000066400000000000000000000124141415623743500230710ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/fileTransferInstance/dir.svg000066400000000000000000000042101415623743500223410ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/fileTransferInstance/filetransferWidget.css000066400000000000000000000006311415623743500254070ustar00rootroot00000000000000[fontColor="white"] QLabel { color: white; font: @big; } [fontColor="black"] QLabel { color: @statusActive; font: @big; } QPushButton { margin:0; border: none; } QProgressBar { border: 2px solid @statusActive; border-radius: 0px; background-color: @statusActive; } QProgressBar::chunk { background-color: @transferMiddle; width: 1px; } qTox/themes/default/fileTransferInstance/no.svg000066400000000000000000000033331415623743500222040ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/fileTransferInstance/pause.svg000066400000000000000000000035221415623743500227050ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/fileTransferInstance/yes.svg000066400000000000000000000033001415623743500223620ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/fileTransferWidget/000077500000000000000000000000001415623743500205245ustar00rootroot00000000000000qTox/themes/default/fileTransferWidget/fileDone.svg000066400000000000000000000022101415623743500227650ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/friendList/000077500000000000000000000000001415623743500170375ustar00rootroot00000000000000qTox/themes/default/friendList/friendList.css000066400000000000000000000023341415623743500216560ustar00rootroot00000000000000QScrollArea { background: @themeMedium; } QScrollBar:vertical { background: @themeMedium; width: 16px; padding: 0px 3px 0px 3px; } QScrollBar:handle:vertical { background: @themeDark; min-height: 20px; border-radius: 5px; margin: 3px 0px 3px 0px; } QScrollBar:handle:vertical:hover { background: @themeMediumDark; } QScrollBar:handle:vertical:pressed { background: @themeDark; } QScrollBar:add-line:vertical { height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar:sub-line:vertical { height: 0px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar:add-page:vertical, QScrollBar::sub-page:vertical { background: none; } QWidget#circleWidgetContainer > QFrame#line { color: white; } QWidget#circleWidgetContainer { background-color: @themeMedium; } QWidget#circleWidgetContainer:hover { background-color: @themeLight; } QWidget#circleWidgetContainer QLineEdit { background-color: @themeLight; } QWidget#circleWidgetContainer > QLabel#status { font: @small; color: @groundExtra; } QWidget#circleWidgetContainer > QLabel#name { font: @big; color: @groundBase; } QLabel { color: white; } qTox/themes/default/genericChatForm/000077500000000000000000000000001415623743500177745ustar00rootroot00000000000000qTox/themes/default/genericChatForm/genericChatForm.css000066400000000000000000000000431415623743500235430ustar00rootroot00000000000000QWidget { background: white; } qTox/themes/default/loginScreen/000077500000000000000000000000001415623743500172045ustar00rootroot00000000000000qTox/themes/default/loginScreen/loginScreen.css000066400000000000000000000016101415623743500221640ustar00rootroot00000000000000#LoginScreen { background-color: #1c1c1c; } #line { color: #757575; } #stackedWidget { background-color: #d6d2Cf; } #labelNewProfile, #labelLoadProfile { border-style: solid; border-width: 15px 0 15px 15px; border-color: #d6d2cf #d6d2cf #d6d2cf #1c1c1c; } #labelNewProfile { margin-top:125px; margin-bottom:45px; } #labelLoadProfile { margin-top:165px; margin-bottom:5px; } LoginScreen > QPushButton, QStackedWidget QPushButton { background: transparent; border: none; color: white; font-size: 9pt; font-weight: bold; } #loginButton, #importButton, #createAccountButton { border-radius:5px; padding:5px; background-color:#42a33a; } #loginButton:hover, #importButton:hover, #createAccountButton:hover { background: #0c530d; } QLabel, QCheckBox, QProgressBar { color: black; } QCheckBox:disabled { color: gray; } qTox/themes/default/msgEdit/000077500000000000000000000000001415623743500163305ustar00rootroot00000000000000qTox/themes/default/msgEdit/msgEdit.css000066400000000000000000000007731415623743500204450ustar00rootroot00000000000000QTextEdit { border-color: @groundExtra; border-style: solid; border-width: 1px 0 1px 1px; background: white; } QTextEdit:hover { border-color: #d7d4d1; } QTextEdit:pressed { border-color: #4ea6ea; } QTextEdit#group { /*border-radius: 0 6px 6px 0; would use to round corners in groupchat, but Qt's implementation seems to be bugged*/ border: 1px solid #c4c1bd; } QTextEdit#group:hover { border-color: #d7d4d1; } QTextEdit#group:pressed { border-color: #4ea6ea; } qTox/themes/default/notificationEdge/000077500000000000000000000000001415623743500202075ustar00rootroot00000000000000qTox/themes/default/notificationEdge/notificationEdge.css000066400000000000000000000001611415623743500241720ustar00rootroot00000000000000NotificationEdgeWidget { background-color: #6bc260; } NotificationEdgeWidget > QLabel { color: white; } qTox/themes/default/palette.ini000066400000000000000000000006141415623743500170740ustar00rootroot00000000000000[colors] transferGood="#6bc260" transferWait="#cebf44" transferBad="#c84e4e" transferMiddle="#d1d1d1" mainText="#000000" nameActive="#1c1c1c" statusActive="#414141" groundExtra="#d1d1d1" groundBase="#ffffff" orange="#ff7700" themeDark="#1c1c1c" themeMediumDark="#2a2a2a" themeMedium="#414141" themeLight="#4e4e4e" action="#1818FF" link="#0000ff" searchHighlighted="#ff7626" selectText = "#9edeff"qTox/themes/default/rejectCall/000077500000000000000000000000001415623743500170045ustar00rootroot00000000000000qTox/themes/default/rejectCall/rejectCall.svg000066400000000000000000000024521415623743500216000ustar00rootroot00000000000000 image/svg+xml qTox/themes/default/screenshotButton/000077500000000000000000000000001415623743500203055ustar00rootroot00000000000000qTox/themes/default/screenshotButton/screenshotButton.css000066400000000000000000000007571415623743500244010ustar00rootroot00000000000000QPushButton { background-color: #6bc260; background-image: url("@getImagePath(screenshotButton/screenshotButton.svg)"); background-repeat: none; background-position: center; border-top-left-radius: 5px; border: none; width: 24px; height: 24px; } QPushButton:hover { background-color: #79c76f; } QPushButton:pressed { background-color: #51b244; } QPushButton[enabled="false"] { background-color: #919191; } QPushButton:focus { outline: none; } qTox/themes/default/screenshotButton/screenshotButton.svg000066400000000000000000000007421415623743500244020ustar00rootroot00000000000000 image/svg+xml Layer 1 qTox/themes/default/sendButton/000077500000000000000000000000001415623743500170615ustar00rootroot00000000000000qTox/themes/default/sendButton/sendButton.css000066400000000000000000000006311415623743500217200ustar00rootroot00000000000000QPushButton { background-color: #6bc260; background-image: url("@getImagePath(sendButton/sendButton.svg)"); background-repeat: none; background-position: center; border: none; border-radius: 5px; width: 50px; height: 50px; } QPushButton:hover { background-color: #79c76f; } QPushButton:pressed { background-color: #51b244; } QPushButton:focus { outline: none; } qTox/themes/default/sendButton/sendButton.svg000066400000000000000000000026041415623743500217310ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/settings/000077500000000000000000000000001415623743500165745ustar00rootroot00000000000000qTox/themes/default/settings/mainHead.css000066400000000000000000000001211415623743500210060ustar00rootroot00000000000000QWidget .QLabel, QWidget .QLineEdit { color: black; background: white; } qTox/themes/default/statusButton/000077500000000000000000000000001415623743500174535ustar00rootroot00000000000000qTox/themes/default/statusButton/menu_indicator.svg000066400000000000000000000015361415623743500232010ustar00rootroot00000000000000 image/svg+xmlqTox/themes/default/statusButton/statusButton.css000066400000000000000000000014161415623743500227060ustar00rootroot00000000000000QPushButton { background: none; background-color: @themeMediumDark; border: none; border-radius: 6px; width: 20px; height: 40px; } QPushButton:default { background-color: @themeMediumDark; } /*Bugged in Qt, but it's probably better to leave enabled so that users can tell it's clickable*/ QPushButton:hover { background-color: @themeMedium; } QPushButton:pressed { background-color: @themeMediumDark; } QPushButton:focus { outline: none; } QPushButton::menu-indicator {image: none;} QPushButton::menu-indicator:pressed, QPushButton::menu-indicator:open { image: url("@getImagePath(statusButton/menu_indicator.svg)"); subcontrol-origin: padding; subcontrol-position: bottom center; position: relative; bottom: 2px; } qTox/themes/default/tooliconsZone/000077500000000000000000000000001415623743500176015ustar00rootroot00000000000000qTox/themes/default/tooliconsZone/tooliconsZone.css000066400000000000000000000007601415623743500231630ustar00rootroot00000000000000QPushButton[update-available=true] { background-color: #115508; border: none; } QPushButton:hover[update-available=true] { background-color: #2b9e1c; border: none; } QPushButton { background-color: @themeDark; border: none; } QPushButton:hover { background-color: @themeMediumDark; border: none; } QPushButton:checked { background-color: @themeMedium; border: none; } QPushButton:pressed { background-color: @themeMediumLight; border: none; } qTox/themes/default/window/000077500000000000000000000000001415623743500162435ustar00rootroot00000000000000qTox/themes/default/window/general.css000066400000000000000000000075571415623743500204100ustar00rootroot00000000000000QToolTip { /* explicit border width is required https://bugreports.qt.io/browse/QTBUG-41313 */ border: 0px; color: black; background: #ffffdc; } QDialog { background: white; } QWidget#contentWidget { background: white; } QTabWidget { background-color: white; } QTabBar::tab:selected { background: #2d3136; color: #f1f1f1; } QTabBar::tab:!selected { background: #d0d1d1; color: #5e5e5e; } QLabel { color: @mainText; } QListView { color: @mainText; } QTextEdit, QPlainTextEdit { border-color: @groundExtra; border-style: solid; border-width: 1px 0 1px 1px; background: white; color: @mainText; } QListWidget { background-color: white; } QMessageBox { background-color: white; } QCheckBox { color: black; } QCheckBox:disabled { color: grey; } QSpinBox, QDoubleSpinBox { color: @mainText; background-color: white; } QSpinBox:disabled, QDoubleSpinBox:disabled { color: @mainText; background-color: lightGrey; } QGroupBox { color: black; background-color: white; font: @bigBold; } QComboBox { color: black; background-color: white; } QComboBox QAbstractItemView { background-color: white; } QLineEdit { color: @mainText; background-color: white; } QLineEdit:disabled { color: @mainText; background-color: lightGrey; } QScrollArea { background-color: white; } QScrollArea > QWidget > QWidget { background: transparent; } QScrollArea::corner { background-color: white; border: none; } QScrollBar:vertical { background: transparent; width: 12px; margin-top: 2px; margin-bottom: 2px; } /* using last is a bit of a hack, but QTabBar otherwise doesn't allow selecting single tabs */ QTabBar::tab:last:!selected[update-available=true] { background-color: #80c580; } QPushButton#updateAvailableButton { background-color: #21da21; } QScrollBar::handle:vertical { background-color: #d1d1d1; min-height: 20px; border-radius: 3px; margin-left: 2px; } QScrollBar::handle:vertical:hover { background-color: #e3e3e3; } QScrollBar::handle:vertical:pressed { background-color: #b1b1b1; } QScrollBar::add-line:vertical { background-color: white; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { background-color: white; height: 0px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar:QScrollBar::down-arrow:vertical { width: 10; height: 10px; background-color: white; } QScrollBar:QScrollBar::up-arrow:vertical { width: 10px; height: 10px; background-color: white; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; } QScrollBar:horizontal { background-color: white; height: 10px; margin: 0 2px 0 2px; } QScrollBar::handle:horizontal { background-color: #d1d1d1; min-width: 20px; border-radius: 2px; } QScrollBar::handle:horizontal:hover { background-color: #e3e3e3; } QScrollBar::handle:horizontal:pressed { background-color: #b1b1b1; } QScrollBar::add-line:horizontal { background-color: white; width: 0px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { background-color: white; width: 0px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar:QScrollBar::down-arrow:horizontal { width: 10; height: 10px; background-color: white; } QScrollBar:QScrollBar::up-arrow:horizontal { width: 10px; height: 10px; background-color: white; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; } QRadioButton { background: white; color: black; } QPushButton { background: #ebebeb; color: @mainText; } QSplitter:handle { color: white; background-color: white; } qTox/themes/default/window/profile.css000066400000000000000000000001121415623743500204070ustar00rootroot00000000000000#selfAvatar:hover { border-radius: 6px; background-color: #ccc; } qTox/themes/default/window/statusPanel.css000066400000000000000000000042761415623743500212710ustar00rootroot00000000000000QLineEdit { background: none; background-color: @themeMedium; color: white; border: 0px; border-radius: 4px; } QToolButton { background: none; background-color: @themeMedium; color: white; border-style: none; border-radius: 4px; } QToolButton:pressed { background-color: @themeMediumDark; border-radius: 4px; color: white; } QToolButton::menu-indicator { image: none } QPushButton#green { background: none; background-color: #6bc260; color: white; border-style: none; border-radius: 4px; padding: 4px; margin: 4px 8px; } QPushButton#green:hover { background-color: #79c76f; } QPushButton#green:pressed { background-color: #51b244; } /** Uncomment this after https://github.com/qTox/qTox/pull/1640 is merged! QComboBox:down-arrow { image: url(ui/css/down_arrow.png); } **/ QListView { background-color: @themeLight; border-style: none; border-radius: 4px; } #statusPanel { background-color: @themeDark; } #statusPanel > #statusHead { background-color: @themeDark; } #statusPanel > #statusHead > #nameLabel { background-color: @themeDark; font: @extraBig; color: @groundBase; } #statusPanel > #statusHead > #statusLabel { background-color: @themeDark; font: @medium; color: @groundExtra; } #statusPanel > #statusHead > #statusButton { background: none; background-color: @themeMedium; border: none; border-radius: 6px; width: 20px; height: 40px; } /*Bugged in Qt, but it's probably better to leave enabled so that users can tell it's clickable*/ #statusPanel > #statusHead > #statusButton:hover { background-color: @themeLight; } #statusPanel > #statusHead > #statusButton:pressed { background-color: @themeMedium; } #statusPanel > #statusHead > #statusButton:focus { outline: none; } #statusPanel > #statusHead > #statusButton::menu-indicator {image: none;} #statusPanel > #statusHead > #statusButton::menu-indicator:pressed, #statusPanel > #statusHead > #statusButton::menu-indicator:open { image: url("@getImagePath(statusButton/menu_indicator.svg)"); subcontrol-origin: padding; subcontrol-position: bottom center; position: relative; bottom: 2px; } qTox/themes/default/window/window.css000066400000000000000000000041211415623743500202620ustar00rootroot00000000000000Widget#activeWindow { background-color: #DFDFDF; border: 2px solid #ABABAB; } Widget#inactiveWindow { background-color: #f4f4f4; border: 2px solid #ABABAB; } /* Widget#titleBar { border: 10px solid #ABABAB; border-top: 0px solid transparent; background-color: #DFDFDF; } Widget#titleBarInactive { border: 5px solid #ABABAB; border-top: 0px solid transparent; background-color: #F1F1F1; }*/ QToolButton#tbMenu { border: 0px solid transparent; background-color: transparent; } QPushButton#minimizeButton { border: 0px solid transparent; background-color: transparent; image: url("@getImagePath(window/minimizeButton.png)"); } QPushButton#minimizeButton:hover {image: url("@getImagePath(window/minimizeButtonHover.png)");} QPushButton#minimizeButton:pressed {image: url("@getImagePath(window/minimizeButtonPressed.png)");} QPushButton#minimizeButton:focus {outline: none;} QPushButton#maximizeButton { border: 0px solid transparent; background-color: transparent; image: url("@getImagePath(window/maximizeButton.png"); } QPushButton#maximizeButton:hover {image: url("@getImagePath(window/maximizeButtonHover.png)");} QPushButton#maximizeButton:pressed {image: url("@getImagePath(window/maximizeButtonPressed.png)");} QPushButton#maximizeButton:focus {outline: none;} QPushButton#closeButton { border: 0px solid transparent; background-color: transparent; image: url("@getImagePath(window/closeButton.png"); } QPushButton#closeButton:hover {image: url("@getImagePath(window/closeButtonHover.png)");} QPushButton#closeButton:pressed {image: url("@getImagePath(window/closeButtonPressed.png)");} QPushButton#closeButton:focus {outline: none;} QPushButton#restoreButton { border: 0px solid transparent; background-color: transparent; image: url("@getImagePath(window/restoreButton.png)"); } QPushButton#restoreButton:hover {image: url("@getImagePath(window/restoreButtonHover.png)");} QPushButton#restoreButton:pressed {image: url("@getImagePath(window/restoreButtonPressed.png)");} QPushButton#restoreButton:focus {outline: none;} qTox/tools/000077500000000000000000000000001415623743500131635ustar00rootroot00000000000000qTox/tools/create-tarballs.sh000077500000000000000000000033401415623743500165670ustar00rootroot00000000000000#!/bin/bash # Copyright © 2017-2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Create `lzip` and `gzip` archives and make detached GPG signatures for them. # # When tag name is supplied, it's used to create archives. If there is no tag # name supplied, latest tag is used. # Requires: # * GPG # * git # * gzip # * lzip # usage: # ./$script [$tag_name] # Fail as soon as error appears set -eu -o pipefail archive() { git archive --format=tar --prefix=qTox/ "${@%%.tar.*}" \ | "${@##*.tar.}"ip --best \ > "$@" echo "$@ archive has been created." } sign_archive() { gpg \ --armor \ --detach-sign \ "$@" echo "$@.asc signature has been created." } create_and_sign() { local archives=("$@".tar.{l,g}z) for a in "${archives[@]}" do archive "$a" sign_archive "$a" done } get_tag() { local tname="$@" if [[ -n "$tname" ]] then echo "$tname" else git describe --abbrev=0 fi } main() { create_and_sign "$(get_tag $@)" } main "$@" qTox/tools/deweblate-translation-file.sh000077500000000000000000000043631415623743500207350ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016 Zetok Zalbavar # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Script to run to clean up translations from Weblate. # # It detects whether current HEAD commit seems to be a translation from # Weblate, and if it is, it proceeds to update the translation commit by # amending it. # # Note: the script assumes: # * there is only 1 translation file modified # * translation commit strictly adheres to consistent commit naming used # * script is called from the root of repo # # If those assumptions aren't met, the end result won't be what you want. set -eu -o pipefail # get title of the last commit get_commit_title() { git log --format=format:%s HEAD~1..HEAD } # get the whole commit message get_whole_commit_name() { git log --format=format:%B HEAD~1..HEAD } # bool, whether HEAD commit is a weblate translation is_webl_tr() { local re='^feat\(l10n\): update .* translation from Weblate$' local commit=$(get_commit_title) [[ $commit =~ $re ]] } # get filename of file to be updated get_filename() { local raw=( $(git log --raw | egrep '^:[[:digit:]]{6}' | head -n1) ) local re='^translations/.+\.ts$' [[ ${raw[5]} =~ $re ]] # check if that's actually right, if not, fail here echo ${raw[5]} } # call the other script to update && amend update() { local file=$(get_filename) local commit_msg=$(get_whole_commit_name) ./tools/update-translation-files.sh "$file" git commit -S --amend -m "$commit_msg" "$file" } main() { is_webl_tr \ && update } main qTox/tools/format-code.sh000077500000000000000000000024521415623743500157250ustar00rootroot00000000000000#!/bin/bash # Copyright © 2017-2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Format all C++ codebase tracked by git using `clang-format`. # Requires: # * git # * clang-format # usage: # ./$script # Fail as soon as error appears set -eu -o pipefail readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly BASE_DIR="$SCRIPT_DIR/../" format() { cd "$BASE_DIR" [[ -f .clang-format ]] # make sure that it exists # NOTE: some earlier than 3.8 versions of clang-format are broken # and will not work correctly clang-format -i -style=file $(git ls-files *.cpp *.h) } format qTox/tools/lib/000077500000000000000000000000001415623743500137315ustar00rootroot00000000000000qTox/tools/lib/PR_bash.source000066400000000000000000000060311415623743500164710ustar00rootroot00000000000000#!/bin/bash # # Copyright © 2016 Zetok Zalbavar # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Source for bash functions used in `/{merge,test}-pr.sh`. # # Only to be sourced in those files. set -e -o pipefail # check if supplied var is a number; if not exit is_pr_number() { [[ "$@" =~ ^[[:digit:]]+$ ]] } exit_if_not_pr() { is_pr_number $@ \ || (echo "Not a PR number!" \ && exit 1 ) } # check if remote is present is_remote_present() { git remote \ | grep $@ > /dev/null } # there's no qTox remote, add it # if `https` is supplied, https version of repo is used add_remote() { local remote_url="git@github.com:qTox/qTox.git" local remote_name="upstream" # change to https if needed [[ "$@" == "https" ]] \ && local remote_url="https://github.com/qTox/qTox.git" is_remote_present $remote_name \ || git remote add $remote_name "${remote_url}" } # print the message only if the merge was successful # # supply either `merge`, `test` or whatever else merge branch name you want after_merge_msg() { echo "" echo "PR #$PR was merged into «$@$PR» branch." echo "To compare with $base_branch:" echo "" echo " git diff $base_branch..$@$PR" echo "" if [[ "$@" == "merge" ]] then echo "To push that to $base_branch on github:" echo "" echo " git checkout $base_branch && git merge --ff $@$PR && git push upstream $base_branch" echo "" echo "After pushing to $base_branch, delete branches:" echo "" echo " git branch -d {$@,}$PR" echo "" fi echo "To discard any changes:" echo "" echo " git checkout $base_branch && git branch -D {$@,}$PR" echo "" } # print the message only if some merge step failed after_merge_failure_msg() { echo "" echo "Merge failed." echo "" echo "You may want to remove not merged branches, if they exist:" echo "" echo " git checkout $base_branch && git branch -D {$@,}$PR" echo "" } # force delete merged and fetched branches rm_obsolete_branch() { git branch -D {$merge_branch,}$PR 2>/dev/null || true } get_sources() { add_remote rm_obsolete_branch git fetch $remote_name pull/$PR/head:$PR && \ git checkout $base_branch -b $merge_branch$PR } # check whether to sign merge() { "${@}" --no-ff $PR -m "Merge pull request #$PR $OPT_MSG $(git shortlog $base_branch..$PR)" } qTox/tools/update-nodes.sh000077500000000000000000000022111415623743500161060ustar00rootroot00000000000000#!/bin/bash # Copyright © 2020 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # script to update the list of bootstrap nodes # # it should be run before releasing a new version ## # requires: # * curl # usage: # # ./$script set -eu -o pipefail readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" readonly NODES_FILE="$SCRIPT_DIR/../res/nodes.json" readonly NODES_URL="https://nodes.tox.chat/json" curl "$NODES_URL" --output "$NODES_FILE" qTox/tools/update-toxcore-version.sh000077500000000000000000000060341415623743500201530ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # script to change targeted toxcore version # # it should be run before releasing a new version # usage: # # ./$script $version # # $version has to be composed of at least one number/dot set -eu -o pipefail readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" readonly BASE_DIR="$SCRIPT_DIR/../" readonly VERSION_PATTERN="[0-9]+\.[0-9]+\.[0-9]+" update_install() { perl -i -0pe "s|(git clone https://github.com/toktok/c-toxcore.*?)$VERSION_PATTERN|\${1}$@|gms" INSTALL.md perl -i -0pe "s|(git clone https://github.com/toktok/c-toxcore.*?)$VERSION_PATTERN|\${1}$@|gms" INSTALL.fa.md } update_bootstrap() { perl -i -0pe "s|(TOXCORE_VERSION=\"v)$VERSION_PATTERN|\${1}$@|gms" bootstrap.sh } update_osx() { cd osx perl -i -0pe "s|$VERSION_PATTERN( --depth=1 https://github.com/toktok/c-toxcore)|$@\${1}|gms" qTox-Mac-Deployer-ULTIMATE.sh cd .. } update_flatpak() { cd flatpak latest_tag_ref=$(git ls-remote --tags https://github.com/toktok/c-toxcore | sort -V -k2 | tail -n1) ref_array=($latest_tag_ref) commit_hash=${ref_array[0]} perl -i -0pe "s|(https://github.com/toktok/c-toxcore.*?)$VERSION_PATTERN(.*?)[a-f0-9]{40}|\${1}$@\${2}$commit_hash|gms" io.github.qtox.qTox.json cd .. } update_travis() { cd .travis perl -i -0pe "s|$VERSION_PATTERN( --depth=1 https://github.com/toktok/c-toxcore)|$@\${1}|gms" build-ubuntu-16-04.sh cd .. } update_docker() { cd docker perl -i -0pe "s|(https://github.com/toktok/c-toxcore.*?)$VERSION_PATTERN|\${1}$@|gms" Dockerfile.debian perl -i -0pe "s|(https://github.com/toktok/c-toxcore.*?)$VERSION_PATTERN|\${1}$@|gms" Dockerfile.ubuntu cd .. } update_windows() { cd windows/cross-compile perl -i -0pe "s|(TOXCORE_VERSION=)$VERSION_PATTERN|\${1}$@|gms" build.sh echo "Manually update the Windows toxcore hash in windows/cross-compile/build.sh" cd ../.. } # exit if supplied arg is not a version is_version() { if [[ ! $@ =~ $VERSION_PATTERN ]] then echo "Not a version: $@" echo "Must match: $VERSION_PATTERN" exit 1 fi } main() { is_version "$@" update_install "$@" update_bootstrap "$@" update_osx "$@" update_flatpak "$@" update_travis "$@" update_docker "$@" update_windows "$@" } main "$@" qTox/tools/update-translation-files.sh000077500000000000000000000026141415623743500204430ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016 Zetok Zalbavar # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Script for updating translation files. Should be ran after # translatable strings are modified. # # Needed, since Weblate cannot do it automatically. # Usage: # ./tools/$script_name [ALL|translation file] set -eu -o pipefail readonly COMMIT_MSG="chore(i18n): update translation files for Weblate" readonly LUPDATE_CMD="lupdate src -no-obsolete -locations none -ts" if [[ "$@" = "ALL" ]] then for translation in translations/*.ts do $LUPDATE_CMD "$translation" done git add translations/*.ts git commit -m "$COMMIT_MSG" else $LUPDATE_CMD "$@" fi qTox/tools/update-versions.sh000077500000000000000000000050371415623743500166570ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # script to change versions in the files for osx and windows "packages" # # it should be run before releasing a new version # # NOTE: it checkouts the files before appending a version to them! # # requires: # * GNU sed # usage: # # ./$script $version # # $version has to be composed of at least one number/dot set -eu -o pipefail readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" readonly BASE_DIR="$SCRIPT_DIR/../" readonly VERSION_PATTERN="[0-9]+\.[0-9]+\.[0-9]+" update_windows() { ( cd "$BASE_DIR/windows" ./qtox-nsi-version.sh "$@" ) } update_osx() { ( cd "$BASE_DIR/osx" ./update-plist-version.sh "$@" ) } update_readme() { cd "$BASE_DIR" sed -ri "s|(github.com/qTox/qTox/releases/download/v)$VERSION_PATTERN|\1$@|g" README.md # for flatpak and AppImage sed -ri "s|(github.com/qTox/qTox/releases/download/v$VERSION_PATTERN/qTox-v)$VERSION_PATTERN|\1$@|g" README.md } update_appdata() { cd "$BASE_DIR"/res/ local isodate="$(date --iso-8601)" sed -ri "s|( # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Script to update the translations from Weblate. Works only if there are no # merge conflicts. Assumes that working dir is a qTox git repo. # # Requires a GPG key to sign merge commits. # usage: # ./$script set -e -o pipefail readonly COMMIT_NAME="chore(l10n): update translations from Weblate " source_functions() { local fns_file="tools/lib/PR_bash.source" source $fns_file } # If there's no qTox Weblate remote, add it add_remote_weblate() { local remote_url="https://hosted.weblate.org/git/tox/qtox/" local remote_name="weblate" is_remote_present $remote_name \ || git remote add $remote_name "${remote_url}" } do_merge() { # update commits git fetch upstream git fetch weblate git checkout upstream/master local COMMIT_MESSAGE=`git shortlog upstream/master..weblate/master` # squash and merge git merge --squash --no-edit weblate/master # update format for adapt to Qt translation format ./tools/deweblate-translation-file.sh ./translations/*.ts || true # commit git commit --no-edit -S -m "$COMMIT_NAME" -m "$COMMIT_MESSAGE" } main() { source_functions add_remote add_remote_weblate do_merge echo "You can now checkout the changes with:" echo "" echo " git checkout -b " echo "" } main qTox/translations/000077500000000000000000000000001415623743500145445ustar00rootroot00000000000000qTox/translations/README.md000066400000000000000000000212021415623743500160200ustar00rootroot00000000000000# Translating Translating qTox should be easy & straightforward for anyone using [**Weblate**](https://hosted.weblate.org/projects/tox/qtox/). Overall translation: [![Translation status](https://hosted.weblate.org/widgets/tox/-/svg-badge.svg)](https://hosted.weblate.org/engage/tox/?utm_source=widget) Language | Status -------- | ------ [Arabic](https://hosted.weblate.org/engage/tox/ar/) | [![Translation status](https://hosted.weblate.org/widgets/tox/ar/svg-badge.svg)](https://hosted.weblate.org/engage/tox/ar/?utm_source=widget) [Беларуская](https://hosted.weblate.org/engage/tox/be/) | [![Translation status](https://hosted.weblate.org/widgets/tox/be/svg-badge.svg)](https://hosted.weblate.org/engage/tox/be/?utm_source=widget) [Български](https://hosted.weblate.org/engage/tox/bg/) | [![Translation status](https://hosted.weblate.org/widgets/tox/bg/svg-badge.svg)](https://hosted.weblate.org/engage/tox/bg/?utm_source=widget) [Čeština](https://hosted.weblate.org/engage/tox/cs/) | [![Translation status](https://hosted.weblate.org/widgets/tox/cs/svg-badge.svg)](https://hosted.weblate.org/engage/tox/cs/?utm_source=widget) [Dansk](https://hosted.weblate.org/engage/tox/da/) | [![Translation status](https://hosted.weblate.org/widgets/tox/da/svg-badge.svg)](https://hosted.weblate.org/engage/tox/da/?utm_source=widget) [Deutsch](https://hosted.weblate.org/engage/tox/de/) | [![Translation status](https://hosted.weblate.org/widgets/tox/de/svg-badge.svg)](https://hosted.weblate.org/engage/tox/de/?utm_source=widget) [Eesti](https://hosted.weblate.org/engage/tox/et/) | [![Translation status](https://hosted.weblate.org/widgets/tox/et/svg-badge.svg)](https://hosted.weblate.org/engage/tox/et/?utm_source=widget) [Ελληνικά](https://hosted.weblate.org/engage/tox/el/) | [![Translation status](https://hosted.weblate.org/widgets/tox/el/svg-badge.svg)](https://hosted.weblate.org/engage/tox/el/?utm_source=widget) [Español](https://hosted.weblate.org/engage/tox/es/) | [![Translation status](https://hosted.weblate.org/widgets/tox/es/svg-badge.svg)](https://hosted.weblate.org/engage/tox/es/?utm_source=widget) [Esperanto](https://hosted.weblate.org/engage/tox/eo/) | [![Translation status](https://hosted.weblate.org/widgets/tox/eo/svg-badge.svg)](https://hosted.weblate.org/engage/tox/eo/?utm_source=widget) [فارسی](https://hosted.weblate.org/engage/tox/fa/) | [![Translation status](https://hosted.weblate.org/widgets/tox/fa/svg-badge.svg)](https://hosted.weblate.org/engage/tox/fa/?utm_source=widget) [Français](https://hosted.weblate.org/engage/tox/fr/) | [![Translation status](https://hosted.weblate.org/widgets/tox/fr/svg-badge.svg)](https://hosted.weblate.org/engage/tox/fr/?utm_source=widget) [한국어](https://hosted.weblate.org/engage/tox/ko/) | [![Translation status](https://hosted.weblate.org/widgets/tox/ko/svg-badge.svg)](https://hosted.weblate.org/engage/tox/ko/?utm_source=widget) [עברית](https://hosted.weblate.org/engage/tox/he/) | [![Translation status](https://hosted.weblate.org/widgets/tox/he/svg-badge.svg)](https://hosted.weblate.org/engage/tox/he/?utm_source=widget) [Hrvatski](https://hosted.weblate.org/engage/tox/hr/) | [![Translation status](https://hosted.weblate.org/widgets/tox/hr/svg-badge.svg)](https://hosted.weblate.org/engage/tox/hr/?utm_source=widget) [Italiano](https://hosted.weblate.org/engage/tox/it/) | [![Translation status](https://hosted.weblate.org/widgets/tox/it/svg-badge.svg)](https://hosted.weblate.org/engage/tox/it/?utm_source=widget) [Kiswahili](https://hosted.weblate.org/engage/tox/sw/) | [![Translation status](https://hosted.weblate.org/widgets/tox/sw/svg-badge.svg)](https://hosted.weblate.org/engage/tox/sw/?utm_source=widget) [Lietuvių](https://hosted.weblate.org/engage/tox/lt/) | [![Translation status](https://hosted.weblate.org/widgets/tox/lt/svg-badge.svg)](https://hosted.weblate.org/engage/tox/lt/?utm_source=widget) [Lojban](https://hosted.weblate.org/engage/tox/jbo/) | [![Translation status](https://hosted.weblate.org/widgets/tox/jbo/svg-badge.svg)](https://hosted.weblate.org/engage/tox/jbo/?utm_source=widget) [Magyar](https://hosted.weblate.org/engage/tox/hu/) | [![Translation status](https://hosted.weblate.org/widgets/tox/hu/svg-badge.svg)](https://hosted.weblate.org/engage/tox/hu/?utm_source=widget) [Македонски](https://hosted.weblate.org/engage/tox/mk/) | [![Translation status](https://hosted.weblate.org/widgets/tox/mk/svg-badge.svg)](https://hosted.weblate.org/engage/tox/mk/?utm_source=widget) [Nederlands](https://hosted.weblate.org/engage/tox/nl/) | [![Translation status](https://hosted.weblate.org/widgets/tox/nl/svg-badge.svg)](https://hosted.weblate.org/engage/tox/nl/?utm_source=widget) [日本語](https://hosted.weblate.org/engage/tox/ja/) | [![Translation status](https://hosted.weblate.org/widgets/tox/ja/svg-badge.svg)](https://hosted.weblate.org/engage/tox/ja/?utm_source=widget) [Norsk Bokmål](https://hosted.weblate.org/engage/tox/no_NB/) | [![Translation status](https://hosted.weblate.org/widgets/tox/no_NB/svg-badge.svg)](https://hosted.weblate.org/engage/tox/no_NB/?utm_source=widget) [Pirate](https://hosted.weblate.org/engage/tox/pr/) | [![Translation status](https://hosted.weblate.org/widgets/tox/pr/svg-badge.svg)](https://hosted.weblate.org/engage/tox/pr/?utm_source=widget) [Polski](https://hosted.weblate.org/engage/tox/pl/) | [![Translation status](https://hosted.weblate.org/widgets/tox/pl/svg-badge.svg)](https://hosted.weblate.org/engage/tox/pl/?utm_source=widget) [Português](https://hosted.weblate.org/engage/tox/pt/) | [![Translation status](https://hosted.weblate.org/widgets/tox/pt/svg-badge.svg)](https://hosted.weblate.org/engage/tox/pt/?utm_source=widget) [Português brasileiro](https://hosted.weblate.org/engage/tox/pt_BR/) | [![Translation status](https://hosted.weblate.org/widgets/tox/pt_BR/svg-badge.svg)](https://hosted.weblate.org/engage/tox/pt_BR/?utm_source=widget) [Română](https://hosted.weblate.org/engage/tox/ro/) | [![Translation status](https://hosted.weblate.org/widgets/tox/ro/svg-badge.svg)](https://hosted.weblate.org/engage/tox/ro/?utm_source=widget) [Русский](https://hosted.weblate.org/engage/tox/ru/) | [![Translation status](https://hosted.weblate.org/widgets/tox/ru/svg-badge.svg)](https://hosted.weblate.org/engage/tox/ru/?utm_source=widget) [Slovenčina](https://hosted.weblate.org/engage/tox/sk/) | [![Translation status](https://hosted.weblate.org/widgets/tox/sk/svg-badge.svg)](https://hosted.weblate.org/engage/tox/sk/?utm_source=widget) [Slovenščina](https://hosted.weblate.org/engage/tox/sl/) | [![Translation status](https://hosted.weblate.org/widgets/tox/sl/svg-badge.svg)](https://hosted.weblate.org/engage/tox/sl/?utm_source=widget) [Српски](https://hosted.weblate.org/engage/tox/sr/) | [![Translation status](https://hosted.weblate.org/widgets/tox/sr/svg-badge.svg)](https://hosted.weblate.org/engage/tox/sr/?utm_source=widget) [Srpski](https://hosted.weblate.org/engage/tox/sr_Latn/) | [![Translation status](https://hosted.weblate.org/widgets/tox/sr_Latn/svg-badge.svg)](https://hosted.weblate.org/engage/tox/sr_Latn/?utm_source=widget) [Suomi](https://hosted.weblate.org/engage/tox/fi/) | [![Translation status](https://hosted.weblate.org/widgets/tox/fi/svg-badge.svg)](https://hosted.weblate.org/engage/tox/fi/?utm_source=widget) [Svenska](https://hosted.weblate.org/engage/tox/sv/) | [![Translation status](https://hosted.weblate.org/widgets/tox/sv/svg-badge.svg)](https://hosted.weblate.org/engage/tox/sv/?utm_source=widget) [தமிழ்](https://hosted.weblate.org/engage/tox/ta/) | [![Translation status](https://hosted.weblate.org/widgets/tox/ta/svg-badge.svg)](https://hosted.weblate.org/engage/tox/ta/?utm_source=widget) [Türkçe](https://hosted.weblate.org/engage/tox/tr/) | [![Translation status](https://hosted.weblate.org/widgets/tox/tr/svg-badge.svg)](https://hosted.weblate.org/engage/tox/tr/?utm_source=widget) [ئۇيغۇرچە](https://hosted.weblate.org/engage/tox/ug/) | [![Translation status](https://hosted.weblate.org/widgets/tox/ug/svg-badge.svg)](https://hosted.weblate.org/engage/tox/ug/?utm_source=widget) [Українська](https://hosted.weblate.org/engage/tox/uk/) | [![Translation status](https://hosted.weblate.org/widgets/tox/uk/svg-badge.svg)](https://hosted.weblate.org/engage/tox/uk/?utm_source=widget) [中文(中国)](https://hosted.weblate.org/engage/tox/zh_CN/) | [![Translation status](https://hosted.weblate.org/widgets/tox/zh_CN/svg-badge.svg)](https://hosted.weblate.org/engage/tox/zh_CN/?utm_source=widget) [繁體中文(台灣)](https://hosted.weblate.org/engage/tox/zh_TW/) | [![Translation status](https://hosted.weblate.org/widgets/tox/zh_TW/svg-badge.svg)](https://hosted.weblate.org/engage/tox/zh_TW/?utm_source=widget) qTox/translations/ar.ts000066400000000000000000003463621415623743500155340ustar00rootroot00000000000000 AVForm Default resolution الأبعاد الطبيعية Audio/Video صوت/فيديو Disabled مٌعطل Select region أختر المنطقة Screen %1 الشاشة %1 Audio Settings إعدادات الصوت Gain الكسب Playback device جهاز مكبر الصوت Use slider to set volume of your speakers. استخدام شريط التمرير لضبط مستوى الصوت الخاص بالسماعة. Capture device جهاز التسجيل Volume الصوت Video Settings اعدادات الفيديو Video device جهاز الفيديو Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. ضع الدقة المناسبة للكاميرا. أعلى القيم هي أفضل جودة قد يحصل عليها أصدقائك. ملاحظة على الرقم من الحصول على أفضل جودة هناك إحتياج لإنترنت أفضل. في بعض الأحيان الإتصال قد لا يكون جيداً بما يكفي للتعامل مع أعلى جودة للفيديو, مما قد يؤدي إلى مشاكل في مكالمات الفيديو. Resolution الدقة Rescan devices إعادة فحص الأجهزة Test Sound اختبار الصوت Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Medium (32 kbps) Low (16 kbps) Very low (8 kbps) Threshold AboutForm About نبذة Original author: %1 المؤلف الأصلي: %1 You are using qTox version %1. حالياً تستخدم كيوتوكس الإصدار %1. Commit hash: %1 إيداع رمز التهشير: %1 toxcore version: %1 إصدار toxcore: %1 Qt version: %1 Qt نسخة: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` لائحة بجميع المشاكل المعروفة موجودة في %1 على GitHub. اذا وجدت خطأ ما أو ضعف أمني في qTox, الرجاء التبليغ عنها فيما يتوافق في التعليمات الموجود على %2 مقالة الويكي. Click here to report a bug. اضغط هنا لكتابة تقرير عن الخطأ. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` رؤية قائمة كاملة من %1 في Github bug-tracker Replaces `%1` in the `A list of all known…` متتبع الأخطاء Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` كتابة تقارير مفيدة بالأخطاء contributors Replaces `%1` in `See a full list of…` المساهمين AboutFriendForm Dialog username اسم المستخدم status message الحالة Used aliases: الاسم: HISTORY OF ALIASES Automatically accept files from contact if set قبول الملفات تلقائيا من جهة الاتصال المسجلة Auto accept files القبول التلقائي للملفات Default directory to save files: مسار حفظ الملفات: Auto accept for this contact is disabled القبول التلقائي للملفات من هذا الشخص معطل Auto accept call: قبول المكالمات تلقائياً: Manual يدوي Audio الصوت Audio + Video الصوت + التسجيل المرئي Automatically accept group chat invitations from this contact if set. Auto accept group invites Remove history (operation can not be undone!) حذف السجل (العملية لا يمكن التراجع عنها) Notes ملاحظات Input field for notes about the contact حقل لادخال للملاحظات عن جهة الاتصار You can save comment about this contact here. يمكنك حفظ بعض الملاحظات عن هذا الشخص هنا. History removed تم مسح السجل Choose an auto accept directory popup title <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation تأكيد Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version الاصدار License ترخيص Authors المؤلفون Known Issues المشاكل الشائعة Open update download link Update available qTox is up to date ✓ AddFriendForm Couldn't add friend لا يمكن اضافة صديق Invalid Tox ID format صيغة المعرف خاطئة Add Friends إضافة صديق Send friend request ارسال طلب اضافة Add a friend إضافة صديق Friend requests قائمة الاضافات Accept قبول Reject رفض Tox ID, either 76 hexadecimal characters or name@example.com (Tox ID) هوية Tox , اما 76 حرف بصيغة ستة عشرية أو بشكل بريد الكتروني name@example.com Type in Tox ID of your friend ادخل هوية Tox (Tox ID) الخاصة بصديقك Friend request message رسالة طلب صداقة Type message to send with the friend request or leave empty to send a default message اكتب رسالة لارسالها مع طلب الصداقة او اترك فارغا لارسال الرسالة الافتراضية %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend لا تستطيع اضافة نفسك كصديق! Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to حساب التوكس "Tox ID" either 76 hexadecimal characters or name@example.com Tox ID format description إما 76 حرف ست عشري أو name@example.com Message The message you send in friend requests رسالة طلب الاضافة Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 هنا! هل تود التواصل بالتوكس معي؟ Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced خصائص Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. إلا إذا كنت %1 تعرف ما تقومون به، يرجى %2 حتى تغيير أي شيء هنا. التغييرات التي تم إجراؤها هنا قد تؤدي إلى مشاكل مع كيوتوكس، وحتى إلى فقدان البيانات الخاصة بك، على سبيل المثال، التاريخ. really حقا not ليس IMPORTANT NOTE ملاحظة هامة Reset settings استعادة الضبط All settings will be reset to default. Are you sure? ستعاد جميع الإعدادات للضبط الافتراضي. هل أنت قيد الموافقة؟ Yes نعم No لا Call active popup title المكالمة نشطة You can't disconnect while a call is active! popup text لا تستطيع قطع الاتصال اثناء المكالمة! Save File حفظ الملف Logs (*.log) سجلات (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox حفظ الاعدادات الى مسار العمل Make Tox portable اجعل توكس متنقل Reset to default settings إعادة الاعدادات الافتراضية Portable متنقل Connection Settings إعدادات الإتصال Enable IPv6 (recommended) Text on a checkbox to enable IPv6 تفعيل IPv6 (موصى به ) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip تعطيلها بهذا يسمح، على سبيل المثال، استخدام التوكس خلال شبكة تور "Tor". فإنه يضيف تحميله إلى الشبكة توكس"Tox" ولكن، حتى يتم إلغائة إلا عند الضرورة. Enable UDP (recommended) Text on checkbox to disable UDP تفعيل UDP (موصى به) Proxy type: نوع الوكيل(بروكسي): Address: Text on proxy addr label العنوان: Port: Text on proxy port label المنفذ: None لا شيئ SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button إعادة الإتصال Debug تصحيح الأخطاء Export Debug Log تصدير سجل تصحيح الاخطاء Copy Debug Log نسخ سجل تصحيح الاخطاء Enable LAN discovery ChatForm Send a file ارسال ملف Unable to open غير قادر على الفتح qTox wasn't able to open %1 qTox غير قادر على فتح %1 Bad idea اقتراح ضعيف %1 calling %1 يتصل Calling %1 يتصل %1 Failed to open temporary file Temporary file for screenshot فشل فتح الملف المؤقت qTox wasn't able to save the screenshot qTox غير قادر على حفظ لقطة الشاشة Call with %1 ended. %2 المكالمة مع %1 انتهت. %2 Call duration: مدة المكالمة: %1 is typing %1 يجري الكتابة Copy نسخ You're trying to send a sequential file, which is not going to work! لا يمكن ارسال ملف متسلسل! %1 is now %2 e.g. "Dubslow is now online" %1 الأن %2 Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call لا يمكن بدء المكالمة الصوتية Start audio call End audio call Cancel audio call الغاء المكالمة الصوتية Accept audio call قبول مكالمة صوتية Can't start video call لا يمكن بدء مكالمة فيديو Start video call بدء مكالمة فيديو End video call Cancel video call الغاء المكالمة المرئية Accept video call قبول مكالمة فيديو Sound can be disabled only during a call يمكن تعطيل الصوت فقط أثناء المكالمة Unmute call Mute call كتم المكالمة Microphone can be muted only during a call يمكن كتم صوت الميكروفون فقط أثناء المكالمة Unmute microphone Mute microphone كتم المايكروفون ChatLog pending قيد الانتظار Copy المحادثة Select all تحديد الكل ChatTextEdit Type your message here... اكتب رسالتك هنا... CircleWidget Rename circle Menu for renaming a circle اعادة تسمية القائمة Remove circle Menu for removing a circle ازالة القائمة Open all in new window فتح الجميع في نوافذ مستقلة Core /me offers friendship, "%1" /me طلب إضافة, "%1" Invalid Tox ID Error while sending friendship request معرف Tox غير صالح You need to write a message with your request Error while sending friendship request تحتاج كتابة رسالة مع الطلب Your message is too long! Error while sending friendship request رسالتك طويلة جدا! Friend is already added Error while sending friendship request تمت الاضافة فعلاً Groupchat %1 DesktopNotify New message رسالة جديدة Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form النوع 10Mb 0kb/s ETA:10:10 Filename اسم الملف Waiting to send... file transfer widget انتظار للارسال... Accept to receive this file file transfer widget قبول تسلم هذا الملف Location not writable Title of permissions popup المسار غير قابل للكتابة You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup لا يوجد لديك صلاحيات للكتابة في هذا المسار. اختر مسار اخر, او الغِ نافذة الحفظ. Paused file transfer widget موقَف Resuming... file transfer widget استكمال... Open file فتح الملف Open file directory فتح مسار الملف Pause transfer ايقاف النقل Cancel transfer الغاء النقل Resume transfer اكمال النقل Accept transfer قبول النقل Save a file Title of the file saving dialog حفظ الملف Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window الملفات المنقولة Downloads التحميلات Uploads المرسل(رفع) FriendListWidget Today اليوم Yesterday امس Last 7 days اخر سبع أيام This month هذا الشهر Older than 6 Months اكثر من 6 شهور Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request طلب صداقة Someone wants to make friends with you هناك شخص يريد ان يجعلك صديقاً User ID: معرف الحساب: Friend request message: رسالة طلب الصداقة: Accept Accept a friend request قبول Reject Reject a friend request رفض FriendWidget Open chat in new window فتح المحادثة في مجموعة جديدة Remove chat from this window ازالة المحادثة من هذه النافذة Invite to group Menu to invite a friend to a groupchat دعوة لمجموعة Move to circle... Menu to move a friend into a different circle نقل الى قائمة... To new circle الى قائمة جديدة Remove from circle '%1' ازالة من القائمة '%1' Move to circle "%1" نقل الى القائمة "%1" Set alias... تعيين الاسم... Auto accept files from this friend context menu entry قبول الملفات تلقائياً من هذا الصديق Remove friend Menu to remove the friend from our friendlist ازالة صديق Show details اظهار التفاصيل Choose an auto accept directory popup title اختر مجلد الحفظ التلقائي New message رسالة جديدة Online متصل Away في الخارج Busy مشغول Offline غير متصل To new group إلى مجموعة جديدة Invite to group '%1' إضافة إلى مجموعة '%1' GeneralForm Choose an auto accept directory popup title اختيار مسار الحفظ التلقائي General عام GeneralSettings General Settings الإعدادات العامة The translation may not load until qTox restarts. قد لا يتم تحميل الترجمة حتى تعيد تشغيل qTox . Language: اللغة: Start qTox on operating system startup (current profile). تشغيل البرنامج بشكل تلقائي عند تشغيل النظام(بالحساب الحالي). Autostart تشغيل تلقائي Enable light tray icon. toolTip for light icon setting تفعيل الأيقونة المضيئة. Light icon تغيير شكل الايقونه Show system tray icon إظهار الايقونة المصغرة qTox will start minimized in tray. toolTip for Start in tray setting تشغيل البرنامج في أيقونة الشاشة. Start in tray التشغيل في الأيقونة After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting عند الضغط على رمز تصغير الشاشه (_) سيذهب البرنامج الى أيقونه الشاشه بدلا من شريط المهام. Minimize to tray التصغير للأيقونة After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting عند الضغط على رمز إغلاق الشاشه (X) سيذهب البرنامج الى أيقونه الشاشه بدلا من أن يغلق تلقائيا. Close to tray الإغلاق للأيقونة Your status is changed to Away after set period of inactivity. يتم تغيير حالتك إلى بالخارج بعد فترة من الخمول. Auto away after (0 to disable): تغيير الحالة الى في الخارج بعد ( 0 للتعطيل): Set to 0 to disable 0 للتعطيل Set where files will be saved. إختر اين يجب أن تحفظ الملفات. Default directory to save files: مسار حفظ الملفات: You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip يمكنك تفعيلها عن طريق النقر بالزر الايمن على جهة الإتصال. Autoaccept files قبول تلقائي للملفات Show contacts' status changes إظهار تغيرات حالات جهات الإتصال Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Save chat log حفظ سجل المحادثة Cleared تم التنظيف Send message إرسال رسالة Smileys الإبتسامات Send file(s) ارسال ملف Send a screenshot ارسال لقطة شاشة Clear displayed messages تنظيف الرسائل المعروضة(حذفها) Quote selected text إقتباس الخط المحدد Copy link address انسخ عنوان الرابط Confirmation تأكيد You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... تحميل سجل الرسائل... Export to file GenericNetCamView Tox video فيديو التوكس "Tox" Show Messages اضهار الرسائل Hide Messages إخفاء الرسائل Full Screen Toggle video preview Mute audio Mute microphone كتم المايكروفون End video call Exit full screen GroupChatForm %1 has set the title to %2 %1 قد وضع عنوان على %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups المجموعات Create new group إنشاء مجموعة جديدة Group invites إضافات المجموعة GroupInviteWidget Invited by %1 on %2 at %3. تمت دعوتهم بنسبة %1 على %2 في %3. Join انضم Decline انخفاض GroupWidget Open chat in new window فتح المحادثة في نافذة مستقلة Remove chat from this window ازالة المحادثة من هذه النافذة Set title... ضع عنواناً... Quit group Menu to quit a groupchat الخروج من المجموعة %n user(s) in chat Number of users in chat New Message Online متصل IdentitySettings Public Information معلومات عامة Tox ID حساب التوكس "Tox ID" This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip هذا معرفك الخاص , شاركه مع اصدقائك في Tox . Your Tox ID (click to copy) المعرف الخاص بك (اضغط هنا للنسخ) This QR code contains your Tox ID. You may share this with your friends as well. رمز QR هذا يتعلق بمعرفك . تستطيع مشاركته مع اصدقائك. Save image حفظ الصورة Copy image نسخ الصورة Profile الملف الشخصي Rename profile. tooltip for renaming profile button اعادة تسمية الحساب. Rename rename profile button اعادة تسمية Delete profile. delete profile button tooltip حذف الحساب. Delete delete profile button حذف Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button يسمح لك بتصدير الملف الشخصي. الملف الشخصي لا يحتوي السجل. Export export profile button تصدير Go back to the login screen tooltip for logout button الرجوع لنافذة تسجيل الدخول Logout import profile button تسجيل خروج Remove password ازالة كلمة المرور Change password تغيير كلمة المرور Server خادم الاتصال Hide my name from the public list إخفاء اسم الحساب الخاص بي من القائمة العامة Register التسجيل Your password كلمة المرور الخاصة بك Update تحديث Register on ToxMe التسجيل في ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. إسم لخدمة ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. اختياري. شئ عنك. أو عن ما تملك. Optional. Something about you. Or your cat. Tooltip for the Biography field. اختياري. شئ عنك. أو عن ما تملك. ToxMe service to register on. خدمة ToxMe لتسجيل الدخول. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. إذا لم يتم التغيير، فإن إدخالات ToxMe واضحة علنا. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. إزالة كلمة المرور والتشفير من ملفك الشخصي. Name input اسم الإدخال Name visible to contacts الاسم مرئي لجهات الاتصال Status message input ادخال رسالة الحالة Status message visible to contacts رسالة الحالة تظهر لجهات الاتصال Your Tox ID هوية Tox الخاصة بك (Tox ID) Save QR image as file حفظ صورة رمز QR كملف Copy QR image to clipboard نسخ صورة رمز QR الى الحافظة ToxMe username to be shown on ToxMe اسم مستخدم ToxMe المراد اظهاره في ToxMe Optional ToxMe biography to be shown on ToxMe السيرة الذاتية الخاصة ToxMe المراد اظهاره في ToxMe خياريا ToxMe service address عنوان خدمة ToxMe Visibility on the ToxMe service الظهور على خدمة ToxMe Password كلمة المرور Update ToxMe entry تحديث إدخال ToxMe Rename profile. اعادة تسمية الحساب. Delete profile. حذف الحساب. Export profile تصدير الملف الشخصي Remove password from profile ازالة كلمة المرور من الحساب الشخصي Change profile password تغيير كلمة المرور في الحساب الشخصي My name: الاسم: My status: حالتي: My username اسم المستخدم الخاص بي My biography سيرتي الذاتية My profile ملفي الشخصي LoadHistoryDialog Load History Dialog تحميل تاريخ المحادثة Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: اسم المستخدم: Password: كلمة المرور: Confirm: تأكيد كلمة المرور: Password strength: %p% قوة كلمة المرور: %p% Create Profile إنشاء حساب If the profile does not have a password, qTox can skip the login screen إذا كنت لا تملك كلمة المرور, qTox يستطيع تخطي شاشة الدخول Load automatically دخول تلقائي Load دخول New Profile حساب جديد Load Profile تحميل الملف الشخصي Couldn't create a new profile لا يمكن إنشاء حساب جديد The username must not be empty. يجب ألا يكون اسم المستخدم فارغاً. The password must be at least 6 characters long. كلمة المرور يجب أن تحتوي على أكثر من 6 أحرف. The passwords you've entered are different. Please make sure to enter same password twice. كلمات المرور التي قمت بإدخالها مختلفة. يرجى التأكد من إدخال نفس كلمة المرور مرتين. A profile with this name already exists. يوجد حساب آخر بهذا الاسم. Couldn't load profile لا يمكن تحميل الملف الشخصي There is no selected profile. You may want to create one. لا يوجد ملف شخصي محدد. قد تحتاج إلى إنشاء حساب جديد. Couldn't load this profile لا يمكن تحميل هذا الملف الشخصى This profile is already in use. هذا الحساب مستخدم حالياً. Wrong password. كلمة المرور خاطئة. Import استيراد Password protected profiles can't be automatically loaded. كلمة السر المحمية للحسابات الشخصية لا يمكن تحميلها تلقائيا. Username input field حقل إدخال اسم المستخدم Password input field, you can leave it empty (no password), or type at least 6 characters كلمة المرور في حقل الإدخال، يمكن تركها فارغة (بلا كلمة مرور)، أو أن لا تقل كلمة المرور عن 6 أحرف Password confirmation field حقل تأكيد كلمة المرور Create a new profile button إنشاء زر حساب شخصي جديد Profile list قائمة الحساب الشخصي List of profiles قائمة الحسابات الشخصية Password input إدخال كلمة المرور Load automatically checkbox تحميل خانة الاختيار بشكل تلقائي Import profile استيراد ملف الحساب الشخصي Load selected profile button تحميل زر الحساب الشخصي المحدد New profile creation page صفحة جديدة لإنشاء حساب شخصي Loading existing profile page تحميل صفحة الحساب الشخصي الحالية MainWindow Your name الإسم Your status الحالة ... ... Add friends إضافة أصدقاء Create a group chat إنشاء محادثة جماعية View completed file transfers عرض الملفات التي تم نقلها Change your settings تغيير الإعدادات Close إغلاق Open profile فتح الحساب الشخصي Open profile page when clicked فتح صفحة الحساب الشخصي عند النقر عليه Status message input ادخال رسالة الحالة Set your status message that will be shown to others تعيين رسالة الحالة التي سيتم عرضها للآخرين Status الحالة Set availability status تعيين حالة التوفر Contact search البحث عن جهة اتصال Contact search input for known friends ادخل اسم للبحث عنه في لائحة الاصدقاء المعروفين Sorting and visibility الفرز والرؤية Set friends sorting and visibility تعيين الفرز والرؤية للأصدقاء Open Add friends page فتح صفحة اضافة اصدقاء Groupchat دردشة جماعية Open groupchat management page فتح صفحة ادارة الدردشة الجماعية File transfers history تاريخ نقل الملف Open File transfers history فتح سجل نقل الملفات Settings الاعدادات Open Settings فتح إعدادات Nexus View OS X Menu bar عرض Window OS X Menu bar نافذة Minimize OS X Menu bar تصغير Bring All to Front OS X Menu bar في المقدمة Exit Fullscreen إلغاء ملئ الشاشة Enter Fullscreen ملئ الشاشة NotificationEdgeWidget Unread message(s) لا يوجد رسائل غير مقروءة رسالة غير مقروءة رسالتين غير مقروءة رسائل غير مقروءة عدة رسائل غير مقروءة مئات الرسائل غير مقروءة PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK مُفعل PrivacyForm Confirmation تأكيد Do you want to permanently delete all chat history? هل تريد حذف سجل المحادثات بشكل دائم ؟ Privacy خصوصية PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting اصدقائك سيرون اشعاراً عندما تكتب. Send typing notifications ارسال اشعار كتابة Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting سجل المحادثات لا يزال قيد التطوير. تغييرات الصيغة المعروفة ممكنة، هذا قد يؤدي الى فقدان البيانات. Keep chat history حفظ سجل المحادثة NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam هو جزء من حساب التوكس. إذا كنت تتلقى رسائل غير مرغوب فيها مع طلبات الأصدقاء، يجب عليك تغيير NoSpam الخاص بك. الأشخاص سيكون غير قادرين على إضافتك عبر الحساب القديم الخاص بك، ولكن سوف يتم الحفاظ على قائمة الأصدقاء الحالية. NoSpam منع الرسائل المزعجة "NoSpam" NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. منع الرسائل المزعجة "NoSpam" هي جزء من الهوية الخاصة بك التي يمكن تغييرها كما تشاء. إذا كنت تتلقى رسائل غير مرغوب فيها لدى طلبات الأصدقاء، تغيير منع الرسائل المزعجة "NoSpam". Generate random NoSpam إنشاء منع الرسائل المزعجة "NoSpam" بشكل عشوائي Privacy خصوصية BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox استخدم التوكس على كيوتوكس ProfileForm Current profile: الملف الحساب الشخصي الحالي: Remove ازالة Choose a profile picture اختيار صورة الملف الشخصي Error خطأ Unable to open this file. غير قابل لفتح هذا الملف. Unable to read this image. غير قابل لقراءة هذه الصورة. The supplied image is too large. Please use another image. الصورة المختارة كبيرة جداً. نرجو استخدام صورة اخرى. Rename "%1" renaming a profile اعادة تسمية "%1" Couldn't rename the profile to "%1" غير قادر على اعادة تسمية الملف الشخصية لــ "%1" Location not writable Title of permissions popup المسار غير قابل للكتابة You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup ليس لديك تصريح للكتابة في هذا المسار. اختر غيره, او الغِ نافذة الحفظ. Failed to copy file فشل في النسخ The file you chose could not be written to. الملف الذي اخترته غير قابل للكتابة عليه. Really delete profile? deletion confirmation title حقاً حذف الملف الشخصي؟ Are you sure you want to delete this profile? deletion confirmation text هل انت متأكد من حذف هذا الملف الشخصي؟ Save save qr image حفظ Save QrCode (*.png) save dialog filter (*.png) حفظ QrCode Nothing to remove لا شيء لإزالته Your profile does not have a password! ملفك الشخصي لا يملك كلمة مرور! Really delete password? deletion confirmation title حقاً حذف كلمة المرور؟ Please enter a new password. يرجى إدخال كلمة مرور جديدة. Files could not be deleted! deletion failed title لا يمكن حذف الملفات! Register (processing) التسجيل على قيد التجهيز Update (processing) التحديث على قيد التجهيز Done! انتهى! Account %1@%2 updated successfully حساب %1@%2 تم تحديثه بنجاح Successfully added %1@%2 to the database. Save your password تمت إضافة %1@%2 بنجاح إلى قاعدة البيانات. إحفظ كلمة المرور الخاصة بك Toxme error خطأ Toxme Register تسجيل Update تحديث Change password button text تغيير كلمة المرور Set profile password button text تعيين كلمة المرور للحساب الشخصي Current profile location: %1 مسار ملف الحساب الشخصي الحالي: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename فشلة اعادة التسمية Profile already exists الملف الشخصي موجود مسبقاً A profile named "%1" already exists. الملف الشخصي باسم "%1" موجود فعلا. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile تصدير الملف الشخصي Tox save file (*.tox) save dialog filter (*.tox) حفظ ملف الحساب الشخصي للتوكس The following files could not be deleted: deletion failed text part 1 لا يمكن حذف الملفات التالية: Please manually remove them. deletion failed text part 2 الرجاء إزالتها يدويا. Are you sure you want to delete your password? deletion confirmation text هل انت متأكد من حذف كلمة المرور الخاصة بك؟ Images (%1) filetype filter صور (%1) ProfileImporter Import profile import dialog title استيراد ملف الحساب الشخصي Tox save file (*.tox) import dialog filter (*.tox) حفظ ملف الحساب الشخصي للتوكس Ignoring non-Tox file popup title تخطي اي ملف غير ملفات التوكس Warning: You have chosen a file that is not a Tox save file; ignoring. popup text تحذير: لقد قمت باختيار ملف ليس بملف توكس محفوظ. تجاهل. Profile already exists import confirm title الملف الشخصي موجود مسبقاً A profile named "%1" already exists. Do you want to erase it? import confirm text إسم الملف الشخصي "%1" موجود مسبقاً . هل تود إزاته؟ File doesn't exist الملف غير موجود Profile doesn't exist الملف الشخصي غير موجود Profile imported تم إستيراد الملف الشخصي %1.tox was successfully imported %1.tox تمت إضافته QApplication LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout RTL Ok موافق Cancel إلغاء Yes نعم No لا QMessageBox Couldn't add friend لا يمكن اضافة صديق %1 is not a valid Toxme address. %1 ليس عنوان ToxMe صحيح. You can't add yourself as a friend! When trying to add your own Tox ID as friend لا تستطيع اضافة نفسك كصديق! QObject Tox URI to parse تحليل عنوان URI للتوكس Starts new instance and loads specified profile. بدء تشغيل حالة جديدة ثم يقوم بتحميل الحساب الشخصي المحدد. profile ملف شخصي Server doesn't support Toxme الخادم لا يدعم Toxme You're making too many requests. Wait an hour and try again لقد عملت الكثير من الطلبات. انتظر ساعة وأعد المحاولة This name is already in use الاسم مستخدم فعلا This Tox ID is already registered under another name هذا المعرف مسجل فعلا تحت اسم اخر Please don't use a space in your name يرجى عدم استخدام مسافات في اسمك Password incorrect كلمة المرور خاطئة You can't use this name لا تستطيع استخدام هذا الاسم Name not found لم يتم ايجاد الاسم Tox ID not sent لم يتم ارسال المعرف That user does not exist هذا المستخدم غير موجود %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 هنا! هل تود التواصل بالتوكس معي؟ Error خطأ qTox couldn't open your chat logs, they will be disabled. غير قادر على فتح سجل المحادثات , قد يكون معطلاً. None No camera device set لا شيئ Desktop Desktop as a camera input for screen sharing سطح المكتب Default افتراضي Blue ازرق Olive زيتوني Red احمر Violet بنفسجي Incoming call... مكالمة واردة... Problem with HTTPS connection مشكلة لدى اتصال بروتوكول HTTPS Internal ToxMe error خطأ ToxMe داخلي Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status متصل away contact status في الخارج busy contact status مشغول offline contact status غير متصل blocked contact status RemoveFriendDialog Remove friend ازالة صديق Also remove chat history ايضا حذف سجل المحادثة Remove ازالة Are you sure you want to remove %1 from your contacts list? هل انت متأكد من ازالة %1 من جهات الاتصال ؟ Remove all chat history with the friend if set ازالة تاريخ جمبيع المحادثات مع الصديق اذا وجد ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet اضغط واسحب للتحديد. اضغط %1 لـ اخفاء/إظهار نافذة qTox او %2 للالغاء. Space [Space] key on the keyboard مسافة Escape [Escape] key on the keyboard خروج Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected اضغط %1 لارسال لقطة الشاشة المحددة، اضغط %2 لـ اخفاء/إظهار نافذة qTox او %3 للالغاء. Enter [Enter] key on the keyboard أدخل SearchForm The text could not be found. Start SearchSettingsForm Form النوع Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password اختيار كلمة المرور Confirm: التأكيد: Password: كلمة المرور: Password strength: %p% قوة كلمة المرور: %p% The password is too short كلمة المرور قصيرة جدا The password doesn't match. كلمة المرور ليست متطابقة. Confirm password تأكيد كلمة المرور Confirm password input التأكد من إدخال كلمة المرور Password input إدخال كلمة المرور Password input field, minimum 6 characters long حقل كلمة المرور ، الحد الأدنى 6 أحرف Settings Circle #%1 قائمة #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI إضافة صديق Do you want to add %1 as a friend? هل تريد اضافة %1 كصديق؟ User ID: ID المستخدم: Friend request message: رسالة طلب الاضافة: Send Send a friend request ارسال Cancel Don't send a friend request إلغاء UserInterfaceForm None لا شيئ User Interface واجهة المستخدم UserInterfaceSettings Chat المحادثة Base font: نوع خط الكتابة: px بكسل Size: الحجم: New text styling preference may not load until qTox restarts. قد لا يتم إستعمال تصميم النص الجديد حتى يتم إعادة تشغيل qTox. Text Style format: نمط تنسيق الخط: Select text styling preference. تحديد نمط الخط المفضل. Plaintext نص عادي Show formatting characters إظهار أحرف التنسيق Don't show formatting characters عدم إظهار أحرف التنسيق New message رسالة جديدة Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting فتح نافذة qTox عند التواصل برسالة جديدة وأي إطار مفتوح حتى الان. Open window افتح النافذة Contact list قائمة الإتصال If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning إذا تم التحقيق، فالمحادثات الجماعية ستصبح فوق قائمة الأصدقاء, خلاف ذلك، ستكون المحادثات الجماعية تحت قائمة الأصدقاء المتصلين. Place groupchats at top of friend list ضع المحادثات الجماعية اعلى قائمه الاصدقاء Your contact list will be shown in compact mode. toolTip for compact layout setting سيتم عرض جهات الاتصال بوضع مضغوط. Compact contact list اظهار القائمه بالوضع المضغوط Multiple windows mode خاصيه النوافذ المتعدد Open each chat in an individual window افتح كل محادثه في نافذه مستقله Emoticons الرموز التعبيرية Use emoticons إستخدام الرموز التعبيرية Smiley Pack: Text on smiley pack label حزمة الإبتسامات: Emoticon size: حجم الرموز التعبيرية: px بكسل Theme المظهر Style: السِمة: Theme color: لون السِمة: Timestamp format: صيغة الوقت: Date format: صيغة التاريخ: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound تشغيل صوت Play sound while Busy تشغيل الصوت في حين الحالة مشغول Notify via desktop notifications Hide message sender and contents Widget Status الحالة toxcore failed to start, the application will terminate after you close this message. toxcore فشل في البدء , البرنامج سيغلق بعد الخروج من هذه الرسالة. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore فشل في البدء مع اعدادات الوكيل"البروكسي" الخاصة بك .لا يمكن بدء الكيوتوكس "qTox" ، يرجى تغيير الاعدادات واعادة المحاولة. Executable file popup title ملف تنفيدي You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text هل انت متأكد من فتح الملف ؟ Your name اسمك Couldn't request friendship لا يمكن طلب الصداقة Message failed to send فشل ارسال الرسالة Add new circle... اضافة قائمة جديدة... By Name بالاسم By Activity بالنشاط All الجميع Online متصل Offline غير متصل Friends الأصدقاء Groups المجموعات Search Contacts بحث عن جهة إتصال Online Button to set your status to 'Online' متصل Away Button to set your status to 'Away' في الخارج Busy Button to set your status to 'Busy' مشغول Logout Tray action menu to logout user تسجيل خروج Exit Tray action menu to exit tox خروج Filter... فلتر... File ملف Edit تعديل Contacts جهات الاتصال Change Status تغيير الحالة Edit Profile تعديل الملف الشخصي Log out تسجيل خروج Add Contact... اضافة جهة اتصال... Next Conversation المحادثة التالية Previous Conversation المحادثة السابقة Groupchat #%1 محادثة جماعية #%1 Create new group... إنشاء مجموعة جديدة... %n New Friend Request(s) %n طلبات الإضافة الجديدة %n طلبات الإضافة الجديدة %n New Group Invite(s) %n طلب إضافة الى مجموعة جديدة %n طلب إضافة الى مجموعة جديدة %n طلبات إضافة الى مجموعة جديدة %n طلبات إضافة الى مجموعة جديدة %n طلبات إضافة الى مجموعة جديدة %n طلبات إضافة الى مجموعة جديدة Show Tray action menu to show qTox window عرض Add friend title of the window إضافة صديق Group invites title of the window دعوات المجموعة File transfers title of the window نقل الملفات Settings title of the window الاعدادات My profile title of the window ملفي Failed to send file "%1" فشل ارسال الملف "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/be.ts000066400000000000000000003661331415623743500155160ustar00rootroot00000000000000 AVForm Audio/Video Аўдыя/Відэа Default resolution Агаданая распазнавальнасць Disabled Адключаны Select region Выбраць рэгіён Screen %1 Экран %1 Audio Settings Аўдыяналады Gain Узмацненне Playback device Прылада прайгравання Use slider to set volume of your speakers. Карыстайцеся паўзунком для ўсталявання гучнасці дынамікаў. Capture device Прылада захопу Volume Гучнасць Video Settings Відэаналады Video device Відэапрылада Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Усталяваць распазнавальнасць вашай камеры. Чым больш высокія значэнні зададзены, тым лепшую якасць відэа вашы сябры могуць атрымаць. Але звярніце ўвагу, што для лепшай якасці відэа патрабуецца лепшае падключэнне да Інтэрнэту. Часам вашага падключэння будзе недастаткова, каб справіцца з больш высокай якасцю відэа, што можа прывесці да праблем з відэавыклікам. Resolution Распазнавальнасць Rescan devices Перасканаваць прылады Test Sound Тэставы гук Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Уключае эксперыментальны аўдыярухавік з падтрымкай рэхакампенсацыі. Неабходна перагрузіць qTox. Enable experimental audio backend Уключыць эксперыментальны аўдыярухавік Audio quality Якасць аўдыя Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Якасць перадачы гуку. Знізце гэтую наладу, калі прапускная здольнасць не дастаткова высокая, або калі вы хочаце знізіць выкарыстанне інтэрнэту. High (64 kbps) Высокая (64 кбіт/с) Medium (32 kbps) Сярэдняя (32 кбіт/с) Low (16 kbps) Нізкая (16 кбіт/с) Very low (8 kbps) Вельмі нізкая (8 кбіт/с) Threshold Парог AboutForm About Аб праграме Original author: %1 Першы аўтар: %1 You are using qTox version %1. Вы выкарыстоўваеце qTox версіі %1. Commit hash: %1 Хэш фіксавання: %1 toxcore version: %1 Версія toxcore: %1 Qt version: %1 Версія Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Спіс усіх вядомых пытанняў можна знайсці ў нашым %1 на Гітхабе. Калі вы знайшлі хібу або ўразлівасць бяспекі ў qTox, калі ласка, паведаміце аб гэтым, згодна з указаннямі ў артыкуле %2 нашай вікі. Click here to report a bug. Націсніце сюды, каб паведаміць аб хібе. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Глядзіце поўны спіс %1 на Гітхабе bug-tracker Replaces `%1` in the `A list of all known…` трэкер хібаў Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Напісанне карысных справаздач аб хібах contributors Replaces `%1` in `See a full list of…` удзельнікаў AboutFriendForm Dialog Дыялог username імя карыстальніка status message паведамленне аб стане Used aliases: Ужытыя псеўданімы: HISTORY OF ALIASES ГІСТОРЫЯ ПСЕЎДАНІМАЎ Automatically accept files from contact if set Аўтаматычна прымаць файлы ад кантактаў, калі зададзеная Auto accept files Аўтаматычна прымаць файлы Default directory to save files: Агаданы каталог для захавання файлаў: Auto accept for this contact is disabled Аўтаматычны прыём адключаны для гэтага кантакта Auto accept call: Аўтаматычна прымаць выклік: Manual Уручную Audio Аўдыя Audio + Video Аўдыя + відэа Automatically accept group chat invitations from this contact if set. Аўтаматычна прымаць запрашэнні ў групавы чат ад гэтага кантакта. Auto accept group invites Аўтапрыём групавых запрашэнняў Remove history (operation can not be undone!) Выдаліць гісторыю (аперацыя не можа быць адмененая!) Notes Нататкі Input field for notes about the contact Поле ўвода нататак аб кантакце You can save comment about this contact here. Тут вы можаце захаваць каментар аб гэтым кантакце. History removed Гісторыя выдаленая Choose an auto accept directory popup title Абраць каталог для аўтаматычнага прыёму <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Гэта адкрыты ключ вашага сябра, выкарыстоўвайце яго, каб пацвердзіць яго асобу з дапамогай іншага канала. Вы не можаце адправіць гэта іншым людзям, каб яны маглі дадаць гэты кантакт.</p></body></html> Public key (not ToxID): Публічны ключ (не ToxID): Confirmation Пацвярджэнне Are you sure to remove %1 chat history? Вы ўпэўнены, што хочаце выдаліць гісторыю чату %1? Failed to remove chat history with %1! Не атрымалася выдаліць гісторыю чату %1! AboutSettings Version Версія License Ліцэнзія Authors Аўтары Known Issues Вядомая пытанні Open update download link Адкрыць спасылку для спампавання абнаўлення Update available Абнаўненне даступнае qTox is up to date ✓ qTox абноўлены ✓ AddFriendForm Add Friends Дадаць сяброў Invalid Tox ID format Некарэктны фармат Tox ID Send friend request Адправіць запыт сяброўства Add a friend Дадаць сябра Friend requests Запыты сяброўства Accept Прыняць Reject Адхіліць Couldn't add friend Не атрымалася дадаць сябра Tox ID, either 76 hexadecimal characters or name@example.com Tox ID — 76 шаснаццатковых знакаў ці name@example.com Type in Tox ID of your friend Надрукуйце Tox ID вашага сябра Friend request message Паведамленне запыту сяброўства Type message to send with the friend request or leave empty to send a default message Надрукуйце паведамленне для адпраўкі з запытам сяброўства або пакіньце пустым для абпраўкі агаданага паведамлення %1 Tox ID is invalid or does not exist Toxme error %1 няправільны Tox ID ці не існуе You can't add yourself as a friend! When trying to add your own Tox ID as friend Вы не можаце дадаць сябе як сябра! Open contact list Адкрыць спіс кантактаў Couldn't open file Не атрымалася адкрыць файл Couldn't open the contact file Error message when trying to open a contact list file to import Не атрымалася адкрыць файл кантактаў Invalid file Няправільны файл We couldn't find any contacts to import in this file! Мы не знайшлі ніякіх кантактаў для імпарта з гэтага файла! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description 76 шаснаццатковых знакаў ці name@example.com Message The message you send in friend requests Паведамленне Open Button to choose a file with a list of contacts to import Адкрыць Send friend requests Адправіць запыт сяброўства %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 тут! Можа адкажаце мне? Import a list of contacts, one Tox ID per line Імпартаваць спіс кантактаў, па аднаму Tox ID у радку Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Гатоўнасць імпартаваць %n кантакт. Націсніце адправіць для пацверджання Гатоўнасць імпартаваць %n кантакта. Націсніце адправіць для пацверджання Гатоўнасць імпартаваць %n кантактаў. Націсніце адправіць для пацверджання Import contacts Імпартаваць кантакты AdvancedForm Advanced Пашыраныя Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Пакуль вы %1 не ведаце, што вы робіце, калі ласка, %2 змяняйце тут нічога. Змены, зробленыя тут, могуць прывесці да праблем з qTox і нават да страты вашых дадзеных, напрыклад, гісторыі. really на самай справе not не IMPORTANT NOTE ВАЖНАЯ ЗАЎВАГА Reset settings Скінуць налады All settings will be reset to default. Are you sure? Усе налады будуць скінуты да агаданых. Вы ўпэўнены? Yes Так No Не Call active popup title Актыўны выклік You can't disconnect while a call is active! popup text Вы не можаце адключыцца пакуль выклік актыўны! Save File Захаваць файл Logs (*.log) журнал (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Захаваць налады ў працоўны каталог замест звычайнага канфігурацыйнага каталога Make Tox portable Зрабіць Tox партатыўным Reset to default settings Скінуць да агаданых налад Portable Партатыўнасць Connection Settings Налады злучэння Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Уключыць IPv6 (рэкамендуецца) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Адключэнне дазваляе, напрыклад, выкарыстоўваць Tox паверх Tor. Але гэта павялічвае нагрузку на сетку Tox. Адключайце толькі, калі гэта неабходна. Enable UDP (recommended) Text on checkbox to disable UDP Уключыць UDP (рэкамендуецца) Proxy type: Тып проксі: Address: Text on proxy addr label Адрас: Port: Text on proxy port label Порт: None Адсутнічае SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Перазлучыцца Debug Адладка Export Debug Log Экспартаваць журнал адладкі Copy Debug Log Капіяваць журнал адладкі Enable LAN discovery Уключыць выяўленне LAN ChatForm Send a file Адправіць файл qTox wasn't able to open %1 qTox не змог адкрыць %1 Unable to open Немагчыма адкрыць Bad idea Дрэнная ідэя %1 calling %1 выклікае Calling %1 Выклікаем %1 Failed to open temporary file Temporary file for screenshot Не атрымалася адкрыць часовы файл qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox не ўдалося захаваць скрыншот Call with %1 ended. %2 Выклік з %1 скончаны. %2 Call duration: Працягласць выкліку: %1 is typing %1 друкуе Copy Капіяваць You're trying to send a sequential file, which is not going to work! Вы спрабуеце адправіць адмысловы (паслядоўны) файл, але гэта не спрацуе! %1 is now %2 e.g. "Dubslow is now online" %1 цяпер %2 Call with %1 ended unexpectedly. %2 Выклік з %1 нечакана завяршыўся. %2 Filename contained illegal characters Назва файла змяшчае недапушчальныя сімвалы Illegal characters have been changed to _ so you can save the file on windows. Недапушчальныя сімвалы будуць зменены на _, так што вы можаце захаваць файл у windows. ChatFormHeader Can't start audio call Немагчыма пачаць аўдыявыклік Start audio call Пачаць аўдыявыклік End audio call Скончыць аўдыявыклік Cancel audio call Адмяніць аўдыявыклік Accept audio call Прыняць аўдыявыклік Can't start video call Немагчыма пачаць відэавыклік Start video call Пачаць відэавыклік End video call Скончыць відэавыклік Cancel video call Адмяніць відэавыклік Accept video call Прыняць відэавыклік Sound can be disabled only during a call Гук можа быць адключаны толькі падчас размовы Unmute call Уключыць гук Mute call Адключыць гук Microphone can be muted only during a call Мікрафон можа быць адключаны толькі падчас размовы Unmute microphone Уключыць мікрафон Mute microphone Адключыць мікрафон ChatLog Copy Капіяваць Select all Абраць усё pending чаканне ChatTextEdit Type your message here... Надрукуйце сваё паведамленне тут... CircleWidget Rename circle Menu for renaming a circle Перайменаваць круг Remove circle Menu for removing a circle Выдаліць круг Open all in new window Адкрыць усё ў новым акне Core /me offers friendship, "%1" /me прапаноўвае сяброўства, «%1» Invalid Tox ID Error while sending friendship request Некарэктны Tox ID You need to write a message with your request Error while sending friendship request Вам трэба напісаць паведамленне да вашага запыта Your message is too long! Error while sending friendship request Ваша паведамленне занадта вялікае! Friend is already added Error while sending friendship request Сябра ўжо даданы Groupchat %1 DesktopNotify New message Новае паведамленне Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen Форма 10Mb Ausgelassen 10 МБ 0kb/s Ausgelassen 0 КБ/сек ETA:10:10 Ausgelassen Разліковы час заканчэння: 10:10 Filename Ausgelassen Імя файла Waiting to send... file transfer widget Чаканне адпраўкі... Accept to receive this file file transfer widget Прыняць гэты файл Location not writable Title of permissions popup Размяшчэнне не даступна для запісу You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup У вас няма дазволу на запіс у гэта размяшчэнне. Выберыце іншае, або адмяніце дыялог захавання. Resuming... file transfer widget Узнаўленне перадачы… Cancel transfer Адмяніць перадачу Pause transfer Прыпыніць перадачу Paused file transfer widget Прыпынена Open file Адкрыць файл Open file directory Адкрыць каталог з файлам Resume transfer Аднавіць перадачу Accept transfer Прыняць перадачу Save a file Title of the file saving dialog Захаваць файл Remote Paused file transfer widget Аддалены бок прыпыніўся FilesForm Transferred Files "Headline" of the window Перададзеныя файлы Downloads Запампоўкі Uploads Адпампоўкі FriendListWidget Today Сёння Yesterday Учора Last 7 days Апошнія 7 дзён This month Апошні месяц Older than 6 Months Старэй 6 месяцаў Never Ніколі FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Запыт сяброўства Someone wants to make friends with you Нехта хоча пасябраваць з вамі User ID: Ідэнтыфікатар карыстальніка: Friend request message: Паведамленне запыту сяброўства: Accept Accept a friend request Прыняць Reject Reject a friend request Адхіліць FriendWidget Invite to group Menu to invite a friend to a groupchat Запрашэнне ў групу Move to circle... Menu to move a friend into a different circle Перамясціць у круг… To new circle У новы круг Remove from circle '%1' Выдаліць з круга «%1» Move to circle "%1" Перамясціць у круг «%1» Open chat in new window Адкрыць чат ў новым акне Remove chat from this window Выдаліць чат з гэтага вакна To new group У новую групу Invite to group '%1' Запрасіць у групу «%1» Set alias... Усталяваць псеўданім… Auto accept files from this friend context menu entry Аўтаматычна прымаць файлы ад гэтага сябра Remove friend Menu to remove the friend from our friendlist Выдаліць сябра Show details Паказаць дэталі Choose an auto accept directory popup title Выбраць каталог для аўтаматычна прынятых файлаў New message Новае паведамленне Online У сеціве Away Адышоў Busy Заняты Offline Ausgelassen Па-за сецівам GeneralForm General Агульныя Choose an auto accept directory popup title Выбраць каталог для аўтаматычна прынятых файлаў GeneralSettings General Settings Агульныя налады The translation may not load until qTox restarts. Пераклад не будзе загружаны да перазапуску qTox. Language: Мова: Show system tray icon Паказваць значок у сістэмным трэі Enable light tray icon. toolTip for light icon setting Уключыць светлы значок у трэі. Light icon Светлы значок qTox will start minimized in tray. toolTip for Start in tray setting qTox будзе запускацца згорнутым у трэй. Start in tray Запускаць у трэй After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Пасля націскання кнопкі закрыцця (X), qTox будзе згортвацца ў трэй замест закрыцця сябе. Close to tray Закрываць у трэй After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Пасля націскання кнопкі згортвання (_), qTox будзе згортвацца ў трэй замест згортвання ў сістэмную панэль задач. Minimize to tray Згортваць у трэй Autostart Аўтазапуск Set where files will be saved. Усталюйце, дзе будуць захоўвацца файлы. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Вы можаце ўсталяваць гэта для кожнага сябра асобна, пстрыкнуўшы правай кнопкай па ім. Autoaccept files Аўтаматычна прымаць файлы Set to 0 to disable Усталяваць 0 для адключэння Your status is changed to Away after set period of inactivity. Ваш стан зменіцца ў «Адышоў» пасля ўсталяванага перыяда неактыўнасці. Auto away after (0 to disable): Аўтаматычна адходзіць пасля (0 для адключэння): Show contacts' status changes Паказваць змены стану кантактаў Start qTox on operating system startup (current profile). Запуск qTox падчас запуску аперацыйнай сістэмы (дзейны профіль). Default directory to save files: Агаданы каталог для захавання файлаў: Check for updates Праверыць наяўнасць абнаўленняў Spell checking Праверка арфаграфіі Max autoaccept file size (0 to disable): Максімальны памер файла для аўтапрыёма (0 для адключэння): MB МБ GenericChatForm Send message Адправіць паведамленне Smileys Смайлікі Send file(s) Адправіць файл(ы) Send a screenshot Адправіць здымак экрана Save chat log Захаваць журнал чату Clear displayed messages Ачысціць адлюстраваныя паведамленні Cleared Ачышчаны Quote selected text Цытаваць выдзелены тэкст Copy link address Капіяваць адрас спасылкі Confirmation Пацвярджэнне You are sure that you want to clear all displayed messages? Вы ўпэўнены, што хочаце выдаліць усе адлюстраваныя паведамленні? Search in text Пошук у тэксце Go to current date Load chat history... Загрузіць гісторыю чату… Export to file Экпартаваць у файл GenericNetCamView Tox video Tox-відэа Show Messages Паказаць паведамленні Hide Messages Схаваць паведамленні Full Screen На ўвесь экран Toggle video preview Пераключыць папярэдні прагляд відэа Mute audio Адключыць аўдыё Mute microphone Адключыць мікрафон End video call Скончыць відэавыклік Exit full screen Выхад з паўнаэкраннага рэжыму GroupChatForm %1 has set the title to %2 %1 змяніў загаловак на «%2» %1 has joined the group %1 далучыўся да групы %1 is now known as %2 %1 цяпер вядомы як %2 %1 has left the group %1 пакінуў групу %n user(s) in chat Number of users in chat %n карыстальнік у чаце %n карыстальнікі ў чаце %n карыстальнікаў у чаце mute прыглушыць unmute уключаць GroupInviteForm Groups Групы Create new group Стварыць новую групу Group invites Запрашэнні ў групу GroupInviteWidget Invited by %1 on %2 at %3. Запрошыны %1 у %2 %3. Join Далучыцца Decline Адмовіцца GroupWidget Set title... Усталяваць назву… Open chat in new window Адкрыць чат у новым акне Remove chat from this window Выдаліць чат з гэтага вакна Quit group Menu to quit a groupchat Пакінуць групу %n user(s) in chat Number of users in chat %n карыстальнік у чаце %n карыстальнікі ў чаце %n карыстальнікаў у чаце New Message Новае паведамленне Online У сеціве IdentitySettings Public Information Публічная інфармацыя Tox ID Ідэнтыфікатар Tox This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Гэта звязка сімвалаў гаворыць іншым кліентам як звязацца з вамі. Падзяліцеся гэтым з сябрамі каб камунікаваць. Your Tox ID (click to copy) Ваш ідэнтыфікатар Tox (націсніце каб скапаваць) Profile Профіль Rename profile. tooltip for renaming profile button Перайменаваць профіль. Go back to the login screen tooltip for logout button Вярнуцца назад на экран уваходу Logout import profile button Выйсці Remove password Выдаліць пароль Change password Змяніць пароль This QR code contains your Tox ID. You may share this with your friends as well. Гэты QR-код змяшчае ваш ідэнтыфікатар Tox. Вы можаце падзяліцца гэтым са сваімі сябрамі. Save image Захаваць малюнак Copy image Капаваць малюнак Rename rename profile button Перайменаваць Delete profile. delete profile button tooltip Выдаліць профіль. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Дазваляе вам экспартаваць ваш Tox-профіль у файл. Профіль не змяшае вашу гісторыю. Export export profile button Экспартаваць Delete delete profile button Выдаліць Server Сервер Hide my name from the public list Схаваць маё імя з публічнага спісу Register Рэгістрацыя Your password Ваш пароль Update Абнавіць Register on ToxMe Рэгістрацыя ў ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Імя для сэрвіса ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Неабавязкова. Што-небудзь пра сябе альбо свойго ката. Optional. Something about you. Or your cat. Tooltip for the Biography field. Неабавязкова. Што-небудзь пра сябе альбо свойго ката. ToxMe service to register on. Сэрвіс ToxMe для рэгістрацыі. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Калі не ўсталявана, то запісы ToxMe будуць даступны публічна. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Выдаляе пароль і шыфраванне з вашага профілю. Name input Увод імя Name visible to contacts Імя бачна кантактам Status message input Увод паведамлення аб стане Status message visible to contacts Паведамленне аб стане бачна кантактам Your Tox ID Ваш Tox ID Save QR image as file Захаваць QR-малюнак як файл Copy QR image to clipboard Капіяваць QR-малюнак у буфер абмену ToxMe username to be shown on ToxMe ToxMe імя карыстальніка будзе паказана ў ToxMe Optional ToxMe biography to be shown on ToxMe Неабавязковая біяграфія будзе паказана ў ToxMe ToxMe service address Адрас сэрвісу ToxMe Visibility on the ToxMe service Бачнасць у сэрвісе ToxMe Password Пароль Update ToxMe entry Абнавіць запіс ToxMe Rename profile. Перайменаваць профіль. Delete profile. Выдаліць профіль. Export profile Экспартаваць профіль Remove password from profile Выдаліць пароль з профілю Change profile password Змяніць пароль профілю My name: Маё імя: My status: Мой стан: My username Маё імя карыстальніка My biography Мая біяграфія My profile Мой профіль LoadHistoryDialog Load History Dialog Загрузка гісторыі Load history from to (about 100 messages are loaded) Select Date Dialog Дыялог выбару даты Select a date Выбраць дату LoginScreen Username: Імя карыстальніка: Password: Пароль: Confirm: Пацвердзіць: Password strength: %p% Надзейнасць: %p% Create Profile Стварыць профіль If the profile does not have a password, qTox can skip the login screen Калі профіль не мае пароля, qTox можа прапусціць экран ўваходу Load automatically Загрузіць аўтаматычна Load Загрузіць Load Profile Загрузіць профіль New Profile Новы профіль Couldn't create a new profile Не атрымалася стварыць новы профіль The username must not be empty. Імя карыстальніка не можа быць пустое. The password must be at least 6 characters long. Пароль павінен быць не менш 6 сімвалаў. The passwords you've entered are different. Please make sure to enter same password twice. Паролі, якія вы ўвялі, розныя. Калі ласка, ўвядзіце адзін і той жа пароль двойчы. A profile with this name already exists. Профіль з такой назвай ужо існуе. Password protected profiles can't be automatically loaded. Профілі, абароненыя паролем, не могуць быць загружаны аўтаматычна. Couldn't load profile Не атрамалася загрузіць профіль There is no selected profile. You may want to create one. Там няма абранага профілю. Вы можаце стварыць адзін. Couldn't load this profile Не атрамалася загрузіць гэты профіль This profile is already in use. Гэты профіль ужо выкарыстоўваецца. Wrong password. Няправільны пароль. Import Імпарт Username input field Поле ўвода імя карыстальніка Password input field, you can leave it empty (no password), or type at least 6 characters Поле ўвода пароля. Вы можаце покінуць яго пустым або ўвядзіце не менш 6 сімвалаў Password confirmation field Поле падцвяржэння пароля Create a new profile button Кнопка стварэння новага профілю Profile list Спіс профіляў List of profiles Спіс профіляў Password input Увод пароля Load automatically checkbox Пераключальнік аўтаматычнай загрузкі Import profile Імпарт профілю Load selected profile button Кнопка загрузкі выбранага профілю New profile creation page Старонка стварэння новага профілю Loading existing profile page Старонка загрузкі наяўнага профілю MainWindow Your name Ваша імя Your status Ваш стан ... Ausgelassen Add friends Дадаць сяброў Create a group chat Стварыць групавы чат View completed file transfers Праглядзець завершаныя перадачы файлаў Change your settings Змяніць вашы налады Close Закрыць Open profile Адкрыць профіль Open profile page when clicked Адкрыць старонку профілю пры націску Status message input Увод паведамлення аб стане Set your status message that will be shown to others Задаць ваша паведамленне аб стане, якое будзе бачна іншым Status Стан Set availability status Задаць стан даступнасці Contact search Пошук кантакта Contact search input for known friends Поле пошуку сяброў Sorting and visibility Упарадкаванне і бачнасць Set friends sorting and visibility Задаць упарадкаванне і бачнасць сяброў Open Add friends page Адкрыць старонку дадання сяброў Groupchat Групавы чат Open groupchat management page Адкрыць старонку кіравання групавым чатам File transfers history Гісторыя перадач файлаў Open File transfers history Адкрыць гісторыю перадач файлаў Settings Налады Open Settings Адкрыць налады Nexus View OS X Menu bar Выгляд Window OS X Menu bar Акно Minimize OS X Menu bar Згарнуць Bring All to Front OS X Menu bar На пярэдні план Exit Fullscreen Выхад з поўнаэкраннага рэжыму Enter Fullscreen Увайсці ў поўнаэкранны рэжым NotificationEdgeWidget Unread message(s) Непрачытанае паведамленне Непрачытаныя паведамленні Непрачытаныя паведамленні PasswordEdit CAPS-LOCK ENABLED УКЛЮЧАНЫ CAPS-LOCK PrivacyForm Privacy Прыватнасць Confirmation Пацвярджэнне Do you want to permanently delete all chat history? Вы хочаце назаўсёды выдаліць ўсю гісторыю чату? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Вашы сябры будуць мець магчымасць бачыць, калі вы друкуеце. Send typing notifications Адпраўляць абвесткі аб друку Keep chat history Захоўваць гісторыю чату NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam — гэта частка вашага ідэнтыфікатара Tox. Калі вы пачалі атрымліваць спам з запытамі сяброўства, вы павінны змяніць значэнне NoSpam. Людзі не змогуць дадаць вас з вашым старым ідэнтыфікатарам, але вы захаваеце наяўных сяброў. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam — гэта частка вашага ідэнтыфікатара, якая можа быць зменена. Калі вы пачалі атрымліваць спам з запытамі сяброўства, змяніце значэнне NoSpam. Generate random NoSpam Генераваць выпадковае значэнне NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Захоўванне гісторыі чату знаходзіцца ў распрацоўцы. Магчымы змены фармату захоўвання, якія могуць прывесці да страты дадзеных. Privacy Прыватнасць BlackList Чорны спіс Filter group message by group member's public key. Put public key here, one per line. Фільтраваць групавыя паведамленні па публічным ключам чальцоў. Змясціце публічныя ключы тут па аднаму ў радок. Profile Failed to derive key from password, the profile won't use the new password. Не атрымалася стварыць ключ з пароля — профіль не будзе выкарыстоваць новы пароль. Couldn't change password on the database, it might be corrupted or use the old password. Не атрымалася змяніць пароль у базе дадзеных, яна можа быць пашкоджана або выкарыстоўваць стары пароль. Toxing on qTox Карыстаю qTox ProfileForm Choose a profile picture Выберыце выяву профілю Error Памылка Rename "%1" renaming a profile Перайменаваць «%1» Unable to open this file. Не атрымалася адкрыць гэты файл. Current profile: Дзейны профіль: Remove Выдаліць Unable to read this image. Не атрымалася прачытаць гэтую выяву. The supplied image is too large. Please use another image. Прадастаўленае выява занадта вялікая. Калі ласка, выкарыстайце іншую выяву. Couldn't rename the profile to "%1" Не ўдалося перайменаваць профіль у «%1» Location not writable Title of permissions popup Размяшчэнне недаступна для запісу You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup У вас няма дазволу на запіс у гэта размяшчэнне. Выберыце іншае, або адмяніце дыялог захавання. Failed to copy file Не атрымалася скапіяваць файл The file you chose could not be written to. Файл, які вы абралі, не можа быць запісаны. Really delete profile? deletion confirmation title Сапраўды выдаліць профіль? Nothing to remove Няма чаго выдаляць Your profile does not have a password! Ваш профіль не мае пароля! Really delete password? deletion confirmation title Сапраўды выдаліць пароль? Please enter a new password. Калі ласка, увядзіце новы пароль. Are you sure you want to delete this profile? deletion confirmation text Вы ўпэўнены, што хочаце выдаліць гэты профіль? Save save qr image Захаваць Save QrCode (*.png) save dialog filter Захаваць QR-код (*.png) Files could not be deleted! deletion failed title Файлы, якія не могуць быць выдаленыя! Register (processing) Ідзе рэгістрацыя Update (processing) Ідзе абнаўленне Done! Выканана! Account %1@%2 updated successfully Улікоўка %1@%2 паспяхова абноўлена Successfully added %1@%2 to the database. Save your password Улікоўка %1@%2 паспяхова дададзена. Захавайце свой пароль Toxme error Памылка Toxme Register Рэгістрацыя Update Абнавіць Change password button text Змяніць пароль Set profile password button text Усталяваць пароль профіля Current profile location: %1 Размяшчэнне дзейнага профілю: %1 Couldn't change password Не атрымалася змяніць пароль This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Гэтая паслядоўнасць сімвалаў кажа ішным Tox-кліентам як зкантактавацца з табой. Падзяліся гэтым з сябрамі каб мець зносіны. Гэты ID уключае NoSpam-код (сіні) і кантрольную суму (шэрую). Empty path is unavaliable Пусты шлях не даступны Failed to rename Немагчыма перайменаваць Profile already exists Профіль ужо існуе A profile named "%1" already exists. Профіль з назвай «%1» ужо існуе. Empty name Пустая назва Empty name is unavaliable Пустая назва не прымальная Empty path Пусты шлях Couldn't change password on the database, it might be corrupted or use the old password. Не атрымалася змяніць пароль базы дадзеных, яна можа быць пашкоджаная або выкарыстоўваць стары пароль. Export profile Экспартаваць профіль Tox save file (*.tox) save dialog filter Файл Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Наступныя файлы не могуць быць выдаленыя: Please manually remove them. deletion failed text part 2 Калі ласка, выдаліце іх уручную. Are you sure you want to delete your password? deletion confirmation text Вы ўпэўненыя, што хочаце выдаліць свой пароль? Images (%1) filetype filter Малюнкі (%1) ProfileImporter Import profile import dialog title Імпарт профілю Tox save file (*.tox) import dialog filter Файл Tox (*.tox) Ignoring non-Tox file popup title Ігнараванне не-Tox файлаў Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Увага: Вы выбралі файл, які не зʼяўляецца файлам Tox; ён ігнаруецца. Profile already exists import confirm title Профіль ужо існуе A profile named "%1" already exists. Do you want to erase it? import confirm text Профіль з назвай «%1» ужо існуе. Вы хочаце выдаліць яго? File doesn't exist Файл не існуе Profile doesn't exist Профіль не існуе Profile imported Профіль імпартаваны %1.tox was successfully imported %1.tox паспяхова імпартаваны QApplication Ok Добра Cancel Адмяніць Yes Так No Не LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Л→П QMessageBox Couldn't add friend Не атрымалася дадаць сябра %1 is not a valid Toxme address. %1 — некарэктны адрас Toxme. You can't add yourself as a friend! When trying to add your own Tox ID as friend Вы не можаце дадаць сябе як сябра! QObject Tox URI to parse Tox URI для разбору Starts new instance and loads specified profile. Запускае новы экзэмпляр і загружае указаны профіль. profile профіль Default Агаданы Blue Сіні Olive Аліўкавы Red Чырвоны Violet Фіялетавы Incoming call... Уваходзячы выклік… %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 тут! Можа адкажаце мне? None No camera device set Адсутнічае Desktop Desktop as a camera input for screen sharing Працоўны стол Server doesn't support Toxme Сервер не падтрымлівае Toxme You're making too many requests. Wait an hour and try again Вы зрабілі занадта шмат запытаў. Пачакайце адну гадзіну і паспрабуце зноў This name is already in use Гэта імя ўжо выкарыстоўваецца This Tox ID is already registered under another name Гэты ідэнтыфікатар Tox ужо зарэгістраваны пад іншым імем Please don't use a space in your name Калі ласка, не выкарыстоўвайце прабелы ў вашым імені Password incorrect Няправільны пароль You can't use this name Вы не можаце скарыстаць гэта імя Name not found Імя не знойдзена Tox ID not sent Ідэнтыфікатар Tox не адпраўлены That user does not exist Такі карыстальнік не існуе Error Памылка qTox couldn't open your chat logs, they will be disabled. qTox не можа адкрыць журнал чату, ён будзе адключаны. Problem with HTTPS connection Праблема з злучэннем HTTPS Internal ToxMe error Унутраная памылка ToxMe Reformatting text in progress.. Ідзе перафарматаванне тэксту.. Starts new instance and opens the login screen. Запускае новы экзэмпляр і адкрывае экран уваходу. Dark Цёмны Dark blue Цёмна-сіні Dark olive Цёмна-аліўкавы Dark red Цёмна-чырвоны Dark violet Цёмна-фіялетавы Failed to load profile automatically. online contact status у сеціве away contact status адышоў busy contact status заняты offline contact status па-за сеткай blocked contact status блакаваны RemoveFriendDialog Remove friend Выдаліць сябра Also remove chat history Таксама выдаліць гісторыю чату Remove Выдаліць Are you sure you want to remove %1 from your contacts list? Вы сапраўды хочаце выдаліць %1 з вашага спісу кантактаў? Remove all chat history with the friend if set Выдаліць усю гісторыю чату з сябрам, калі зададзена ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Клікніце і перацягніце для выбару рэгіёна. Націсніце %1 каб схаваць/паказаць акно qTox ці %2 каб адмяніць. Space [Space] key on the keyboard Прабел Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Націсніце %1 каб адправіць здымак абранай вобласці, %2 каб схаваць/паказаць акно qTox ці %3 каб адмяніць. Enter [Enter] key on the keyboard Увод SearchForm The text could not be found. Тэкст не можа быць знойдзены. Start Шукаць SearchSettingsForm Form Форма Start search: Пачаць шукаць: from the end з канца from the beginning з пачатку after date пасля даты before date да даты 00.00.0000 00.00.0000 Case sensitive Улічваючы рэгістр Whole words only Словы толькі цалкам Use regular expressions Ужываць рэгулярны выраз SetPasswordDialog Set your password Задайце ваш пароль Confirm: Паўтор: Password: Пароль: Password strength: %p% Надзейнасць: %p% The password is too short Пароль занадта кароткі The password doesn't match. Пароль не супадае. Confirm password Пацвердзіць пароль Confirm password input Поле пацвярджэнне пароля Password input Увод пароля Password input field, minimum 6 characters long Поле ўводу пароля, не менш, чым 6 знакаў Settings Circle #%1 Круг #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Дадаць сябра Do you want to add %1 as a friend? Вы хочаце дадаць %1 сябрам? User ID: Ідэнтыфікатар карыстальніка: Friend request message: Паведамленне запыту сяброўства: Send Send a friend request Адправіць Cancel Don't send a friend request Адмяніць UserInterfaceForm None Адсутнічае User Interface Інтэрфейс карыстальніка UserInterfaceSettings Chat Чат Base font: Базавы шрыфт: px px Size: Памер: New text styling preference may not load until qTox restarts. Новы стыль тэксту будзе загружаны пасля перазагрузкі qTox. Text Style format: Фармат тэксту: Select text styling preference. Выберыце стыль тэксту. Plaintext Просты тэкст Show formatting characters Паказаць сімвалы фарматавання Don't show formatting characters Не паказаць сімвалы фарматавання New message Новае паведамленне Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Адкрыць акно qTox, калі вы атрымліваеце новае паведамленне і вакно не адкрыта. Open window Адкрыць акно Contact list Спіс кантактаў If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Калі ўключаны, групавыя чаты будуць размешчаны наверсе спісу кантактаў, інакш яны будуць размешчаны ніжэй сяброў у сеціве. Place groupchats at top of friend list Размясціць групавыя чаты наверсе спісу сяброў Your contact list will be shown in compact mode. toolTip for compact layout setting Ваш спіс кантактаў будзе паказаны ў кампакным рэжыме. Compact contact list Кампактны спіс кантактаў Multiple windows mode Шматаконны рэжым Open each chat in an individual window Адкрываць кожны чат у асобным акне Emoticons Смайлікі Use emoticons Выкарыстоўваць смайлікі Smiley Pack: Text on smiley pack label Пакет смайлікаў: Emoticon size: Памер смайлікаў: px px Theme Тэма Style: Стыль: Theme color: Колер тэмы: Timestamp format: Фармат часу: Date format: Фармат даты: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Калі ўключанае, кожны кантакт без аватара будзе мець згенераваны аватар на аснове яго Tox ID замест агаданай выявы. Патрабуецца перазапуск. Use identicons instead of empty avatars Паказваць ідэнтыфікацыйныя выявы замест пустых аватараў Use colored nicknames in chats Выкарыстоўваць каляровыя мянушкі ў чатах Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Паказваць апавяшчэнне, калі вы атрымалі новае паведамленне і вакно неактыўнае. Notify Апавясціць Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Апавяшчаць толькі аб новых паведамленнях у групавых чатах, калі ўзгадваемся. Group chats only notify when mentioned Групавыя чаты апавяшчаюцца, толькі калі ўзгадваемся Play sound Прайграваць гук Play sound while Busy Прайграваць гук, калі заняты Notify via desktop notifications Апавяшчаць праз працоўны стол Hide message sender and contents Widget Online Button to set your status to 'Online' У сеціве Away Button to set your status to 'Away' Адышоў Busy Button to set your status to 'Busy' Заняты toxcore failed to start, the application will terminate after you close this message. Не атрымалася запусціць toxcore. Праграма будзе завершана пасля закрыцця гэтага паведамлення. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Не атрымалася запусціць toxcore з вашымі настаўленнямі проксі. qTox не можа працаваць. Калі ласка, змяніце вашы настаўленні і перазапусціце яго. File Файл Edit Profile Змяніць профіль Change Status Змяніць стан Log out Выйсці Edit Змяніць Logout Tray action menu to logout user Выйсці Exit Tray action menu to exit tox Выхад Filter... Фільтр… Contacts Кантакты Add Contact... Дадаць кантакт… Next Conversation Наступная гутарка Previous Conversation Папярэдняя гутарка Executable file popup title Выканальны файл You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Вы просіце, каб qTox адкрыў выканальны файл. Выканальныя файлы патэнцыйна могуць нанесці шкоду вашаму камп’ютару. Вы сапраўды хочаце адкрыць гэты файл? Couldn't request friendship Не атрымалася запытаць сяброўства Status Стан Your name Ваша імя Message failed to send Не атрымалася адправіць паведамленне Create new group... Ставарыць новую групу… Add new circle... Дадаць новы круг… %n New Friend Request(s) %n новы запыт сяброўства %n новыя запыты сяброўства %n новых запытаў сяброўства %n New Group Invite(s) %n новае запрашэнне ў групу %n новых запрашэння ў групу %n новых запрашэнняў у групу By Name Па імені By Activity Па аўктыўнасці All Усе Online У сеціве Offline Ausgelassen Па-за сецівам Friends Сябры Groups Групы Search Contacts Шукаць кантакты Groupchat #%1 Групавы чат № %1 Show Tray action menu to show qTox window Паказаць Add friend title of the window Дадаць сябра Group invites title of the window Запрашэнні ў групу File transfers title of the window Перадачы файлаў Settings title of the window Налады My profile title of the window Мой профіль Failed to send file "%1" Не атрымалася адправіць файл «%1» File sent sent you a friend request. invites you to join a group. qTox/translations/bg.ts000066400000000000000000003642441415623743500155210ustar00rootroot00000000000000 AVForm Audio/Video Аудио/Видео Default resolution Разделителна способност по подразбиране Disabled Изключен Select region Изберете област Screen %1 Екран %1 Audio Settings Настройки на звука Gain Сила на записващо устройство Playback device Устройство за възпроизвеждане Use slider to set volume of your speakers. Използвайте слайдера за да зададете силата на звука. Capture device Устройство за запис Volume Сила на възпроизвеждане Video Settings Видео настройки Video device Видео устройство Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Задайте резолюцията на вашата камера. Колкото по-големи са стойностите, толкова по-качествено видео вашите приятели може да получат. Имайте предвид, че с по-добро качество на видеото е нужна и по-бърза връзка. Понякога вашата връзка може да не е достатъчно добра за да се справи с по-качествено видео, което може да доведе до проблеми с видео разговорите. Resolution Разделителна способност Rescan devices Повторно сканиране на устройствата Test Sound Проверка на звука Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Включва експериментален звуков процесор с потискане на ехото, изисква рестарт на qTox. Enable experimental audio backend Включва експериментален звуков процесор Audio quality Качество на звука Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Качество на предавания звук. Намалете тази настройка ако нямате достатъчно широколентова връзка или ако искате да намалите използването на интернет. High (64 kbps) Високо (64 kбита/с) Medium (32 kbps) Средно (32 kбита/с) Low (16 kbps) Ниско (16 kбита/с) Very low (8 kbps) Много ниско (8 kбита/с) Threshold Прагова стойност AboutForm About За програмата Original author: %1 Оригинален автор: %1 You are using qTox version %1. Вие ползвате qTox версия %1. Commit hash: %1 Хеш на commit: %1 toxcore version: %1 toxcore версия: %1 Qt version: %1 Qt версия: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Лист с всички познати проблеми може да намерите в нашия %1 при Github. Ако намерите бъг или уязвимост на сигурността във qTox, моля докладвайте спрямо нашите насоки в нашата %2 уики статия. Click here to report a bug. Щракнете тук, за да докладвате за грешка. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Вижте пълен лист със %1 при Github bug-tracker Replaces `%1` in the `A list of all known…` бъг-тракер Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Писане на полезни доклади за грешки(бъгове) contributors Replaces `%1` in `See a full list of…` сътрудници AboutFriendForm Dialog Диалог username потребителско име status message съобщение за състояние Used aliases: Използвани псевдоними: HISTORY OF ALIASES ИСТОРИЯ НА ПСЕВДОНИМИ Automatically accept files from contact if set Автоматично приемане на файлове от контакт, ако е зададено Auto accept files Автоматично приемане на файлове Default directory to save files: Директория по подразбиране за запазване на файлове: Auto accept for this contact is disabled Автоматичното приемане е изключено за този контакт Auto accept call: Автоматично приемане на обаждане: Manual Ръчен Audio Аудио Audio + Video Аудио + Видео Automatically accept group chat invitations from this contact if set. Автоматично приемане на покани за групов чат от този контакт ако е включено. Auto accept group invites Автоматично приемане на поканите за групи Remove history (operation can not be undone!) Изтрий история (операцията не може да се отмени!) Notes Бележки Input field for notes about the contact Въведи бележка за абонат You can save comment about this contact here. Можете да запишете коментар за този контакт тук. History removed Историята е премахната Choose an auto accept directory popup title Изберете папка за автоматично приемане <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Потвърждение Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Версия License Лиценз Authors Автори Known Issues Познати проблеми Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Добави приятели Send friend request Не ми харесва, но други не са измислени, и го използва Facebook Изпратете покана за приятелство Couldn't add friend Неуспешно прибавяне на приятел Invalid Tox ID format Невалиден Tox ID формат Add a friend Добави приятел Friend requests Покани за приятелство Accept Приемам Reject Отхвърлям Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, или 76 шеснадесетични символи или име@пример,com Type in Tox ID of your friend Напиши Tox ID-то на твоя приятел Friend request message Молба за приятелство Type message to send with the friend request or leave empty to send a default message Напиши съобщение към поканата за приятелство или остави празно за да се изпрати със стандартно съобщение %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID е невалиден или не съществува You can't add yourself as a friend! When trying to add your own Tox ID as friend Не можете да добавите себе си като приятел! Open contact list Отваряне на списък с контакти Couldn't open file Не може да се отвори файла Couldn't open the contact file Error message when trying to open a contact list file to import Не може да се отвори файла с контакти Invalid file Невалиден файл We couldn't find any contacts to import in this file! Не са намерени контакти за зареждане в този файл! Tox ID Tox ID of the person you're sending a friend request to Tox ID на получателя either 76 hexadecimal characters or name@example.com Tox ID format description или 76 шестнадесетични символа или име@пример.com Message The message you send in friend requests Съобщение Open Button to choose a file with a list of contacts to import Отваряне Send friend requests Изпращане на заявка за приятелство %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 е тук! Да се Tox-ваме? Import a list of contacts, one Tox ID per line Зареждане на списък с контакти, по един Tox ID на ред Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) В готовност за прибавяне на %n контакт(и), натиснете изпращане за да потвърдите В готовност за прибавяне на %n контакти, натиснете изпращане за да потвърдите Import contacts Зареждане на контакти AdvancedForm Advanced За напреднали Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Освен ако %1 знаете какво правите, моля %2 сменяйте нищо тук. Промените направени тук може да доведат до проблеми с qTox и дори загуба на данни, например история. really наистина not не IMPORTANT NOTE ВАЖНО СЪОБЩЕНИЕ Reset settings Рестартирай настройките All settings will be reset to default. Are you sure? Всички настройки ще се рестартират по подразбиране. Сигурни ли сте? Yes Да No Не Call active popup title Обаждане в активност You can't disconnect while a call is active! popup text Вие не можете да излезете, докато повикването е активно! Save File Запази файла Logs (*.log) Логове (* .log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Запазване на настройките в работната директория вместо в директорията по подразбиране Make Tox portable Направете Tox портативен Reset to default settings Възстановяване на настройки по подразбиране Portable Преносим Connection Settings Настройки за връзка Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Активиране на IPv6 (Препоръчително) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Изключването на тази опция позволява, примерно, писане през Tor. Обаче по този начин се прибавя товар към Tox мрежата, затова я изключвайте само, ако е нужно. Enable UDP (recommended) Text on checkbox to disable UDP Активиране на UDP (Препоръчително) Proxy type: Тип прокси: Address: Text on proxy addr label Адрес: Port: Text on proxy port label Порт: None Нищо SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Свържете се отново Debug Дебъг Export Debug Log Изведи Дебъг Регистър Copy Debug Log Копирай Дебъг Регистър Enable LAN discovery ChatForm Send a file Изпрати файл qTox wasn't able to open %1 qTox неуспя да отвори %1 %1 calling %1 звъни Call with %1 ended. %2 Разговор с %1 приключи. %2 Call duration: Продължителност на разговор: Unable to open Неупешно отваряне Bad idea Лоша идея Calling %1 Звънене на %1 Failed to open temporary file Temporary file for screenshot Неуспешно отваряне на временен файл qTox wasn't able to save the screenshot qTox неуспя да запази скрийншота %1 is typing %1 в момента пише Copy Копирай You're trying to send a sequential file, which is not going to work! Вие се опитвате да изпратите последователен файл, който няма да работи! %1 is now %2 e.g. "Dubslow is now online" %1 сега е %2 Call with %1 ended unexpectedly. %2 Връзката с %1 завърши неочаквано. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Звуковото обаждане не може да започне Start audio call Започни звуково обаждане End audio call Приключи звуково обаждане Cancel audio call Отложи звуково обаждане Accept audio call Приеми звуково обаждане Can't start video call Видео обаждането не може да започне Start video call Започни видео обаждане End video call Приключи видео обаждане Cancel video call Отложи видео обаждане Accept video call Приеми видео обаждане Sound can be disabled only during a call Звукът може да бъде изключен само по време на обаждане Unmute call Пусни звук в обаждането Mute call Заглуши обаждането Microphone can be muted only during a call Микрофонът може да бъде заглушен само по време на обаждане Unmute microphone Включи микрофон Mute microphone Заглуши микрофон ChatLog Copy Копирай Select all Избери всички pending висящ ChatTextEdit Type your message here... Въведете съобщението си тук... CircleWidget Rename circle Menu for renaming a circle Преименувай кръг Remove circle Menu for removing a circle Премахни кръг Open all in new window Отвори всичко в нов прозорец Core /me offers friendship, "%1" /me преглага приятелство, "%1" Invalid Tox ID Error while sending friendship request Невалиден Tox ID You need to write a message with your request Error while sending friendship request Трябва да напишете съобщение заедно с вашата молба Your message is too long! Error while sending friendship request Вашето съобщение е твърде дълго! Friend is already added Error while sending friendship request Този приятел вече е прибавен Groupchat %1 DesktopNotify New message Ново съобщение Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Формуляр 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 ETA:10:10 Filename Име на файл Waiting to send... file transfer widget Чакане за изпращане... Accept to receive this file file transfer widget Приеми за да получиш този файл Location not writable Title of permissions popup Неможе да се записва на това място You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Вие нямате позволение да пишете в тази директория. Изберете друга, или отменете диалога. Save a file Title of the file saving dialog Запазете файл Paused file transfer widget Паузирано Resuming... file transfer widget Възобновяване... Open file Отвори файл Open file directory Отвори файл директория Pause transfer Паузирай трансфер Cancel transfer Отмени трансфер Resume transfer Възобнови трансфер Accept transfer Приеми трансфер Remote Paused file transfer widget FilesForm Downloads Сваляния Uploads Изпратени Transferred Files "Headline" of the window Пренесени файлове FriendListWidget Today Днес Yesterday Вчера Last 7 days Последните 7 дни This month Този месец Older than 6 Months По-стари от 6 месеца Never Никога FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Заглавие на прозореца, за Приемамане/Отказ на покана от приятел. Заявка за да добавите като приятел Someone wants to make friends with you "Приятелство" Някой иска да се сприятели с вас User ID: Потребителски ИД: Friend request message: Съобщение за искане на приятелство: Accept Accept a friend request Приемам Reject Reject a friend request Отхвърлям FriendWidget Set alias... Задай псевдоним... Auto accept files from this friend context menu entry Автоматично получаване на файлове от приятел Invite to group Menu to invite a friend to a groupchat Покани в групата Remove friend Menu to remove the friend from our friendlist Премахни приятел Choose an auto accept directory popup title Изберете папка за автоматично приемане Open chat in new window Отвори чат в нов прозорец Remove chat from this window Премахни чат от този прозорец To new group Нова група Invite to group '%1' Покани в групата '%1' Move to circle... Menu to move a friend into a different circle Премести в кръг... To new circle Нов кръг Remove from circle '%1' Премахни от кръг '%1' Move to circle "%1" Премести в кръгът "%1" Show details Покажи детайли New message Ново съобщение Online На линия Away Отсъстващ Busy Зает Offline Извън линия GeneralForm General Общи Choose an auto accept directory popup title Изберете папка за автоматично приемане GeneralSettings General Settings Общи настройки The translation may not load until qTox restarts. Преводът няма да се промени, докато не рестартирате qTox. Close to tray Затваряне в областта за уведомяване Minimize to tray Минимизиране в областта за уведомяване Set to 0 to disable Посочете 0, за да забраните Start in tray Стартиране в областта за уведомяване Show contacts' status changes Показване на промените на статуса на контактите ви Auto away after (0 to disable): Направи ме автоматично Отсъстващ след (0 за деактивиране): Language: Език: Show system tray icon Покажи иконата в областта за уведомяване Enable light tray icon. toolTip for light icon setting Активиране на светла икона в областта за уведомяване. Light icon Светла икона qTox will start minimized in tray. toolTip for Start in tray setting qTox ще се включи минимизиран в областта за уведомяване. After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting След натискане на бутона за затваряне (X), qTox ще се минимизира в областта за уведомяване, вместо да се затвори. After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting След натискане на бутона за минимизиране (_), qTox ще се минимизира в областта за уведомяване, вместо в лентата за задачи. Autostart Автоматичен старт Set where files will be saved. Определи място за запазване на файлове. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Можете да зададете това на отделни приятели, като натиснете с дясно копче отгоре им. Autoaccept files Автоматично приемане на файлове Your status is changed to Away after set period of inactivity. Вашето състояние се променя на Отсъстващ след определен период на бездействие. Start qTox on operating system startup (current profile). Стартирай qTox при стартирване на операционната система (сегашен профил). Default directory to save files: Директория по подразбиране за запазване на файлове: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Изпращане на съобщение Smileys Емотикони Send file(s) Изпрати файл/ове Save chat log Запазване на логовете от чата Clear displayed messages Изчистване на съобщенията Cleared Почистено Send a screenshot Изпрати скрийншот Quote selected text Цитирай маркиран текст Copy link address Копирай линковия адрес Confirmation Потвърждение You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Зареди чат история... Export to file Изнасяне към файл GenericNetCamView Tox video Tox видео Show Messages Покажи съобщенията Hide Messages Скрий съобщенията Full Screen Toggle video preview Mute audio Mute microphone Заглуши микрофон End video call Приключи видео обаждане Exit full screen GroupChatForm %1 has set the title to %2 %1 промени заглавието на %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Групи Create new group Създай нова група Group invites Покани за групи GroupInviteWidget Invited by %1 on %2 at %3. Поканен от %1на %2 в %3. Join Присъединяване Decline Откажи GroupWidget Quit group Menu to quit a groupchat Излез от групата Set title... Задай надпис... Open chat in new window Отвори чат в нов прозорец Remove chat from this window Премахни чат от този прозорец %n user(s) in chat Number of users in chat New Message Online На линия IdentitySettings Public Information Публична информация Tox ID Tox ИД This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Тези символи казват на другите Tox клиенти как да се свържат с вас. Споделете ги с вашите приятели за да можете да комуникирате. Your Tox ID (click to copy) Вашия Tox ID (Кликнете върху него, за да го копирате) Rename rename profile button Преименувай Export export profile button Изнеси Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Позволява ви да изнесете вашия Tox профил във файл. Пофилът не съдържа вашата история. Delete delete profile button Изтрий This QR code contains your Tox ID. You may share this with your friends as well. Този QR код съдържа вашия Tox ID. Можете да го споделите с вашите приятели. Save image Запази изображение Copy image Копирай изображение Server Сървър Hide my name from the public list Скрий ми името от публичния лист Register Регистрирай се Your password Вашата парола Update Актуализация Profile Профил Rename profile. tooltip for renaming profile button Преименувай профил. Delete profile. delete profile button tooltip Изтрий профил. Go back to the login screen tooltip for logout button Върни се обратно към екрана за вход Logout import profile button Излез от профила си Remove password Премахни паролата Change password Промени парола Register on ToxMe Регистрирайте се на ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Име за услугата на ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. По желание. Нещо за вас. Или вашата котка. Optional. Something about you. Or your cat. Tooltip for the Biography field. По желание. Нещо за вас. Или вашата котка. ToxMe service to register on. ToxMe услуга на която да се регистрирате. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ако не зададено, ToxMe записите са публично видими. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Премахнете вашата парола и криптиране от вашия профил. Name input Въвеждане на име Name visible to contacts Име видимо за контакти Status message input Въвеждане на съобщение за състояние Status message visible to contacts Съобщението за състояние е видимо за контакти Your Tox ID Вашият Tox ID Save QR image as file Запишете QR изображение като файл Copy QR image to clipboard Копирайте QR изображението в клипборда ToxMe username to be shown on ToxMe ToxMe потребителско име да се показва на ToxMe Optional ToxMe biography to be shown on ToxMe Незадължителен ToxMe биография да бъде показан на ToxMe ToxMe service address Адрес на ToxMe услуга Visibility on the ToxMe service Видимост на услугата на ToxMe Password Парола Update ToxMe entry Актуализиране на ToxMe запис Rename profile. Преименувай профил. Delete profile. Изтрий профил. Export profile Изнасяне на профил Remove password from profile Премахване на парола от профил Change profile password Промяна на паролата на профила My name: Моето име: My status: Моят статус: My username Моето потребителско име My biography Моята биография My profile Моят профил LoadHistoryDialog Load History Dialog Зареди история Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Потребителско име: Password: Парола: Confirm: Потвърди: Password strength: %p% Сила на парола: %p% Create Profile Създай профил If the profile does not have a password, qTox can skip the login screen Ако профилът няма парола, qTox може да пропусне екрана за вход Load automatically Зареди автоматично Import Внеси Load Зареди New Profile Нов профил Load Profile Зареди профил Couldn't create a new profile Неуспешно създаване на нов профил The username must not be empty. Потребителското име не трябва да е празно. The password must be at least 6 characters long. Паролата трябва да е дълга поне 6 символа. The passwords you've entered are different. Please make sure to enter same password twice. Паролите които сте въвели, са различни. Моля, уверете се паролите да бъдат еднакви. A profile with this name already exists. Профил с това име вече съществува. Password protected profiles can't be automatically loaded. Профилите защитени с парола, немогат да бъдат заредени автоматично. Couldn't load profile Неуспешно зареждане на профил There is no selected profile. You may want to create one. Няма избран профил. Може да създадете нов. Couldn't load this profile Неуспешно зареждане на профил This profile is already in use. Този профил вече се използва. Wrong password. Грешна парола. Username input field Поле за въвеждане на потребителско име Password input field, you can leave it empty (no password), or type at least 6 characters Поле за въвеждане на парола, може да го оставите празно (без парола), или напишете поне 6 символа Password confirmation field Поле за потвърждение на паролата Create a new profile button Създаване на нов профилен бутон Profile list Списък на профил List of profiles Списък на профили Password input Въвеждане на парола Load automatically checkbox Зареди автоматично квадратче за отметка Import profile Внасяне на профил Load selected profile button Зареждане на избрания профилен бутон New profile creation page Страницата за създаване на нов профил Loading existing profile page Зареждане на съществуваща профил страница MainWindow Your name Вашеto имe Your status Вашия статус Add friends Добави приятел Create a group chat Създай групов чат View completed file transfers Преглед на завършените файлови трансфери Change your settings Промяна на настройките Close Затвори ... ... Open profile Отваряне на профил Open profile page when clicked Отваряне на профил страницата при кликване Status message input Въвеждане на съобщение за състоянието Set your status message that will be shown to others Задаване на вашето съобщение за състояние, което ще бъде показано на другите Status Статус Set availability status Задаване на състояние за достъп Contact search Търсене на контакт Contact search input for known friends Входно поле за търсене на известни приятели Sorting and visibility Сортиране и видимост Set friends sorting and visibility Задайте сортиране и видимост на приятели Open Add friends page Отвори страницата за добавяне на приятели Groupchat Групов чат Open groupchat management page Отвори страницата за управление на групов чат File transfers history История на файлови трансфери Open File transfers history Отвори историята на файловите трансфери Settings Настройки Open Settings Отворете настройките Nexus View OS X Menu bar Изглед Window OS X Menu bar Прозорец Minimize OS X Menu bar Минимизирай Bring All to Front OS X Menu bar Изкарай всичко отпред Exit Fullscreen Изход от Цял екран Enter Fullscreen Влизане в Цял екран NotificationEdgeWidget Unread message(s) Непрочетено/и съобщение/я Непрочетени съобщения PasswordEdit CAPS-LOCK ENABLED КЛАВИШ ЗА ГЛАВНИ БУКВИ ВКЛЮЧЕН PrivacyForm Privacy Поверителност Confirmation Потвърждение Do you want to permanently delete all chat history? Искате ли да изтриете всичката чат история завинаги? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Вашите приятели ще могат да виждат, когато пишете. Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Пазенето на чат история, все още е в разработка. Промени във формата за запазване са възможни, което може да доведе до загуба на данни. Send typing notifications Изпрати уведомления за писане Keep chat history Пази чат история NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam е част от вашия Tox ID. Ако ви пращат спам с молби за приятелство, променете вашия NoSpam. Хората няма да могат да ви добавят със стария ви ID, но ще си запазите сегашните приятели. NoSpam ПъленТашак NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam е част от вашия Tox ID. Ако ви пращат спам с молби за приятелство, променете вашия NoSpam. Generate random NoSpam Генерирайте произволен NoSpam Privacy Поверителност BlackList Черен списък Filter group message by group member's public key. Put public key here, one per line. Филтриране на групови съобщения по публичен ключ на членовете. Поставете побличните ключове тук, по един на ред. Profile Failed to derive key from password, the profile won't use the new password. Неуспешно произвеждане на ключ от парола, профилът няма за използва новата парола. Couldn't change password on the database, it might be corrupted or use the old password. Неуспешна промяна на парола. Възможно е да е развалена или да използва стара парола. Toxing on qTox Tox-ва в qTox ProfileForm Current profile: Сегашен профил: Remove Премахни Choose a profile picture Изберете профилна снимка Error Грешка Unable to open this file. Неуспешно отваряне на този файл. Unable to read this image. Неуспешно разчитане на това изображение. The supplied image is too large. Please use another image. Подаденото изображение е твърде голямо. Моля ползвайте друго изображение. Rename "%1" renaming a profile Преименуване на "%1" Couldn't rename the profile to "%1" Неуспешно преименуване на профила към "%1" Location not writable Title of permissions popup Неможе да се записва на това място You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Вие нямате права да пишете на тази директория. Изберете друга, или отложете диалога. Failed to copy file Неуспешно копиране на файл The file you chose could not be written to. Във файлът който сте избрал, не може да се пише. Really delete profile? deletion confirmation title Наистина ли да се изтрие профилът? Are you sure you want to delete this profile? deletion confirmation text Сигурни ли сте че искате да изтриете този профил? Files could not be deleted! deletion failed title Файловете не можаха да се изтрият! Save save qr image Запази Save QrCode (*.png) save dialog filter Запази QrCode (*.png) Nothing to remove Няма нищо за премахване Your profile does not have a password! Вашия профил няма парола! Really delete password? deletion confirmation title Наистина ли да се изтрие паролата? Please enter a new password. Моля въведете нова парола. Register (processing) Регистриране (преработване) Update (processing) Актуализация (обработване) Done! Готово! Account %1@%2 updated successfully Акаунт %1@%2 актуализиран успешно Successfully added %1@%2 to the database. Save your password Успешно прибавен %1@%2 към базата с данни. Запазете си паролата Toxme error Toxme грешка Register Регистрирай Update Актуализация Change password button text Промени паролата Set profile password button text Задай парола на профила Current profile location: %1 Местоположение на профила: %1 Couldn't change password Неуспешна смяна на парола This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Тези символи казват на другия Tox клиент как да се свърже с вас. Споделете ги с вашите приятели за да комуникирате. Този ID включва NoSpam код (в синьо), и checksum (в сиво). Empty path is unavaliable Празен път не е достъпен Failed to rename Неуспешно преименуване Profile already exists Този профил вече съществува A profile named "%1" already exists. Профил с име "%1" вече съществува. Empty name Празно име Empty name is unavaliable Празно име не е достъпно Empty path Празен път Couldn't change password on the database, it might be corrupted or use the old password. Неуспешна промяна на парола. Възможно е да е развалена или да използва стара парола. Export profile Изнасяне на профил Tox save file (*.tox) save dialog filter Tox запиши файл (*.tox) The following files could not be deleted: deletion failed text part 1 Неуспешно изтриване на фаловете: Please manually remove them. deletion failed text part 2 Моля премахнете ги ръчно. Are you sure you want to delete your password? deletion confirmation text Сигурни ли сте, че искате да си изтриете паролата? Images (%1) filetype filter Изображения (%1) ProfileImporter Import profile import dialog title Внеси профил Tox save file (*.tox) import dialog filter Tox запази файл (*.tox) Ignoring non-Tox file popup title Не е избран Tox файл Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Внимание: Избрали сте файл, който не е Tox save file; игнориране. Profile already exists import confirm title Този профил вече съществува A profile named "%1" already exists. Do you want to erase it? import confirm text Профил с име "%1" вече съществува. Искате ли да го изтриете? File doesn't exist Файлът не съществува Profile doesn't exist Профилът не съществува Profile imported Профилът е внесен %1.tox was successfully imported %1.tox бе внесен успешно QApplication Ok OK Cancel Отложи Yes Да No Не LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Отляво надясно QMessageBox Couldn't add friend Неуспешно добавяне на приятел %1 is not a valid Toxme address. %1 не е валиден Toxme адрес. You can't add yourself as a friend! When trying to add your own Tox ID as friend Не можете да добавите себе си като приятел! QObject Tox URI to parse Tox URI за подаване Starts new instance and loads specified profile. Стартира нова инстанция и зарежда избран профил. profile профил Default По подразбиране Blue Син Olive Маслинов Red Червен Violet Виолетов Incoming call... Входящо обаждане... Server doesn't support Toxme Сървърът не поддържа Toxme You're making too many requests. Wait an hour and try again Пращате твърде много молби. Моля изчакайте час и опитайте отново This name is already in use Това име вече се използва This Tox ID is already registered under another name Този Tox ID вече е регистриран под друго име Please don't use a space in your name Моля не ползвайте разтояние във вашето име Password incorrect Паролата е грешна You can't use this name Не можете да ползвате това име Name not found Името не е открито Tox ID not sent Tox ID не е пратен That user does not exist Потребителят не съществува %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 е тук! Да се Tox-ваме? Error Грешка qTox couldn't open your chat logs, they will be disabled. qTox неуспя да отвори чат логове, те ще бъдат изключени. None No camera device set Нищо Desktop Desktop as a camera input for screen sharing Работен плот Problem with HTTPS connection Проблем с HTTPS връзката Internal ToxMe error Вътрешна ToxMe грешка Reformatting text in progress.. Изпълнява се преформатиране на текст.. Starts new instance and opens the login screen. Стартира ново копие и отваря екрана за вписване. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status На линия away contact status отсъстващ busy contact status зает offline contact status Извън линия blocked contact status RemoveFriendDialog Remove friend Премахване на приятеля Also remove chat history Също да се премахне хронологията на разговора Remove Премахване Are you sure you want to remove %1 from your contacts list? Сигурни ли сте, че искате да премахнете %1 от вашия лист с контакти? Remove all chat history with the friend if set Премахнете цялата история на чатовете с приятел, ако е зададено ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Натисни и издърпай за да избереш регион. Натисни %1 за да скриеш/покажеш qTox прозорец, или %2 за да отложиш. Space [Space] key on the keyboard Интервал Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Натисни %1 за да изпратиш снимка от селекцията, %2 за да скриеш/покажеш qTox прозорец, или %3 за да отложиш. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form Формуляр Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Задайте вашата парола Confirm: Потвърждаване: Password: Парола: Password strength: %p% Сила на парола: %p% The password is too short Паролата е твърде кратка The password doesn't match. Паролата не съответства. Confirm password Потвърждение на паролата Confirm password input Въвеждане на потвърждение на парола Password input Въвеждане на парола Password input field, minimum 6 characters long Поле за въвеждане на парола, минимум 6 символа Settings Circle #%1 Кръг #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Добавяне на приятел Do you want to add %1 as a friend? Искате ли да прибавите %1 като приятел? User ID: Потребителски ID: Friend request message: Съобщение за искане на приятелство: Send Send a friend request Изпращане Cancel Don't send a friend request Отказ UserInterfaceForm None Нищо User Interface Потребителски интерфейс UserInterfaceSettings Chat Разговори Base font: Основен шрифт: px пиксели Size: Размер: New text styling preference may not load until qTox restarts. Новите предпочитания за стил на текста може да не се заредят, докато не рестартирате qTox. Text Style format: Формат на стил на текста: Select text styling preference. Изберете предпочитания за стил на текста. Plaintext Обикновен текст Show formatting characters Покажи форматиращи символи Don't show formatting characters Не показвай форматиращи символи New message Ново съобщение Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Отвори прозореца на qTox, когато получиш ново съобщение и няма отворен прозорец все още. Open window Отвори прозорец Contact list Списък с контакти If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Ако е маркирано, групови чатове ще се поставят най-отгоре на листа с приятели, а ако не е маркирано ще се поставят под приятелите, които са онлайн. Place groupchats at top of friend list Постави групови чатове на върха на листа с приятели Your contact list will be shown in compact mode. toolTip for compact layout setting Вашият лист с контакти ще бъде показан в компактен режим. Compact contact list Компактен списък с контакти Multiple windows mode Режим с много прозорци Open each chat in an individual window Отвори всеки чат в индивидуален прозорец Emoticons Емотикони Use emoticons Използване на емотикони Smiley Pack: Text on smiley pack label Пакет емотикони: Emoticon size: Размер на емотикони: px пиксели Theme Тема Style: Стил: Theme color: Цвят на тема: Timestamp format: Формат на дата и час: Date format: Формат на дата: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Ако е включено, всеки контакт без зададен аватар ще има генериран аватар, базиран на техния Tox ID, вместо снимката по подразбиране. Нужно е рестартиране за да се приложи. Use identicons instead of empty avatars Използване на идентикони вместо празни аватари Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Възпроизвеждане на звук Play sound while Busy Възпроизвеждане на звук докато зададен Зает Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' На линия Away Button to set your status to 'Away' Вероятно, это не столь долгое путешествие Отсъстващ Busy Button to set your status to 'Busy' Зает toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Toxcore не успя да стартира с вашите прокси настройки. Tox не може да зареди; моля, променете вашите настройки и рестартирайте Tox. Couldn't request friendship Неуспешно поискване на приятелство Message failed to send Съобщението не успя да се изпрати Status Статус toxcore failed to start, the application will terminate after you close this message. toxcore неуспя да се стартира, приложението ще се прекрати, след като затворите това съобщение. Executable file popup title Изпълним файл You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Вие помолихте qTox да отвори изпълним файл. Изпълнимите файлове могат да причинят вреда на вашия компютър. Сигурни ли сте че искате да отворите този файл? Your name Вашето име Groupchat #%1 Групов разговор #%1 Create new group... Създаване на нова група... Add new circle... Добавяне на нов кръг... %n New Friend Request(s) %n нова молба за приятелство %n нови молби за приятелство %n New Group Invite(s) %n нова покана за група %n нови покани за група By Name По име By Activity По дейност All Всичко Online На линия Offline Извън линия Friends Приятели Groups Групи Search Contacts Търсене на контакти Logout Tray action menu to logout user Отписване Exit Tray action menu to exit tox Изход Filter... Филтър... File Файл Edit Редактиране Contacts Контакти Change Status Промени състояние Edit Profile Редактирай профил Log out Отписване Add Contact... Добавяне на контакт... Next Conversation Следващ разговор Previous Conversation Предишен разговор Show Tray action menu to show qTox window Показване Add friend title of the window Прибави приятел Group invites title of the window Покани за групи File transfers title of the window Трансфери на файлове Settings title of the window Настройки My profile title of the window Моят профил Failed to send file "%1" Неуспешно изпратен файл "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/cs.ts000066400000000000000000003302541415623743500155300ustar00rootroot00000000000000 AVForm Default resolution Výchozí rozlišení Audio/Video Audio/Video Disabled Zakázáno Select region Zvolit oblast Screen %1 Obrazovka %1 Audio Settings Nastavení zvuku Gain Zesílení Playback device Přehrávací zařízení Use slider to set volume of your speakers. Použijte posuvník pro nastavení hlasitosti reproduktorů. Capture device Nahrávací zařízení Volume Hlasitost Video Settings Nastavení videa Video device Video zařízení Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Nastavte rozlišení vaší kamery. Čím vyšší hodnota je nastavena, v tím lepší kvalitě vás uvidí ostatní. Čím vyšší kvalita, tím vyšší jsou nároky na internetové připojení. Rychlost vašeho připojení nemusí být vždy dostačující pro vyšší kvalitu videa, což může způsobovat problémy během videohovorů. Resolution Rozlišení Rescan devices Znovu načíst zařízení Test Sound Testovací Zvuk Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Zapne experimentální zvukové pozadí s podporou potlačení ozvěny, je třeba restartovat qTox pro aktivaci. Enable experimental audio backend Povolit experimentální zvukové pozadí Audio quality Kvalita zvuku Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Kvalita přenosu zvuku. Snižte toto nastavení pokud máte pomalé připojení nebo pokud chcete šetřit data. High (64 kbps) Vysoká (64 kbps) Medium (32 kbps) Střední (32 kbps) Low (16 kbps) Nízká (16 kbps) Very low (8 kbps) Velmi nízká (8 kbps) Threshold Práh AboutForm About O aplikaci Original author: %1 Původní autor: %1 You are using qTox version %1. Používáte qTox verzi %1. Commit hash: %1 Commit hash: %1 toxcore version: %1 toxcore verze: %1 Qt version: %1 Qt verze: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Seznam všech známých problémů najdete na našem %1 Github. Pokud ve qTox zjistíte chybu nebo chybu zabezpečení, nahlaste ji podle pokynů v našem článku %2 wiki. Click here to report a bug. Klikněte zde pro nahlášení chyby. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Úplný seznam %1 najdete na Github bug-tracker Replaces `%1` in the `A list of all known…` sledování chyb Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` psaní chybových hlášení contributors Replaces `%1` in `See a full list of…` přispěvatelů AboutFriendForm Dialog Dialogové okno username uživatelské jméno status message zpráva o stavu Used aliases: Používané přezdívky: HISTORY OF ALIASES HISTORIE PŘEZDÍVEK Automatically accept files from contact if set Automaticky přijímat soubory z kontaktu pokud je nastaveno Auto accept files Automaticky přijímat soubory Default directory to save files: Výchozí adresář pro ukládání souborů: Auto accept for this contact is disabled Automatické přijímání je pro tento kontakt zakázané Auto accept call: Automaticky přijímat hovor: Manual Ručně Audio Audio Audio + Video Audio + Video Automatically accept group chat invitations from this contact if set. Automaticky přijímat pozvání k chatu skupiny z tohoto kontaktu pokud je nastaveno. Auto accept group invites Automaticky přijímat skupinové pozvánky Remove history (operation can not be undone!) Odstranit historii (operaci nelze vrátit zpět!) Notes Poznámky Input field for notes about the contact Vstupní pole pro poznámky o kontaktu You can save comment about this contact here. Zde můžete uložit poznámku o kontaktu. History removed Historie odstraněna Choose an auto accept directory popup title Vyberte složku pro automatický příjem <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Toto je veřejný klíč vašeho přítele, použijte jej k ověření své identity prostřednictvím jiného kanálu. Tuto zprávu nemůžete poslat jiným lidem, aby mohli tento kontakt přidat.</p></body></html> Public key (not ToxID): Veřejný klíč (nikoli ToxID): Confirmation Potvrzení Are you sure to remove %1 chat history? Opravdu chcete odstranit %1 historii chatu? Failed to remove chat history with %1! Historie chatu s %1 se nepodařilo odstranit! AboutSettings Version Verze License Licence Authors Autoři Known Issues Známé problémy Open update download link Otevřete odkaz ke stažení aktualizace Update available Aktualizace je k dispozici qTox is up to date ✓ qTox je aktuální ✓ AddFriendForm Couldn't add friend Nepodařilo se přidat přítele Add Friends Přidat přítele Send friend request Odeslat požadavek příteli Invalid Tox ID format Neplatný formát Tox ID Add a friend Přidat přítele Friend requests Žádosti o přátelství Accept Přijmout Reject Odmítnout Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, 76 hexadecimálních znaků nebo jmeno@domena.cz Type in Tox ID of your friend Zadejte Tox ID vašeho přítele Friend request message Zpráva s žádostí o přátelství Type message to send with the friend request or leave empty to send a default message Zadejte zprávu, kterou chcete odeslat s žádostí o přátelství, nebo nechte prázdné pro odeslání výchozí zprávy %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID je neplatné nebo neexistuje You can't add yourself as a friend! When trying to add your own Tox ID as friend Nelze přidat sám sebe jako přítele! Open contact list Otevřít seznam kontaktů Couldn't open file Soubor se nepodařilo otevřít Couldn't open the contact file Error message when trying to open a contact list file to import Nepodařilo se otevřít soubor s kontakty Invalid file Neplatný soubor We couldn't find any contacts to import in this file! V souboru jsme nenašli žádné kontakty pro import! Tox ID Tox ID of the person you're sending a friend request to ToxID either 76 hexadecimal characters or name@example.com Tox ID format description 76 šestnáctkových znaků nebo jmeno@domena.cz Message The message you send in friend requests Zpráva Open Button to choose a file with a list of contacts to import Otevřít Send friend requests Poslat žádost o přátelství %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Jsem %1 ! Přidáš si mne za přítele a Zatoxujeme si ? Import a list of contacts, one Tox ID per line Importovat seznam kontaktů, jedno Tox ID na řádek Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Připraven k importu %n kontakt, potvrďte kliknutím na Odeslat Připraveno k importu %n kontaktů, potvrďte kliknutím na Odeslat Kontakty připravené k importu. Jejich počet: %n . Potvrďte kliknutím na Odeslat Import contacts Importovat kontakty AdvancedForm Advanced Pokročilé Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Pokud %1 nevíte, co zde dělat, prosím tady nic %2měňte. Změny provedené zde mohou vést k problémům s qTox a dokonce ke ztrátě dat, např. historie. really vážně not ne IMPORTANT NOTE DŮLEŽITÉ Reset settings Návrat k výchozímu nastavení All settings will be reset to default. Are you sure? Všechna nastavení budou obnovena na výchozí hodnoty. Jste si jisti? Yes Ano No Ne Call active popup title Hovor je spojen You can't disconnect while a call is active! popup text Nelze se odpojit dokud je hovor aktivní! Save File Uložit Soubor Logs (*.log) Protokoly (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Uložit nastavení do pracovní složky místo výchozího umístění Make Tox portable Udělat Tox přenosný Reset to default settings Návrat k výchozímu nastavení Portable Přenosný Connection Settings Nastavení připojení Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Povolit IPv6 (doporučeno) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Zakázání umožní například použití Tox přes Tor. Přidává ale zatížení Tox sítě, zakažte pouze pokud je to nutné. Enable UDP (recommended) Text on checkbox to disable UDP Povolit UDP (doporučeno) Proxy type: Typ proxy: Address: Text on proxy addr label Adresa: Port: Text on proxy port label Port: None Žádný SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Znovu připojit Debug Ladění Export Debug Log Export protokolu ladění Copy Debug Log Kopírovat protokol ladění Enable LAN discovery Povolit vyhledávání LAN ChatForm Send a file Odeslat soubor qTox wasn't able to open %1 qTox nemohl otevřít %1 %1 calling %1 volá Failed to open temporary file Temporary file for screenshot Nepodařilo se otevřít dočasný soubor qTox wasn't able to save the screenshot Nepodařilo se uložit snímek obrazovky Call with %1 ended. %2 Hovor s %1 skončil. %2 Call duration: Délka hovoru: Unable to open Nelze otevřít Bad idea Špatný nápad Calling %1 Volání %1 %1 is typing %1 píše Copy Kopírovat You're trying to send a sequential file, which is not going to work! Pokoušíte se odeslat sekvenční soubor, který nebude fungovat! %1 is now %2 e.g. "Dubslow is now online" %1 je nově %2 Call with %1 ended unexpectedly. %2 Hovor s %1 byl neočekávaně ukončen. %2 Filename contained illegal characters Název souboru obsahuje nepovolené znaky Illegal characters have been changed to _ so you can save the file on windows. Neplatné znaky byly změněny na _ takže můžete soubor uložit do systému Windows. ChatFormHeader Can't start audio call Nelze spustit audio volání Start audio call Zahajte audio hovor End audio call Ukončit audio hovor Cancel audio call Zrušit audio hovor Accept audio call Přijmout audio hovor Can't start video call Nelze zahájit videohovor Start video call Zahajte videohovor End video call Ukončit video hovor Cancel video call Zrušit video hovor Accept video call Přijmout video hovor Sound can be disabled only during a call Zvuk lze vypnout pouze během hovoru Unmute call Zrušit ztlumení hovoru Mute call Ztlumit hovor Microphone can be muted only during a call Mikrofon lze ztlumit pouze během hovoru Unmute microphone Zapnout mikrofón Mute microphone Vypnout mikrofon ChatLog pending probíhá Copy Kopírovat Select all Vybrat vše ChatTextEdit Type your message here... Zde napište svou zprávu... CircleWidget Rename circle Menu for renaming a circle Přejmenovat kruh Remove circle Menu for removing a circle Odstranit kruh Open all in new window Otevřít vše v novém okně Core /me offers friendship, "%1" nabídnout přátelství, "%1" Invalid Tox ID Error while sending friendship request Neplatné Tox ID You need to write a message with your request Error while sending friendship request Musíte napsat svou zprávu spolu s požadavkem Your message is too long! Error while sending friendship request Vaše zpráva je příliš dlouhá! Friend is already added Error while sending friendship request Přítel je již v seznamu Groupchat %1 DesktopNotify New message Nová zpráva Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Rámec 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 ETA:10:10 Filename Jméno souboru Waiting to send... file transfer widget Čekáme na odeslání... Accept to receive this file file transfer widget Potvrďte pro přijetí souboru Location not writable Title of permissions popup Nelze zapsat do cíle You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nemáte povolení pro zápis do cíle. Vyberte prosím jiný nebo zruště přenos souboru. Resuming... file transfer widget Navazuji... Cancel transfer Zrušit přenos Pause transfer Pozastavit přenos Resume transfer Navázat přenos Accept transfer Přijmout přenos Save a file Title of the file saving dialog Uložit soubor Paused file transfer widget Pozastaveno Open file Otevřít soubor Open file directory Otevřít adresář souboru Remote Paused file transfer widget Vzdáleně pozastaveno FilesForm Downloads Stažené Uploads Odeslané Transferred Files "Headline" of the window Přenesené soubory FriendListWidget Today Dnes Yesterday Včera Last 7 days Posledních 7 dní This month Tento měsíc Older than 6 Months Starší 6 měsícům Never Nikdy FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Přidání přítele Someone wants to make friends with you Někdo se chce stát vaším přítelem User ID: Uživatelské ID: Friend request message: Požadavek o přátelství: Accept Accept a friend request Přijmout Reject Reject a friend request Odmítnout FriendWidget Invite to group Menu to invite a friend to a groupchat Pozvat do skupiny Move to circle... Menu to move a friend into a different circle Přesunout do kruhu... To new circle Do nového kruhu Remove from circle '%1' Odstranit z kruhu "%1" Move to circle "%1" Přesunout do kruhu "%1" Set alias... Nastavit jméno... Auto accept files from this friend context menu entry Automaticky přijímat soubory od tohoto přítele Remove friend Menu to remove the friend from our friendlist Odebrat přítele Choose an auto accept directory popup title Vyberte sloužku pro automatický příjem New message Nová zpráva Online Přítomen Away Pryč Busy Zaneprázdněn Offline Nepřítomen Open chat in new window Otevřít konverzaci v novém okně Remove chat from this window Odstranit konverzaci z tohoto okna To new group Do nové skupiny Invite to group '%1' Pozvat do skupiny '%1' Show details Zobrazit podrobnosti GeneralForm Choose an auto accept directory popup title Vyberte sloužku pro automatický příjem General Obecné GeneralSettings General Settings Obecné nastavení The translation may not load until qTox restarts. Překlad se nemusí nahrát správně dokud nedojde k restartu qTox. Language: Jazyk: qTox will start minimized in tray. toolTip for Start in tray setting qTox se spustí minimalizovaný v liště. Start in tray Spustit v systémové liště Show system tray icon Zobrazovat ikonu v systémové liště After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Po zavření (X) se qTox minimalizuje do lišty, místo ukončení. Close to tray Zavřít do systémové lišty Enable light tray icon. toolTip for light icon setting Povolit světlou ikonu v liště. Light icon Světlá ikona v systémové liště After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Po stisknutí minimalizovat (_) se qTox schová do lišty, místo panelu úloh. Minimize to tray Minimalizovat do systémové lišty Your status is changed to Away after set period of inactivity. Váš status se při neaktivitě změní na Pryč po uplynutí nastaveného času. Auto away after (0 to disable): Automaticky pryč po (0 pro zakázání): Set to 0 to disable Nastavte 0 pro zakázání Autostart Automaticky spustit Set where files will be saved. Umístění kam ukládat soubory. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Lze využít pro každého uživatele zvlášť nastavením na jejich profilu. Autoaccept files Automaticky přijímat soubory Show contacts' status changes Upozornění při změně stavu kontaktů Start qTox on operating system startup (current profile). Spustit qTox při startu operačního systému (aktuální profil). Default directory to save files: Výchozí adresář pro ukládání souborů: Check for updates Kontrola aktualizací Spell checking Kontrola pravopisu Max autoaccept file size (0 to disable): Maximální velikost souboru pro automatický příjem (0 pro deaktivaci): MB MB GenericChatForm Save chat log Uložit záznam z chatu Cleared Vyčištěno Send message Odeslat zprávu Smileys Emotikony Send file(s) Odeslané soubory Send a screenshot Odeslat snímek obrazovky Clear displayed messages Vyčistit zobrazené zprávy Quote selected text Citovat vybraný text Copy link address Kopírovat adresu odkazu Confirmation Potvrzení You are sure that you want to clear all displayed messages? Jste si jisti, že chcete vymazat všechny zobrazené zprávy? Search in text Prohledat text Go to current date Load chat history... Nahrát historii zpráv... Export to file Export do souboru GenericNetCamView Tox video Tox video přenos Show Messages Zobrazit zprávy Hide Messages Skrýt zprávy Full Screen Celá obrazovka Toggle video preview Přepnout náhled videa Mute audio Ztišit zvuk Mute microphone Vypnout mikrofon End video call Ukončit video hovor Exit full screen Ukončit režim celé obrazovky GroupChatForm %1 has set the title to %2 %1 nastavil název konverzace na %2 %1 has joined the group %1 se připojil ke skupině %1 is now known as %2 %1je nyní známý jako %2 %1 has left the group %1 opustil skupinu %n user(s) in chat Number of users in chat %n uživatel v chatu %n uživatelé v chatu počet uživatelů v chatu je: %n mute ztlumit unmute nahlas GroupInviteForm Groups Skupiny Create new group Vytvořit novou skupinu Group invites Seznam pozvánek do skupin GroupInviteWidget Invited by %1 on %2 at %3. Pozvání přítelem %1 na %2 v %3. Join Připojit se Decline Odmítnout GroupWidget Set title... Nastavit jméno... Quit group Menu to quit a groupchat Opustit skupinu Open chat in new window Otevřít konverzaci v novém okně Remove chat from this window Odstranit konverzaci z tohoto okna %n user(s) in chat Number of users in chat %n uživatel v chatu %n uživatelé v chatu uživatelů v chatu %n New Message Nová zpráva Online Online IdentitySettings Public Information Veřejné informace Tox ID ToxID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Identifiakční údaje které vás identifikují v síti. Sdílejte je se svými přáteli, aby vás mohli kontaktovat. Your Tox ID (click to copy) Vaše Tox ID (kliknutím zkopírujete) This QR code contains your Tox ID. You may share this with your friends as well. Následující QR kód obsahuje vaše Tox ID. Můžete ho také sdílet s vašimi přátely. Save image Uložit obrázek Copy image Kopírovat obrázek Profile Profil Rename profile. tooltip for renaming profile button Přejmenovat profil. Rename rename profile button Přejmenovat Delete profile. delete profile button tooltip Smazat profil. Delete delete profile button Smazat Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Umožňuje exportovat váš Tox profil do souboru. Při exportu nebude nahrána vaše historie. Export export profile button Exportovat Go back to the login screen tooltip for logout button Návrat na přihlašovací obrazovku Logout import profile button Odhlásit Remove password Odstranit heslo Change password Změnit heslo Server Server Hide my name from the public list Nezobrazovat mé jméno ve veřejném seznamu Register Registrace Your password Vaše heslo Update Aktualizovat Register on ToxMe Registrovat na ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Název služby ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Volitelné. Něco o sobě nebo o vaší kočce. Optional. Something about you. Or your cat. Tooltip for the Biography field. Volitelné. Něco o sobě nebo o vaší kočce. ToxMe service to register on. Adresa registrační služby ToxMe. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Pokud není nastaveno, jsou položky ToxMe veřejně viditelné. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Odstranit heslo a šifrování z profilu uživatele. Name input Zadejte jméno Name visible to contacts Jméno viditelné pro kontakty Status message input Nastavení vašeho statusu Status message visible to contacts Váš status viditelný pro kontakty Your Tox ID Vaše Tox ID Save QR image as file Uložit QR kód jako obrázek Copy QR image to clipboard Zkopírovat QR kód do schránky ToxMe username to be shown on ToxMe Uživatelské jméno ToxMe, které se zobrazí na ToxMe Optional ToxMe biography to be shown on ToxMe Volitelná biografie ToxMe, která bude zobrazena na ToxMe ToxMe service address Adresa služby ToxMe Visibility on the ToxMe service Viditelnost v rámci služby ToxMe Password Heslo Update ToxMe entry Aktualizovat záznam v ToxMe Rename profile. Přejmenovat profil. Delete profile. Smazat profil. Export profile Eportovat profil Remove password from profile Odstranit heslo z profilu Change profile password Změnit heslo profilu My name: Moje jméno: My status: Můj status: My username Moje uživatelské jméno My biography O mně My profile Můj profil LoadHistoryDialog Load History Dialog Zobrazit historii Load history from to (about 100 messages are loaded) Select Date Dialog Vyberte dialogové okno Datum Select a date Vyberte datum LoginScreen Username: Uživatelské jméno: Password: Heslo: Confirm: Potvrdit: Password strength: %p% Síla hesla: %p% If the profile does not have a password, qTox can skip the login screen Pokud profl nemá přiřezené heslo qTox může přeskočit přihlašovací obrazovku New Profile Nový profil Couldn't create a new profile Nelze vytvořit nový profil The username must not be empty. Uživatelské jméno nemůže být prázdné. The password must be at least 6 characters long. Heslo musí být alespoň 6 znaků dlouhé. The passwords you've entered are different. Please make sure to enter same password twice. Zadaná hesla se neshodují. Zadejte prosím dvakrát stejné heslo. A profile with this name already exists. Profil s tímto jménem už existuje. Couldn't load this profile Nelze nahrát tento profil This profile is already in use. Profil už je používán. Wrong password. Špatné heslo. Create Profile Vytvořit Profil Load automatically Automatické načtení Import Importovat Load Načíst Load Profile Načíst Profil Password protected profiles can't be automatically loaded. Heslem chráněné profily nelze načíst automaticky. Couldn't load profile Nelze načíst profil There is no selected profile. You may want to create one. Není vybrán žádný profil. Možná budete chtít vytvořit. Username input field Pole pro zadání uživatelského jména Password input field, you can leave it empty (no password), or type at least 6 characters Pole pro zadání hesla, můžete jej nechat prázdné (bez hesla) nebo zadejte alespoň 6 znaků Password confirmation field Pole pro potvrzení hesla Create a new profile button Tlačítko Vytvořit nový profil Profile list Seznam profilů List of profiles Seznam profilů Password input Zadání hesla Load automatically checkbox Zaškrtávací políčko Načíst automaticky Import profile Importovat profil Load selected profile button Tlačítko Načíst vybraný profil New profile creation page Stránka pro vytvoření nového profilu Loading existing profile page Stránka Načíst existující profil MainWindow Your name Vaše jméno Your status Váš status ... ... Add friends Přidat přítele Create a group chat Vytvořit skupinovou konverzaci View completed file transfers Zobrazit dokončené přenosy souborů Change your settings Změnit vaše nastavení Close Zavřít Open profile Otevřít profil Open profile page when clicked Po kliknutí otevřete stránku profilu Status message input Zadání statusu Set your status message that will be shown to others Nastavte svůj status, který se zobrazí ostatním Status Status Set availability status Nastavení statusu dostupnosti Contact search Vyhledávání kontaktů Contact search input for known friends Hledat známé přátele Sorting and visibility Třídění a viditelnost Set friends sorting and visibility Nastavit třídění a viditelnost přátel Open Add friends page Otevřete stránku Přidat přátele Groupchat Skupinový chat Open groupchat management page Otevřít stránku nastavení skupiny File transfers history Historie přenosu souborů Open File transfers history Otevřít historii přenosu souborů Settings Nastavení Open Settings Otevřít Nastavení Nexus View OS X Menu bar Zobrazit Window OS X Menu bar Okno Minimize OS X Menu bar Minimalizovat Bring All to Front OS X Menu bar Přenést vše do popředí Exit Fullscreen Opustit režim celé obrazovky Enter Fullscreen Přepnout na celou obrazovku NotificationEdgeWidget Unread message(s) Nepřečtená zpráva Nepřečtených zpráv Nepřečtené zprávy PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK ZAPNUTÝ PrivacyForm Privacy Soukromí Confirmation Potvrzení Do you want to permanently delete all chat history? Chcete trvale odstranit celou historii konverzace? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Vaši přátelé budou moci vidět že píšete. Send typing notifications Odesílat upozornění o psaní Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Uchovávání historie zpráv je stále vyvíjeno. Je zde riziko změn v ukládání a možná ztráta dat. Keep chat history Uchovávat historii zpráv NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam je součástí vašeho Tox ID. Pokud jste obtěžován žádostni o přátelství měli byste si změnit vaše NoSpam. Lidé si vás nebudou moci přidat s vaším starým Tox ID, ale budete moci komunikovat se starými přáteli. NoSpam No Spam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam je součástí vašeho Tox ID. Pokud jste obtěžován žádostni o přátelství měli byste si změnit vaše NoSpam. Generate random NoSpam Generovat náhodné NoSpam Privacy Soukromí BlackList Černá listina Filter group message by group member's public key. Put public key here, one per line. Filtrujte zprávu skupiny podle veřejného klíče člena skupiny. Sem vložte veřejný klíč, jeden na řádek. Profile Failed to derive key from password, the profile won't use the new password. Nepodařilo se odvodit klíč z hesla, profil nové heslo nepoužije. Couldn't change password on the database, it might be corrupted or use the old password. Nelze změnit heslo v databázi, může být poškozená nebo jste použili staré heslo. Toxing on qTox Toxuji na qToxe ProfileForm Choose a profile picture Změnit profilový obrázek Error Chyba Unable to open this file. Nelze otevřít tento soubor. Unable to read this image. Nelze načíst tento obrázek. The supplied image is too large. Please use another image. Obrázek je příliš velký. Prosím použijte jiný. Rename "%1" renaming a profile Přejmenovat "%1" Couldn't rename the profile to "%1" Nelze přejmenovat profil na "%1" Location not writable Title of permissions popup Nelze zapsat do cílového souboru You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nelze zapsat do cílového souboru. Vyberte prosím jinou nebo zavřete dialog pro uložení. Failed to copy file Nelze zkopírovat soubor The file you chose could not be written to. Nelze zapsat do souboru který je vybrán. Really delete profile? deletion confirmation title Opravdu smazat profil? Are you sure you want to delete this profile? deletion confirmation text Opravdu chcete odstranit profil? Save save qr image Uložit Save QrCode (*.png) save dialog filter Uložit QR kód (*.png) Nothing to remove Nic k vymazání Your profile does not have a password! Váš profil neobsahuje heslo! Really delete password? deletion confirmation title Opravdu vymazat heslo? Please enter a new password. Prosím zadejte nové heslo. Current profile: Aktuální profil: Remove Odstranit Files could not be deleted! deletion failed title Soubory nemohou být odstraněny! Register (processing) Registrace (zpracování) Update (processing) Aktualizace (zpracování) Done! Hotovo! Account %1@%2 updated successfully Účet %1@%2 byl úspěšně aktualizován Successfully added %1@%2 to the database. Save your password %1@%2 úspěšně přidáno do databáze. Uložte si heslo Toxme error Toxme chyba Register Registrace Update Aktualizovat Change password button text Změnit heslo Set profile password button text Nastavit heslo profilu Current profile location: %1 Aktuální umístění profilu: %1 Couldn't change password Nepodařilo se změnit heslo This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Tato skupina znaků říká ostatním toxickým klientům, jak vás kontaktovat. Sdělte je svým přátelům a komunikujte. Toto ID zahrnuje kód NoSpam (modrý) a kontrolní součet (šedý). Empty path is unavaliable Prázdná cesta není k dispozici Failed to rename Nelze přejmenovat Profile already exists Profil již existuje A profile named "%1" already exists. Profil jménem "%1" již existuje. Empty name Prázdné jméno Empty name is unavaliable Nezadali jste jméno Empty path Prázdná cesta Couldn't change password on the database, it might be corrupted or use the old password. Nelze změnit heslo v databázi, může být poškozená nebo jste použli staré heslo. Export profile Eportovat profil Tox save file (*.tox) save dialog filter Tox soubor (*.tox) The following files could not be deleted: deletion failed text part 1 Následující soubory nelze odstranit: Please manually remove them. deletion failed text part 2 Odstraňte je ručne. Are you sure you want to delete your password? deletion confirmation text Opravdu odstranit vaše heslo? Images (%1) filetype filter Obrázky (%1) ProfileImporter Import profile import dialog title Importovat profil Tox save file (*.tox) import dialog filter Tox soubor (*.tox) Ignoring non-Tox file popup title Ignorovat soubory které nejsou Tox Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Upozornění: Vybrali jste soubor, který není Tox; bude ignorován. Profile already exists import confirm title Profil již existuje A profile named "%1" already exists. Do you want to erase it? import confirm text Profil jménem "%1" již existuje. Chcete ho vymazat? File doesn't exist Soubor neexistuje Profile doesn't exist Profil neexistuje Profile imported Profil přidán %1.tox was successfully imported %1.tox byl úspěšně přidán QApplication Ok Ok Cancel Zrušit Yes Ano No Ne LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout z leva do prava QMessageBox Couldn't add friend Nepodařilo se přidat přítele %1 is not a valid Toxme address. %1 není platnou adresou Toxme. You can't add yourself as a friend! When trying to add your own Tox ID as friend Nelze přidat sám sebe jako přítele! QObject Tox URI to parse Tox URI pro zpracování Starts new instance and loads specified profile. Spustit novou instanci a nahrát profil. profile profil %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Jsem %1 ! Napíšeš mi ? Default Výchozí Blue Modrá Olive Olivová Red Červená Violet Fialový Incoming call... Příchozí hovor... Server doesn't support Toxme Server nepodporuje Toxme You're making too many requests. Wait an hour and try again Příliš mnoho požadavků. Počkejte hodinu a zkuste to znovu This name is already in use Toto jméno je již používáno This Tox ID is already registered under another name Toto Tox ID je již registrováno pod jiným jménem Please don't use a space in your name Prosím, nepoužívejte mezeru ve jménu Password incorrect Nesprávné heslo You can't use this name Toto jméno nelze použít Name not found Jméno nebylo nalezeno Tox ID not sent Tox ID nebylo odesláno That user does not exist Tento uživatel neexistuje Error Chyba qTox couldn't open your chat logs, they will be disabled. qTox nemohl otevřít vaše protokoly chatu, budou deaktivovány. None No camera device set Žádné Desktop Desktop as a camera input for screen sharing Plocha Problem with HTTPS connection Problém s připojením HTTPS Internal ToxMe error Vnitřní chyba ToxMe Reformatting text in progress.. Probíhá formátování textu.. Starts new instance and opens the login screen. Spustí novou instanci a otevře přihlašovací obrazovku. Dark Tmavý Dark blue Tmavomodrý Dark olive Tmavě olivová Dark red Tmavočervená Dark violet Tmavě fialová Failed to load profile automatically. online contact status online away contact status pryč busy contact status zaneprázdněn offline contact status offline blocked contact status blokovaný RemoveFriendDialog Remove friend Odebrat přítele Also remove chat history Také odstranit historii chatu Remove Odstranit Are you sure you want to remove %1 from your contacts list? Opravdu chcete odebrat %1 ze seznamu přátel? Remove all chat history with the friend if set Odstranit celou historii chatu s vybraným přítelem ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Klikněte a táhněte pro vybrání oblasti. Ztlačte %1 pro skrytí/zobrazení okna qTox, nebo %2 pro zrušení. Space [Space] key on the keyboard Mezerník Escape [Escape] key on the keyboard Esc Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Ztlačte %1 pro odeslání vybrané oblasti, %2 pro skrytí/zobrazení okna qTox, nebo %3 pro zrušení. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Text nebyl nalezen. Start Start SearchSettingsForm Form Rámec Start search: Zahájit vyhledávání: from the end od konce from the beginning od začátku after date po datu before date před datem 00.00.0000 00.00.0000 Case sensitive Rozeznává velká a malá písmena Whole words only Pouze celá slova Use regular expressions Používejte regulární výrazy SetPasswordDialog Set your password Nastavení hesla The password is too short Heslo je příliš krátké The password doesn't match. Hesla si neodpovídají. Confirm: Potvrdit: Password: Heslo: Password strength: %p% Síla hesla: %p% Confirm password Potvrďte heslo Confirm password input Potvrďte zadání hesla Password input Zadání hesla Password input field, minimum 6 characters long Pole pro zadání hesla, minimálně 6 znaků Settings Circle #%1 Kruh #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Přidat přítele Do you want to add %1 as a friend? Přejete si přidat %1 jako přítele? User ID: Uživatelské ID: Friend request message: Požadavek na přátelství: Send Send a friend request Odeslat Cancel Don't send a friend request Zrušit UserInterfaceForm None Nic User Interface Uživatelské rozhraní UserInterfaceSettings Chat Zprávy Base font: Základní písmo: px px Size: Velikost: New text styling preference may not load until qTox restarts. Nový styl textu se nemusí načíst do restartu qTox. Text Style format: Styl textu: Select text styling preference. Zvolte preferovaný styl textu. Plaintext Prostý text Show formatting characters Zobrazovat formátování znaků Don't show formatting characters Nezobrazovat formátování znaků New message Nová zpráva Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Otevřít okno qTox když obdržíte novou zprávu a žádné okno ještě není otevřené. Open window Otevřít okno Contact list Seznam kontaktů If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Pokud je vybráno, budou skupinové konverzace zobrazeny na začátku seznamu přátel, jinak budou zobrazeny pod přátely kteří jsou aktivní. Place groupchats at top of friend list Zobrazit skupinové konverzace na začátku seznamu Your contact list will be shown in compact mode. toolTip for compact layout setting Váš seznam přátel bude zobrazen v kompaktním režimu. Compact contact list Udělat seznam přátel kompaktní Multiple windows mode Režim více oken Open each chat in an individual window Otevřít každou konverzaci v individuálním okně Emoticons Emotikony Use emoticons Používat emotikony Smiley Pack: Text on smiley pack label Emotikony: Emoticon size: Velikost emotikon: px px Theme Téma Style: Styl: Theme color: Barva tématu: Timestamp format: Formát času: Date format: Formát data: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Pokud je povoleno, každý kontakt bez sady avatarů bude mít vygenerovaný avatar založený na jejich toxickém ID místo výchozího obrázku. Vyžaduje restartování. Use identicons instead of empty avatars Místo prázdných avatarů použijte identicon Use colored nicknames in chats V chatech používejte barevné přezdívky Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Zobrazit oznámení, když obdržíte novou zprávu a okno není vybráno. Notify Oznámit Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned O nových zprávách ve skupinových chatech informujte, pouze pokud jsou zmíněny. Group chats only notify when mentioned Skupinové chatování upozorní pouze tehdy, když je uvedeno Play sound Přehrát zvuk Play sound while Busy Přehrát zvuk když Zaneprázdněn Notify via desktop notifications Upozorňovat prostřednictvím oznámení na ploše Hide message sender and contents Widget File Soubor Edit Profile Upravit profil Change Status Změnit status Log out Odhlásit Edit Upravit Filter... Filtrovat... Contacts Kontakty Add Contact... Přidat přítele... Next Conversation Další konverzece Previous Conversation Předchozí konverzace toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore nelze spustit s vaším nastavením proxy. qTox nelze spustit prosím změňte nastavení a restartujte. Executable file popup title Spustitelný soubor You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Požadujete aby qTox spustil soubor Spustitelné soubory mohou být rizikem pro váš počítač. Jste si jistý, že chcete tento soubor spustit? Couldn't request friendship Nelze požádat o přátelství Status Stav Message failed to send Nepodařilo se odeslat zprávu Add new circle... Přidat nový kruh... By Name Podle jména By Activity Podle aktivity All Vše Online Přítomný Offline Nepřítomný Friends Přátelé Groups Skupiny Search Contacts Hledat kontakty Online Button to set your status to 'Online' Přítomný Away Button to set your status to 'Away' Pryč Busy Button to set your status to 'Busy' Zaneprázdněn toxcore failed to start, the application will terminate after you close this message. toxcore se nepodařilo spustit, aplikace bude ukončena poté, co zavřete tuto zprávu. Your name Vaše jméno Groupchat #%1 Skupinová konverzace #%1 Create new group... Vytvořit novou skupinu... %n New Friend Request(s) %n Nová žádost o přátelství %n Nových žádostí o přátelství Nové žádostí o přátelství: %n %n New Group Invite(s) %n Pozvání do skupiny %n Pozvání do skupin Pozvání do skupin: %n Logout Tray action menu to logout user Odhlásit se Exit Tray action menu to exit tox Konec Show Tray action menu to show qTox window Zobrazit Add friend title of the window Přidat přítele Group invites title of the window Pozvánky do skupin File transfers title of the window Přenosy souborů Settings title of the window Nastavení My profile title of the window Můj profil Failed to send file "%1" Nepodařilo se poslat soubor "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/da.ts000066400000000000000000003134371415623743500155130ustar00rootroot00000000000000 AVForm Audio/Video Audio/Video Default resolution Standardopløsning Disabled Deaktiveret Select region Vælg region Screen %1 Skærm %1 Audio Settings Lydindstillinger Gain Forstærkning Playback device Afspilnings-enhed Use slider to set volume of your speakers. Brug skyderen til at indstille lydstyrken for højttalerne. Capture device Optageenhed Volume Lydstyrke Video Settings Video-indstillinger Video device Videoenhed Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Resolution Opløsning Rescan devices Test Sound Test Lyd Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Lydkvalitet Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Høj (64 kbps) Medium (32 kbps) Middel (32 kbps) Low (16 kbps) Lav (16 kbps) Very low (8 kbps) Meget Lav (8 kbps) Threshold AboutForm About Om Original author: %1 Original forfatter: %1 You are using qTox version %1. Du bruger qTox version %1. Commit hash: %1 toxcore version: %1 Qt version: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Se en fuld liste af %1 på Github bug-tracker Replaces `%1` in the `A list of all known…` Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Skriv Brugbare Fejlrapporter contributors Replaces `%1` in `See a full list of…` AboutFriendForm Dialog username brugernavn status message statusmeddelelse Used aliases: Brugte aliaser: HISTORY OF ALIASES Automatically accept files from contact if set Auto accept files Accepter filer automatisk Default directory to save files: Standardmappen til at gemme filer: Auto accept for this contact is disabled Auto accept call: Manual Audio Audio + Video Lyd + Video Automatically accept group chat invitations from this contact if set. Auto accept group invites Accepter gruppeinvitationer automatisk Remove history (operation can not be undone!) Fjern historik (kan ikke fortrydes!) Notes Noter Input field for notes about the contact You can save comment about this contact here. Gem kommentar om kontaktpersonen her. History removed Historik fjernet Choose an auto accept directory popup title <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Version License Licens Authors Forfattere Known Issues Kendte problemer Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Tilføj venner Invalid Tox ID format Ugyldigt Tox id-format Send friend request Send venneanmodning Couldn't add friend Kunne ikke tilføje ven Add a friend Tilføj en ven Friend requests Venneanmodning Accept Accepter Reject Afvis Tox ID, either 76 hexadecimal characters or name@example.com Type in Tox ID of your friend Friend request message Type message to send with the friend request or leave empty to send a default message %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend Du kan ikke tilføje dig selv som en ven! Open contact list Couldn't open file Kunne ikke åbne fil Couldn't open the contact file Error message when trying to open a contact list file to import Kunne ikke åbne kontaktfil Invalid file Ugyldig fil We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description Message The message you send in friend requests Besked Open Button to choose a file with a list of contacts to import Åben Send friend requests Send venneanmodning %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts Importer kontakter AdvancedForm Advanced Avanceret Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not ikke IMPORTANT NOTE Reset settings All settings will be reset to default. Are you sure? Yes Ja No Nej Call active popup title You can't disconnect while a call is active! popup text Save File Gem File Logs (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Make Tox portable Reset to default settings Nulstil til standardindstillinger Portable Connection Settings Forbindlesesinnstillinger Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Aktiver IPv6 (andbefalet) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP Aktiver UDP (andbefalet) Proxy type: Address: Text on proxy addr label Addresse: Port: Text on proxy port label None Ingen SOCKS5 HTTP Reconnect reconnect button Debug Export Debug Log Copy Debug Log Enable LAN discovery ChatForm Send a file Send en fil qTox wasn't able to open %1 qTox var ikke i stand til at åbne %1 Unable to open Bad idea Dårlig idé %1 calling Calling %1 Failed to open temporary file Temporary file for screenshot qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch Call with %1 ended. %2 Call duration: %1 is typing Copy Kopier You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" %1 er nu %2 Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Start audio call Start lydopkald End audio call Afslut lydopkald Cancel audio call Annuller lydopkald Accept audio call Accepter lydopkald Can't start video call Kan ikke starte videoopkald Start video call Start video opkald End video call Alfsut videoopkald Cancel video call Annuller videoopkald Accept video call Accepter videoopkald Sound can be disabled only during a call Unmute call Mute call Microphone can be muted only during a call Unmute microphone Mute microphone ChatLog Copy Kopier Select all Vælg alle pending ventende ChatTextEdit Type your message here... Skriv din besked her... CircleWidget Rename circle Menu for renaming a circle Remove circle Menu for removing a circle Open all in new window Åben alt i nyt vindue Core /me offers friendship, "%1" Invalid Tox ID Error while sending friendship request Ugyldigt Tox ID You need to write a message with your request Error while sending friendship request Your message is too long! Error while sending friendship request Din besked er for lang! Friend is already added Error while sending friendship request Ven er allerede tilføjet Groupchat %1 DesktopNotify New message Ny besked Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen Form 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen Filename Ausgelassen Filnavn Waiting to send... file transfer widget Venter på at sende... Accept to receive this file file transfer widget Accepter for at modtage denne fil Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Resuming... file transfer widget Genoptager... Cancel transfer Annuller overførsel Pause transfer Paused file transfer widget Open file Åben fil Open file directory Åben mappe Resume transfer Accept transfer Save a file Title of the file saving dialog Gem en fil Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Downloads Uploads FriendListWidget Today I dag Yesterday I går Last 7 days Sidste 7 dage This month Denne måned Older than 6 Months Never Aldrig FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Someone wants to make friends with you User ID: Friend request message: Accept Accept a friend request Accepter Reject Reject a friend request Afvis FriendWidget Invite to group Menu to invite a friend to a groupchat Inviter til gruppe Move to circle... Menu to move a friend into a different circle To new circle Remove from circle '%1' Move to circle "%1" Open chat in new window Åben chat i nyt vindue Remove chat from this window Fjern chat fra dette vindue Set alias... Auto accept files from this friend context menu entry Remove friend Menu to remove the friend from our friendlist Show details Choose an auto accept directory popup title New message Ny besked Online Online Away Busy Offline Ausgelassen Offline To new group Til ny gruppe Invite to group '%1' GeneralForm General Choose an auto accept directory popup title GeneralSettings General Settings Generelle Indstillinger The translation may not load until qTox restarts. Language: Sprog: Show system tray icon Enable light tray icon. toolTip for light icon setting Light icon qTox will start minimized in tray. toolTip for Start in tray setting Start in tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Close to tray After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Minimize to tray Autostart Set where files will be saved. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Autoaccept files Set to 0 to disable Your status is changed to Away after set period of inactivity. Auto away after (0 to disable): Show contacts' status changes Start qTox on operating system startup (current profile). Default directory to save files: Standardmappen til at gemme filer: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Send besked Smileys Send file(s) Send fil(er) Send a screenshot Send er skærmbillede Save chat log Clear displayed messages Cleared Quote selected text Copy link address Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Export to file GenericNetCamView Tox video Show Messages Vis Beskeder Hide Messages Skjul Beskeder Full Screen Toggle video preview Mute audio Mute microphone End video call Alfsut videoopkald Exit full screen GroupChatForm %1 has set the title to %2 %1 har sat titlen til %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Grupper Create new group Opret ny gruppe Group invites Gruppeinvitationer GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Set title... Open chat in new window Åben chat i nyt vindue Remove chat from this window Fjern chat fra dette vindue Quit group Menu to quit a groupchat Forlad gruppe %n user(s) in chat Number of users in chat New Message Online Online IdentitySettings Public Information Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Your Tox ID (click to copy) Profile Profil Rename profile. tooltip for renaming profile button Go back to the login screen tooltip for logout button Logout import profile button Log ud Remove password Change password Ændre dit kodeord This QR code contains your Tox ID. You may share this with your friends as well. Save image Gem billede Copy image Kopier billede Rename rename profile button Omdøb Delete profile. delete profile button tooltip Slet profil. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Export export profile button Eksporter Delete delete profile button Slet Server Hide my name from the public list Register Your password Update Opdater Register on ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Navn synligt til kontakter Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Kodeord Update ToxMe entry Rename profile. Delete profile. Slet profil. Export profile Eksporter profil Remove password from profile Change profile password My name: Mit navn: My status: My username My biography My profile LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Password: Kodeord: Confirm: Password strength: %p% Create Profile Opret Profil If the profile does not have a password, qTox can skip the login screen Load automatically Indlæs automatisk Load Load Profile New Profile Ny Profil Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. Kodeordet skal være mindst 6 tegn. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Forkert kodeord Import Password protected profiles can't be automatically loaded. Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Dit navn Your status Din status ... Ausgelassen Add friends Tilføj venner Create a group chat View completed file transfers Change your settings Close Luk Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status Status Set availability status Contact search Kontaktsøgning Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Gruppechat Open groupchat management page File transfers history Open File transfers history Settings Indstillinger Open Settings Nexus View OS X Menu bar Se Window OS X Menu bar Vindue Minimize OS X Menu bar Minimér Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox ProfileForm Choose a profile picture Error Fejl Rename "%1" renaming a profile Omdøb "%1" Unable to open this file. Kunne ikke åbne denne fil. Current profile: Remove Fjern Unable to read this image. The supplied image is too large. Please use another image. Couldn't rename the profile to "%1" Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Nothing to remove Your profile does not have a password! Din profil har ikke et kodeord! Really delete password? deletion confirmation title Please enter a new password. Are you sure you want to delete this profile? deletion confirmation text Save save qr image Gem Save QrCode (*.png) save dialog filter Gem QrCode (*.png) Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Færdig! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Opdater Change password button text Ændre dit kodeord Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists Profilen eksisterer allerede A profile named "%1" already exists. Empty name Tomt navn Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Eksporter profil Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter Billeder (%1) ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title Profilen eksisterer allerede A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Ok Cancel Annullér Yes Ja No Nej LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend Kunne ikke tilføje ven %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend Du kan ikke tilføje dig selv som en ven! QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Oliven Red Rød Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! None No camera device set Ingen Desktop Desktop as a camera input for screen sharing Skrivebord Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect Forkert kodeord You can't use this name Du kan ikke bruge dette navn Name not found Tox ID not sent That user does not exist Error Fejl qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status away contact status busy contact status offline contact status blocked contact status RemoveFriendDialog Remove friend Also remove chat history Remove Fjern Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Mellemrum Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Password: Kodeord: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Tilføj en ven Do you want to add %1 as a friend? User ID: Friend request message: Send Send a friend request Cancel Don't send a friend request Annullér UserInterfaceForm None Ingen User Interface Brugerflade UserInterfaceSettings Chat Base font: Standard Skrifttype: px Size: Størrelse: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message Ny besked Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list Kontaktliste If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Kompakt kontaktliste Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme Style: Theme color: Timestamp format: Date format: Datoformat: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Afspil lyd Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Online Away Button to set your status to 'Away' Busy Button to set your status to 'Busy' toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File Edit Profile Change Status Log out Edit Redigér Logout Tray action menu to logout user Log ud Exit Tray action menu to exit tox Filter... Filtrer Contacts Kontakter Add Contact... Tilføj Kontakt... Next Conversation Næste Samtale Previous Conversation Tidligere Samtale Executable file popup title Udførbar fil You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Du har bedt qTox at åbne en udførbar fil. Udførbare filer kan potentielt skade din computer. Er du sikker på, st du ønsker at udføre denne fil? Couldn't request friendship Status Status Your name Dit navn Message failed to send Meddelelse kunne ikke afsendes Add new circle... Opret ny cirkel... By Name Efter Navn By Activity Efter Aktivitet All Alle Online Online Offline Ausgelassen Offline Friends Venner Groups Grupper Search Contacts Søg i Kontakter Groupchat #%1 Create new group... %n New Friend Request(s) %n New Group Invite(s) Show Tray action menu to show qTox window Add friend title of the window Group invites title of the window Gruppeinvitationer File transfers title of the window Filoverførsler Settings title of the window Indstillinger My profile title of the window Failed to send file "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/de.ts000066400000000000000000003336011415623743500155120ustar00rootroot00000000000000 AVForm Audio/Video Audio/Video Default resolution Standardauflösung Disabled Deaktiviert Select region Region auswählen Screen %1 Bildschirm %1 Audio Settings Audioeinstellungen Gain Aufnahmelautstärke Playback device Wiedergabegerät Use slider to set volume of your speakers. Verwende den Schieberegler, um die Wiedergabelautstärke deiner Lautsprecher anzupassen. Capture device Aufnahmegerät Volume Wiedergabelautstärke Video Settings Videoeinstellungen Video device Kamera Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Wähle deine Kameraauflösung. Höhere Werte führen zu einem schärferen Bild für deine Freunde, allerdings wird eine bessere Internetverbindung benötigt. Zu hohe Auflösungen können daher zu Problemen in Videoanrufen führen, wenn die Verbindung nicht schnell und stabil genug ist. Resolution Kameraauflösung Rescan devices Geräte aktualisieren Test Sound Testton Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Aktiviert den experimentellen Audio Back-End mit Echo Dämpfung, benötigt jedoch einen Neustart von qTox. Enable experimental audio backend Aktiviere den experimentellen Audio Back-End Audio quality Tonqualität Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Übertragene Tonqualität. Verringern Sie diese Einstellung, wenn Ihre Bandbreite nicht ausreichend ist oder wenn Sie die Internetauslastung verringern möchten. High (64 kbps) Hoch (64 kbps) Medium (32 kbps) Mittel (32 kbps) Low (16 kbps) Niedrig (16 kbps) Very low (8 kbps) Sehr niedrig (8 kbps) Threshold Schwellenwert AboutForm About Über Original author: %1 Originalauthor: %1 You are using qTox version %1. Du verwendest die qTox-Version %1. Commit hash: %1 Commit-Hash: %1 toxcore version: %1 Toxcore-Version: %1 Qt version: %1 Qt-Version: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Eine Liste von bekannten Problemen kann in unserem %1 angesehen werden. Wenn Du einen Bug oder eine Sicherheitslücke findest, melde ihn bitte entsprechend unseren Richtlinien, die Du in unserem Wiki-Artikel %2 findest. Click here to report a bug. Hier klicken, um einen Fehler zu melden. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Eine vollkommene Liste von %1 findest du auf Github bug-tracker Replaces `%1` in the `A list of all known…` Bug-Tracker Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` „Schreiben von nützlichen Fehlerberichten” (Englisch) contributors Replaces `%1` in `See a full list of…` Mitwirkende AboutFriendForm Dialog Dialog username Benutzername status message Statusmeldung Used aliases: Verwendete Nutzernamen: HISTORY OF ALIASES Verlauf der verwendeten Nutzernamen Automatically accept files from contact if set Nimmt Dateien dieses Kontaktes automatisch an, wenn aktiviert Auto accept files Dateien automatisch annehmen Default directory to save files: Standartordner für das Speichern von Dateien: Auto accept for this contact is disabled Automatische Annahme von Dateien ist für diesem Kontakt deaktiviert Auto accept call: Anruf automatisch annehmen: Manual Manuell Audio Audio Audio + Video Audio + Video Automatically accept group chat invitations from this contact if set. Akzeptieren Sie automatisch Gruppenchat-Einladungen von diesem Kontakt, falls festgelegt. Auto accept group invites Gruppeneinladungen automatisch annehmen Remove history (operation can not be undone!) Chatverlauf löschen (kann nicht rückgängig gemacht werden!) Notes Notizen Input field for notes about the contact Eingabefeld für Notizen über den Kontakt You can save comment about this contact here. Hier können Sie Notizen über diesen Kontakt eintragen. History removed Chatverlauf gelöscht Choose an auto accept directory popup title Wählen Sie einen Ordner aus, in dem die automatisch-akzeptierten Dateien gespeichert werden sollen <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Dies ist der öffentliche Schlüssel Ihres Freundes, verwenden Sie es, um seine Identität über einen anderen Kanal zu überprüfen. Das kann man nicht an andere Personen schicken, damit sie diesen Kontakt hinzufügen können.</p></body></html> Public key (not ToxID): Öffentlicher Schlüssel (nicht ToxID): Confirmation Bestätigung Are you sure to remove %1 chat history? Soll der Chatverlauf mit %1 gelöscht werden? Failed to remove chat history with %1! Der Chatverlauf mit %1 konnte nicht gelöscht werden! AboutSettings Version Version License Lizenz Authors Entwickler Known Issues Bekannte Probleme Open update download link Update herunterladen Update available Update verfügbar qTox is up to date ✓ qTox ist auf dem neusten Stand ✓ AddFriendForm Add Friends Freunde hinzufügen Invalid Tox ID format Ungültige Tox ID Send friend request Freundschaftsanfrage senden Add a friend Einen Freund hinzufügen Friend requests Freundschaftsanfragen Accept Annehmen Reject Ablehnen Couldn't add friend Freund konnte nicht hinzugefügt werden Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, entweder 76 hexadezimale Zeichen oder name@beispiel.de Type in Tox ID of your friend Tox ID deines Freundes eingeben Friend request message Nachricht deiner Freundschaftsanfrage Type message to send with the friend request or leave empty to send a default message Nachricht eingeben, die mit der Freundschaftsanfrage gesendet werden soll (optional) %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID ist ungültig oder existiert nicht You can't add yourself as a friend! When trying to add your own Tox ID as friend Du kannst dich nicht selbst als Freund hinzufügen! Open contact list Öffne Kontaktliste Couldn't open file Kann Datei nicht öffnen Couldn't open the contact file Error message when trying to open a contact list file to import Kann Kontaktdatei nicht öffnen Invalid file Ungültige Datei We couldn't find any contacts to import in this file! Die Datei enthält keine Kontakte zum Importieren! Tox ID Tox ID of the person you're sending a friend request to ToxID either 76 hexadecimal characters or name@example.com Tox ID format description Entweder 76 hexadezimale Zeichen oder name@example.com Message The message you send in friend requests Nachricht Open Button to choose a file with a list of contacts to import Öffnen Send friend requests Freundschaftsanfragen senden %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Hier ist %1! Lust sich mit mir zu unterhalten? Import a list of contacts, one Tox ID per line Importiere Kontaktliste, eine Tox ID pro Zeile Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) %n Kontakt wird importiert. Klicken Sie auf Senden, um den Import zu bestätigen. %n Kontakte werden importiert. Kicken Sie auf Senden, um den Import zu bestätigen. Import contacts Importiere Kontakte AdvancedForm Advanced Erweitert Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Wenn du nicht %1 weißt, was du tust, solltest du hier %2 ändern. Änderungen, die du hier machst, könnten zu Problemen mit qTox und sogar dem Verlust deiner Daten, z.B. dem Verlauf, führen. really wirklich not nicht IMPORTANT NOTE WICHTIGER HINWEIS Reset settings Einstellungen zurücksetzen All settings will be reset to default. Are you sure? Alle Einstellungen werden zurückgesetzt. Bist Du dir sicher? Yes Ja No Nein Call active popup title Anruf aktiv You can't disconnect while a call is active! popup text Du kannst dich nicht abmelden, solange ein Anruf noch aktiv ist! Save File Datei speichern Logs (*.log) Protokolle (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Einstellungen im Arbeitsverzeichnis (anstelle des üblichen Konfigurationsverzeichnis) speichern Make Tox portable Tox portabel machen Reset to default settings Einstellungen zurücksetzen Portable Portabel Connection Settings Verbindungseinstellungen Enable IPv6 (recommended) Text on a checkbox to enable IPv6 IPv6 aktivieren (empfohlen) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Wenn deaktiviert, lässt sich z.B. qTox über Tor verwenden. Die Deaktivierung belastet allerdings das Tox-Netzwerk, also bitte deaktiviere es nur wenn nötig. Enable UDP (recommended) Text on checkbox to disable UDP UDP aktivieren (Empfohlen) Proxy type: Proxy-Typ: Address: Text on proxy addr label Adresse: Port: Text on proxy port label Port: None Kein Proxy SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Erneut verbinden Debug Debug Export Debug Log Fehlerbericht exportieren Copy Debug Log Fehlerbericht kopieren Enable LAN discovery Netzwerkerkennung aktivieren ChatForm Send a file Datei versenden qTox wasn't able to open %1 %1 konnte nicht geöffnet werden Unable to open Kann nicht geöffnet werden Bad idea Datei nicht gesendet %1 calling %1 ruft an Calling %1 Anruf %1 Failed to open temporary file Temporary file for screenshot Temporäre Datei konnte nicht geöffnet werden qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox konnte den Screenshot nicht speichern Call with %1 ended. %2 Anruf mit %1 beendet. %2 Call duration: Anrufdauer: %1 is typing %1 tippt gerade Copy Kopieren You're trying to send a sequential file, which is not going to work! Du versuchst eine kontinuierliche Datei zu senden, das wird nicht funktionieren! %1 is now %2 e.g. "Dubslow is now online" %1 ist jetzt %2 Call with %1 ended unexpectedly. %2 Anruf mit %1 brach unerwartet ab. %2 Filename contained illegal characters Der Dateiname enthält nicht unterstützte Satzzeichen Illegal characters have been changed to _ so you can save the file on windows. Nicht unterstützte Satzzeichen wurden zu _ geändert, um sie in Windows speichern zu können. ChatFormHeader Can't start audio call Sprachanruf konnte nicht gestartet werden Start audio call Sprachanruf starten End audio call Sprachanruf beenden Cancel audio call Sprachanruf abbrechen Accept audio call Sprachanruf annehmen Can't start video call Videoanruf konnte nicht gestartet werden Start video call Videoanruf starten End video call Videoanruf beenden Cancel video call Videoanruf abbrechen Accept video call Videoanruf annehmen Sound can be disabled only during a call Lautsprecher kann nur während eines Anrufs stumm geschaltet werden Unmute call Stummschaltung deaktivieren Mute call Anruf stummschalten Microphone can be muted only during a call Mikrofon kann nur während eines Anrufs stummgeschaltet werden Unmute microphone Stummschaltung für Mikrofon aufheben Mute microphone Mikrofon stummschalten ChatLog Copy Kopieren Select all Alles auswählen pending Ausstehend ChatTextEdit Type your message here... Hier eine Nachricht eingeben ... CircleWidget Rename circle Menu for renaming a circle Kreis umbenennen Remove circle Menu for removing a circle Kreis entfernen Open all in new window Alle in neuem Fenster öffnen Core /me offers friendship, "%1" /me macht eine Freundschaftsanfrage: „%1“ Invalid Tox ID Error while sending friendship request Ungültige Tox ID You need to write a message with your request Error while sending friendship request Du musst eine Nachricht mit deiner Anfrage schreiben Your message is too long! Error while sending friendship request Deine Nachricht ist zu lang! Friend is already added Error while sending friendship request Dieser Freund wurde bereits hinzugefügt Groupchat %1 Gruppenchat %1 DesktopNotify New message Neue Nachricht Incoming file transfer Eingehende Dateiübertragung Friend request received Freundschaftsanfrage erhalten New group message Neue Gruppennachricht Group invite received Gruppeneinladung erhalten FileTransferWidget Form Ausgelassen Eingabemaske 10Mb Ausgelassen 0kb/s Ausgelassen ETA:10:10 Ausgelassen Filename Ausgelassen Dateiname Waiting to send... file transfer widget Dateitransfer läuft ... Accept to receive this file file transfer widget Akzeptiere, um die Datei zu empfangen Location not writable Title of permissions popup Keine Schreibrechte für diesen Ordner You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Du besitzt nicht die Rechte, um hier eine Datei zu speichern. Bitte wähle einen anderen Ordner oder brich die Aktion ab. Resuming... file transfer widget Fortsetzen ... Cancel transfer Übertragung abbrechen Pause transfer Übertragung anhalten Paused file transfer widget Angehalten Open file Datei öffnen Open file directory Ordner öffnen Resume transfer Übertragung fortsetzen Accept transfer Übertragung annehmen Save a file Title of the file saving dialog Datei speichern Remote Paused file transfer widget Remote Angehalten FilesForm Transferred Files "Headline" of the window Übertragene Dateien Downloads Heruntergeladene Dateien Uploads Hochgeladene Dateien FriendListWidget Today Heute Yesterday Gestern Last 7 days letzte Woche This month Diesen Monat Older than 6 Months Älter als 6 Monate Never Nie FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Freundschaftsanfrage Someone wants to make friends with you Jemand lädt dich in seine Freundesliste ein User ID: Benutzer-ID: Friend request message: Nachrichtentext der Freundschaftsanfrage: Accept Accept a friend request Annehmen Reject Reject a friend request Ablehnen FriendWidget Invite to group Menu to invite a friend to a groupchat Gruppeneinladung Move to circle... Menu to move a friend into a different circle Verschieben in Kreis... To new circle Zu neuem Kreis Remove from circle '%1' Aus Kreis "%1" entfernen Move to circle "%1" In Kreis "%1" verschieben Open chat in new window Chat in neuem Fenster öffnen Remove chat from this window Chat aus diesem Fenster entfernen To new group Zu einer neuen Gruppe Invite to group '%1' In Gruppe „%1“ einladen Set alias... Pseudonym wählen... Auto accept files from this friend context menu entry Dateien von diesem Freund automatisch annehmen Remove friend Menu to remove the friend from our friendlist Freund löschen Show details Zeige Details Choose an auto accept directory popup title Speicherort für empfangene Dateien wählen New message Neue Nachricht Online Online Away Abwesend Busy Beschäftigt Offline Ausgelassen GeneralForm General Allgemein Choose an auto accept directory popup title Speicherort für empfangenen Dateien wählen GeneralSettings General Settings Allgemeine Einstellungen The translation may not load until qTox restarts. Die Änerung wird erst nach Neustart von qtox aktiv. Language: Sprache: Show system tray icon Icon in Systemleiste anzeigen Enable light tray icon. toolTip for light icon setting Helles Icon aktivieren. Light icon Helles Icon qTox will start minimized in tray. toolTip for Start in tray setting qTox wird in der Systemleiste minimiert starten. Start in tray Im Hintergrund starten After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Durch das Klicken auf „Schließen“ (X) wird qTox nicht beendet, sondern in die Systemleiste minimiert. Close to tray In Systemleiste minimieren After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Durch Klicken auf „Minimieren“ (_) wird qTox in den Systemabschnitt minimiert und verschwindet von der Startleiste. Minimize to tray In Systemleiste minimieren Autostart Autostart Set where files will be saved. Datei-Speicherort festlegen. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Dies lässt sich für jeden Kontakt per Rechtsklick einstellen. Autoaccept files Dateien automatisch annehmen Set to 0 to disable '0' deaktiviert die Funktion Your status is changed to Away after set period of inactivity. Nach der festgelegten Zeit wird dein Status auf „Abwesend“ gesetzt. Auto away after (0 to disable): Automatisch abwesend nach (Aus = 0): Show contacts' status changes Statusänderungen deiner Freunde anzeigen Start qTox on operating system startup (current profile). Mit Betriebssystem starten (aktuelles Profil). Default directory to save files: Standardordner für Dateien: Check for updates Auf Aktualisierungen prüfen Spell checking Rechtschreibprüfung Max autoaccept file size (0 to disable): Max. automatisch akzeptierte Dateigröße (0 zum Deaktivieren): MB MB GenericChatForm Send message Nachricht versenden Smileys Emoticons Send file(s) Datei(en) senden Send a screenshot Screenshot versenden Save chat log Gesprächsverlauf speichern Clear displayed messages Angezeigte Nachrichten entfernen Cleared Gelöscht Quote selected text Ausgewählten Text zitieren Copy link address Link-Adresse kopieren Confirmation Bestätigung You are sure that you want to clear all displayed messages? Sollen wirklich alle angezeigten Nachrichten gelöscht werden? Search in text Suche im Text Go to current date Zum aktuellen Datum gehen Load chat history... Gesprächsverlauf laden... Export to file In Datei exportieren GenericNetCamView Tox video Tox-Videokonferenz Show Messages Nachrichten anzeigen Hide Messages Nachrichten nicht anzeigen Full Screen Vollbild Toggle video preview Videovorschau umschalten Mute audio Ton stummschalten Mute microphone Mikrofon stummschalten End video call Videoanruf beenden Exit full screen Vollbild verlassen GroupChatForm %1 has set the title to %2 %1 hat den Titel zu %2 geändert. %1 has joined the group %1 ist der Gruppe beigetreten %1 is now known as %2 %1 heißt ab sofort %2 %1 has left the group %1 hat die Gruppe verlassen %n user(s) in chat Number of users in chat %n Benutzer im Chat %n Benutzer im Chat mute stumm unmute Stummschaltung aufheben GroupInviteForm Groups Gruppen Create new group Neue Gruppe erstellen Group invites Gruppeneinladungen GroupInviteWidget Invited by %1 on %2 at %3. Eingeladen von %1 am %2 um %3. Join Beitreten Decline Ablehnen GroupWidget Set title... Namen festlegen … Open chat in new window Chat in neuem Fenster öffnen Remove chat from this window Chat aus diesem Fenster entfernen Quit group Menu to quit a groupchat Gruppe verlassen %n user(s) in chat Number of users in chat %n Benutzer im Chat %n Benutzer im Chat New Message Neue Nachricht Online Online IdentitySettings Public Information Öffentliche Informationen Tox ID Tox-ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Mit dieser Zeichenkette können dich andere Tox-Clients kontaktieren. Teile sie mit deinen Freunden, um mit ihnen zu chatten. Your Tox ID (click to copy) Deine Tox ID (Klicken zum Kopieren) Profile Profil Rename profile. tooltip for renaming profile button Profil umbenennen. Go back to the login screen tooltip for logout button Zurück zum Login-Fenster Logout import profile button Ausloggen Remove password Passwort entfernen Change password Passwort ändern This QR code contains your Tox ID. You may share this with your friends as well. Dieser QR-Code enthält deine Tox ID. So kannst du sie auch mit deinen Freunden teilen. Save image Bild speichern Copy image Bild kopieren Rename rename profile button Umbenennen Delete profile. delete profile button tooltip Profil löschen. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Speichert dein Tox Profil in eine Datei. Das Profil beinhaltet keine Gesprächsverläufe. Export export profile button Exportieren Delete delete profile button Löschen Server Server Hide my name from the public list Name nicht zur öffentlichen Liste hinzufügen Register Registrieren Your password Dein Passwort Update Aktualisieren Register on ToxMe Auf ToxMe registrieren Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Name für den ToxMe Service. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Etwas über dich. Oder deine Katze. Optional. Something about you. Or your cat. Tooltip for the Biography field. Optional. Etwas über dich. Oder deine Katze. ToxMe service to register on. ToxMe Dienst bei dem du dich registrieren willst. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Wenn nicht gesetzt, sind ToxMe Einträge öffentlich sichtbar. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Entferne dein Passwort und die Verschlüsselung von deinem Profil. Name input Namenseingabe Name visible to contacts Der für Kontakte sichtbare Name Status message input Eingabe für Statusmeldungen Status message visible to contacts Für deine Kontakte sichtbare Statusnachricht Your Tox ID Deine Tox ID Save QR image as file QR-Code als Bild speichern Copy QR image to clipboard QR-Code in Zwischenablage kopieren ToxMe username to be shown on ToxMe ToxMe Benutzername, sichtbar auf ToxMe Optional ToxMe biography to be shown on ToxMe Auf ToxMe angezeigte Biografie (optional) ToxMe service address Adresse des ToxMe Dienstes Visibility on the ToxMe service Sichtbarkeit auf ToxMe Password Passwort Update ToxMe entry ToxMe Eintrag aktualisieren Rename profile. Profil umbenennen. Delete profile. Profil löschen. Export profile Profil exportieren Remove password from profile Passwort des Profils löschen Change profile password Passwort des Profils ändern My name: Mein Name: My status: Mein Status: My username Mein Benutzername My biography Meine Biografie My profile Mein Profil LoadHistoryDialog Load History Dialog Gesprächsverlauf laden Load history Lade Verlauf from von to zu (about 100 messages are loaded) (es werden ca. 100 Nachrichten geladen) Select Date Dialog Datumsauswahl-Dialog Select a date Wählen Sie ein Datum LoginScreen Username: Benutzername: Password: Passwort: Confirm: Passwort bestätigen: Password strength: %p% Passwortstärke: %p % Create Profile Profil erstellen If the profile does not have a password, qTox can skip the login screen Für Profile ohne Passwort kann das Login-Fenster übersprungen werden Load automatically Automatisch laden Load Laden Load Profile Profil laden New Profile Neues Profil Couldn't create a new profile Es konnte kein neues Profil angelegt werden The username must not be empty. Der Benutzername darf nicht leer sein. The password must be at least 6 characters long. Das Passwort muss mindestens 6 Zeichen lang sein. The passwords you've entered are different. Please make sure to enter same password twice. Die Passwörter stimmen nicht überein. Bitte gib in beide Felder das gleiche Passwort ein. A profile with this name already exists. Ein Profil mit diesem Namen existiert bereits. Password protected profiles can't be automatically loaded. Passwortgeschützte Profile können nicht automatisch geladen werden. Couldn't load profile Profil konnte nicht geladen werden There is no selected profile. You may want to create one. Es wurde kein Profil ausgewählt. Du kannst aber eins erstellen. Couldn't load this profile Das Profil konnte nicht geladen werden This profile is already in use. Dieses Profil wird gerade verwendet. Wrong password. Falsches Passwort. Import Importieren Username input field Eingabefeld für den Benutzernamen Password input field, you can leave it empty (no password), or type at least 6 characters Eingabefeld für das Passwort. Du kannst es leer lassen oder ein Passwort mit mindestens 6 Zeichen eingeben Password confirmation field Feld um das Passwort zu bestätigen Create a new profile button Schaltfläche zum Erstellen eines neuen Profils Profile list Profilliste List of profiles Profilliste Password input Passworteingabe Load automatically checkbox Kontrollkästchen für automatisches Laden Import profile Profil importieren Load selected profile button Schaltfläche Ausgewähltes Profil laden New profile creation page Seite zum erstellen eines neuen Profils Loading existing profile page Lade bestehende Profilseite MainWindow Your name Dein Name Your status Dein Status ... Ausgelassen Add friends Freunde hinzufügen Create a group chat Einen Gruppenchat eröffnen View completed file transfers Dateiverläufe anzeigen Change your settings Einstellungen ändern Close Schließen Open profile Profil öffnen Open profile page when clicked Profilseite öffnen wenn angeklickt Status message input Eingabefeld für deine Statusnachricht Set your status message that will be shown to others Status-Nachricht eingeben, die bei Anderen angezeigt werden soll Status Status Set availability status Verfügbarkeitsstatus setzen Contact search Kontaktsuche Contact search input for known friends Suche nach bekannten Freunde Sorting and visibility Sortierung und Sichtbarkeit Set friends sorting and visibility Sortierung und Sichtbarkeit deiner Freunde einstellen Open Add friends page Seite „Freunde hinzufügen” öffnen Groupchat Gruppenchat Open groupchat management page Gruppenchat-Verwaltung öffnen File transfers history Dateiübertragungsverlauf Open File transfers history Öffne Dateitransferverlauf Settings Einstellungen Open Settings Einstellungen öffnen Nexus View OS X Menu bar Ansicht Window OS X Menu bar Fenster Minimize OS X Menu bar Minimieren Bring All to Front OS X Menu bar Alles in den Vordergrund bringen Exit Fullscreen Vollbildmodus verlassen Enter Fullscreen Vollbildmodus NotificationEdgeWidget Unread message(s) Ungelesene Nachricht Ungelesene Nachrichten PasswordEdit CAPS-LOCK ENABLED FESTSTELLTASTE AKTIVIERT PrivacyForm Privacy Privatsphäre Confirmation Bestätigung Do you want to permanently delete all chat history? Möchtest du den kompletten Gesprächsverlauf endgültig löschen? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Deine Kontakte können sehen, wenn du eine Nachricht tippst. Send typing notifications Schreibbenachrichtigungen senden Keep chat history Chatverlauf speichern NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam ist ein Teil deiner Tox ID. Wenn du mit Freundesanfragen überhäuft wirst, solltest du den NoSpam-Wert ändern. Deine jetzigen Freunde bleiben erhalten, aber mit deiner alten Tox ID kann dich dann niemand mehr hinzufügen. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam ist ein Teil deiner Tox ID. Wenn du mit Freundesanfragen überhäuft wirst, solltest du den NoSpam-Wert ändern. Deine jetzigen Freunde bleiben erhalten, aber mit deiner alten Tox ID kann dich dann niemand mehr hinzufügen. Generate random NoSpam Zufällige NoSpam-ID generieren Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Wenn aktiviert, wird der Gesprächsverlauf dauerhaft gesichert. Die Sicherung des Verlaufs ist noch in Entwicklung. Formatierungsänderungen beim Speichern sind möglich, die zu Datenverlust führen können. Privacy Datenschutz BlackList Schwarze Liste Filter group message by group member's public key. Put public key here, one per line. Gruppennachricht nach öffentlichen Schlüssel des Gruppenmitglieds filtern. Füge öffentlichen Schlüssel hier ein, ein Schlüssel pro Zeile. Profile Failed to derive key from password, the profile won't use the new password. Ableiten des Schlüssels vom Passwort fehlgeschlagen, das Profil wird das neue Passwort nicht verwenden. Couldn't change password on the database, it might be corrupted or use the old password. Datenbankänderung des Passwortes nicht möglich, es ist möglich, dass ein älteres Passwort verwendet wurde. Toxing on qTox Toxen mit qTox ProfileForm Choose a profile picture Wähle ein Profilbild Error Fehler Rename "%1" renaming a profile Profil '%1' umbenennen Unable to open this file. Datei kann nicht geöffnet werden. Current profile: Aktuelles Profil: Remove Löschen Unable to read this image. Bild kann nicht gelesen werden. The supplied image is too large. Please use another image. Das Bild ist zu groß. Bitte verwende ein anderes. Couldn't rename the profile to "%1" Profil konnte nicht in „%1“ umbenannt werden Location not writable Title of permissions popup Keine Schreibrechte für diesen Ordner You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Du scheinst nicht die nötigen Rechte zu haben, um hier eine Datei zu speichern. Wähle doch ein anderes Verzeichnis. Failed to copy file Kopieren fehlgeschlagen The file you chose could not be written to. In die gewählte Datei konnte leider nicht geschrieben werden. Really delete profile? deletion confirmation title Das Profil wirklich löschen? Nothing to remove Nichts zu entfernen Your profile does not have a password! Dein Profil hat kein Passwort! Really delete password? deletion confirmation title Password wirklich löschen? Please enter a new password. Bitte gib ein neues Passwort ein. Are you sure you want to delete this profile? deletion confirmation text Bist du sicher, dass das gewählte Profil gelöscht werden soll? Save save qr image Speichern Save QrCode (*.png) save dialog filter QR-Code speichern (*.png) Files could not be deleted! deletion failed title Dateien konnten nicht gelöscht werden! Register (processing) Registrieren (in Arbeit) Update (processing) Aktualisieren (in Arbeit) Done! Erledigt! Account %1@%2 updated successfully Account %1@%2 wurde erfolgreich aktualisiert Successfully added %1@%2 to the database. Save your password %1@%2 wurde erfolgreich der Datenbank hinzugefügt. Bitte Passwort speichern Toxme error ToxMe-Fehler Register Registrieren Update Aktualisieren Change password button text Passwort ändern Set profile password button text Profilpasswort festlegen Current profile location: %1 Aktuelles Profil in: %1 Couldn't change password Passwortänderung nicht möglich This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Diese Zeichenkette teilt anderen Tox-Clients mit, wie sie dich kontaktieren können. Teile sie mit deinen Freunden um zu kommunizieren. Diese ID beinhaltet den NoSpam-Code (in blau), und die Prüfsumme (in grau). Empty path is unavaliable Leerer Pfad ist nicht verfügbar Failed to rename Fehler beim Umbenennen Profile already exists Profil bereits vorhanden A profile named "%1" already exists. Ein Profil namens „%1“ existiert bereits. Empty name Leerer Name Empty name is unavaliable Leerer Name ist nicht verfügbar Empty path Leerer Pfad Couldn't change password on the database, it might be corrupted or use the old password. Das Passwort der Datenbank konnte nicht geändert werden, sie ist möglicherweise fehlerhaft oder verwendet das alte Passwort. Export profile Profil exportieren Tox save file (*.tox) save dialog filter Tox-Datei (*.tox) speichern The following files could not be deleted: deletion failed text part 1 Die folgenden Dateien konnten nicht gelöscht werden: Please manually remove them. deletion failed text part 2 Bitte manuell löschen. Are you sure you want to delete your password? deletion confirmation text Bist du sicher, dass du dein Passwort löschen möchtest? Images (%1) filetype filter Bilder (%1) ProfileImporter Import profile import dialog title Profil importieren Tox save file (*.tox) import dialog filter Tox-Datei (*.tox) Ignoring non-Tox file popup title Nicht-Tox-Datei ignoriert Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Warnung: Die von dir gewählte Datei ist keine Tox-Datei. Sie wird ignoriert. Profile already exists import confirm title Profil bereits vorhanden A profile named "%1" already exists. Do you want to erase it? import confirm text Ein Profil namens „%1“ existiert bereits. Möchtest du es überschreiben? File doesn't exist Datei existiert nicht Profile doesn't exist Profil existiert nicht Profile imported Profil importiert %1.tox was successfully imported Die Datei %1.tox wurde erfolgreich importiert QApplication Ok Ok Cancel Abbrechen Yes Ja No Nein LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout rechtsläufige Schreibrichtung QMessageBox Couldn't add friend Freund konnte nicht hinzugefügt werden %1 is not a valid Toxme address. %1 ist keine gültige Toxme-Adresse. You can't add yourself as a friend! When trying to add your own Tox ID as friend Du kannst dich nicht selbst als Freund hinzufügen! QObject Tox URI to parse Zu parsende Tox-URI Starts new instance and loads specified profile. Startet eine neue Instanz und lädt das angegebene Profil. profile Profil Default Standard Blue Blau Olive Oliv Red Rot Violet Violett Incoming call... Eingehender Anruf ... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Hier ist %1! Lust dich mit mir zu unterhalten? None No camera device set Keine Kamera ausgewählt Desktop Desktop as a camera input for screen sharing Bildschirm Server doesn't support Toxme Server unterstützt ToxMe nicht You're making too many requests. Wait an hour and try again Du machst zu viele Anfragen. Bitte versuche es in einer Stunde erneut. This name is already in use Dieser Name wird bereits verwendet This Tox ID is already registered under another name Diese Tox ID ist bereits unter einem anderen Namen registriert. Please don't use a space in your name Dein Name darf keine Leerzeichen enthalten. Password incorrect Passwort falsch You can't use this name Du kannst diesen Namen nicht verwenden. Name not found Name nicht gefunden Tox ID not sent Tox ID wurde nicht gesendet That user does not exist Dieser Benutzer existiert nicht. Error Fehler qTox couldn't open your chat logs, they will be disabled. qTox konnte Ihr Gesprächsprotokoll nicht öffnen. Das Speichern der Gespräche wird deaktiviert! Problem with HTTPS connection Problem mit der HTTPS-Verbindung Internal ToxMe error Interner ToxMe Fehler Reformatting text in progress.. Neuformatierung des Textes in Arbeit... Starts new instance and opens the login screen. Startet eine neue Instanz und öffnet den Anmeldebildschirm. Dark Dunkel Dark blue Dunkelblau Dark olive Dunkeloliv Dark red Dunkelrot Dark violet Dunkelviolett Failed to load profile automatically. Das automatische Laden des Profils ist fehlgeschlagen. online contact status Online away contact status abwesend busy contact status beschäftigt offline contact status offline blocked contact status blockiert RemoveFriendDialog Remove friend Kontakt löschen Also remove chat history Gesprächsverlauf ebenfalls löschen Remove Löschen Are you sure you want to remove %1 from your contacts list? Bist du dir sicher, dass du %1 aus deiner Kontaktliste entfernen möchtest? Remove all chat history with the friend if set Löscht den gesamten Chatverlauf mit diesem Freund, wenn aktiviert ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Klicke und ziehe, um einen Ausschnitt auszuwählen. Drücke %1, um das qTox-Fenster auszublenden/anzuzeigen oder drücke %2, um abzubrechen. Space [Space] key on the keyboard Leertaste Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Drücke %1, um einen Screenshot des ausgewählten Ausschnitts zu senden, %2, um das qTox-Fenster auszublenden/einzublenden oder %3, um abzubrechen. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Der Text konnte nicht gefunden werden. Start Start SearchSettingsForm Form Formular Start search: Suche starten: from the end vom Ende from the beginning vom Anfang after date nach dem Datum before date vor dem Datum 00.00.0000 00.00.0000 Case sensitive Groß-/Kleinschreibung beachten Whole words only Nur ganze Wörter Use regular expressions Reguläre Ausdrücke verwenden SetPasswordDialog Set your password Setze dein Passwort Confirm: Passwort bestätigen: Password: Passwort: Password strength: %p% Passwortstärke: %p % The password is too short Das Passwort ist zu kurz The password doesn't match. Die Passwörter stimmen nicht überein. Confirm password Passwort bestätigen Confirm password input Bestätige Passworteingabe Password input Passworteingabe Password input field, minimum 6 characters long Passworteingabefeld, mindestens 6 Zeichen lang Settings Circle #%1 Kreis #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Einen Kontakt hinzufügen Do you want to add %1 as a friend? Möchtest du %1 als Kontakt in deine Liste aufnehmen? User ID: ID: Friend request message: Freundschaftsanfrage: Send Send a friend request Senden Cancel Don't send a friend request Abbrechen UserInterfaceForm None Ohne User Interface Benutzeroberfläche UserInterfaceSettings Chat Chat Base font: Schriftart: px Px Size: Größe: New text styling preference may not load until qTox restarts. Änderungen am Aussehen des Textes werden eventuell erst nach einem Neustart von qTox angezeigt. Text Style format: Textformat: Select text styling preference. Wählen Sie die Textstil Einstellung. Plaintext Klartext Show formatting characters Formatierungszeichen anzeigen Don't show formatting characters Formatierungszeichen nicht anzeigen New message Neue Nachricht Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Öffne qTox bei neuen Nachrichten, falls noch kein Fenster geöffnet ist. Open window Fenster öffnen Contact list Kontaktliste If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Falls aktiviert, werden Gruppenchats an erster Stelle deiner Kontaktliste platziert, andernfalls werden sie unter deinen Kontakten platziert. Place groupchats at top of friend list Gruppenchats an erster Stelle der Kontaktliste anzeigen Your contact list will be shown in compact mode. toolTip for compact layout setting Die Kontaktliste wird kompakt dargestellt. Compact contact list Kompakte Kontaktliste Multiple windows mode Mehrfenster-Modus Open each chat in an individual window Jeden Chat in einem separaten Fenster öffnen Emoticons Emoticons Use emoticons Emoticons verwenden Smiley Pack: Text on smiley pack label Smileyart: Emoticon size: Emoticongröße: px Pixel Theme Farbschema Style: Stil: Theme color: Farbschema: Timestamp format: Zeitstempelformat: Date format: Datumsformat: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Wenn diese Option aktiviert ist, erhält jeder Kontakt ohne festgelegtem Avatar einen generierten Avatar basierend auf seiner Tox-ID anstelle eines Standardbildes. Neustart erforderlich. Use identicons instead of empty avatars Identitätssymbol statt leerer Avatare verwenden Use colored nicknames in chats Farbige Spitznamen in Chats verwenden Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Zeigt eine Benachrichtigung an, wenn Sie eine neue Nachricht erhalten und das Fenster nicht ausgewählt ist. Notify Benachrichtigen Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Nur über neue Nachrichten in Gruppenchats benachrichtigen , wenn sie erwähnt werden. Group chats only notify when mentioned Gruppen-Chats benachrichtigen nur, wenn sie erwähnt werden Play sound Ton abspielen Play sound while Busy Ton abspielen wenn „Beschäftigt” Notify via desktop notifications Über Desktop-Benachrichtigungen benachrichtigen Hide message sender and contents Absender und Nachrichteninhalte ausblenden Widget Online Button to set your status to 'Online' Online Away Button to set your status to 'Away' Abwesend Busy Button to set your status to 'Busy' Beschäftigt toxcore failed to start, the application will terminate after you close this message. Toxcore konnte nicht gestartet werden, die Anwendung wird beendet nachdem sie diese Nachricht geschlossen haben. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Es ist ein Fehler aufgetreten, und die Anwendung kann nicht gestartet werden. Leider führen deine Proxy-Einstellungen zu Problemen. Bitte ändere deine Einstellungen und versuch es erneut. File Datei Edit Profile Profil bearbeiten Change Status Status ändern Log out Ausloggen Edit Bearbeiten Logout Tray action menu to logout user Ausloggen Exit Tray action menu to exit tox Beenden Filter... Filter ... Contacts Kontakte Add Contact... Kontakt hinzufügen ... Next Conversation Nächste Unterhaltung Previous Conversation Vorherige Unterhaltung Executable file popup title Ausführbare Datei You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Du hast qTox aufgefordert, eine Datei auszuführen. Bitte beachte, dass ausführbare Dateien ein Sicherheitsrisiko darstellen können. Bist du dir sicher, dass du die Datei ausführen möchtest? Couldn't request friendship Freundschaftsanfrage konnte nicht gesendet werden Status Status Your name Dein Name Message failed to send Nachricht konnte nicht gesendet werden Create new group... Neue Gruppe erstellen ... Add new circle... Neuen Kreis hinzufügen ... %n New Friend Request(s) %n neue Freundschaftsanfrage %n neue Freundschaftsanfragen %n New Group Invite(s) %n neue Gruppeneinladung %n neue Gruppeneinladungen By Name Nach Namen By Activity Nach Aktivität All Alle Online Online Offline Ausgelassen Friends Freunde Groups Gruppen Search Contacts Kontakte durchsuchen Groupchat #%1 Gruppenchat #%1 Show Tray action menu to show qTox window Öffnen Add friend title of the window FreundIn hinzufügen Group invites title of the window Gruppeneinladungen File transfers title of the window Dateiübertragungen Settings title of the window Einstellungen My profile title of the window Mein Profil Failed to send file "%1" Datei „%1“ konnte nicht gesendet werden File sent Datei gesendet sent you a friend request. schickte Dir eine Freundschaftsanfrage. invites you to join a group. lädt Dich ein, einer Gruppe beizutreten. qTox/translations/el.ts000066400000000000000000003735521415623743500155330ustar00rootroot00000000000000 AVForm Default resolution Προεπιλεγμένη ανάλυση Audio/Video Ήχος/Βίντεο Disabled Απενεργοποιημένο Select region Επιλέξτε περιοχή Screen %1 Οθόνη %1 Audio Settings Ρυθμίσεις Ήχου Gain Απολαβή Playback device Συσκευή αναπαραγωγής Use slider to set volume of your speakers. Χρησιμοποιήστε το ρυθμιστικό για να αυξομειώσετε την ένταση των ηχείων σας. Capture device Συσκευή λήψης Volume Ένταση Video Settings Ρυθμίσεις βίντεο Video device Συσκευή βίντεο Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Ορίστε την ανάλυση της κάμερας σας. Όσο υψηλότερη η ανάλυση, τόσο καλύτερη η ποιότητα του βίντεο που θα βλέπουν οι φίλοι σας. Έχετε υπόψη όμως, ότι μια καλύτερη ποιότητα βίντεο χρειάζεται καλύτερη σύνδεση στο διαδίκτυο. Μερικές φορές η σύνδεση σας μπορεί να μην επαρκεί για να υποστηρίξει υψηλότερη ποιότητα βίντεο, κάτι το οποίο μπορεί να οδηγήσει σε προβλήματα με τις βιντεοκλήσεις. Resolution Ανάλυση Rescan devices Επανασάρωση συσκευών Test Sound Δοκιμή Ήχου Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Ενεργοποιεί την ακύρωση ηχούς (πειραματικό στάδιο), απαιτείται επανεκκίνηση του qTox . Enable experimental audio backend Ενεργοποιεί το backend ήχου (πειραματικό στάδιο) Audio quality Ποιότητα ήχου Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Ποιότητα μεταδιδόμενου ήχου. Μειώστε εάν το εύρος ζώνης δεν είναι αρκετό ή εάν θέλετε να ελαχιστοποιήσετε τη μεταφορά του όγκου των δεδομένων. High (64 kbps) Υψηλή (64 kbps) Medium (32 kbps) Μέτρια (32 kbps) Low (16 kbps) Χαμηλή (16 kbps) Very low (8 kbps) Πολύ χαμηλή (8 kbps) Threshold Όριο AboutForm About Σχετικά Original author: %1 Αρχικός δημιουργός: %1 You are using qTox version %1. Χρησιμοποιείτε την qTox έκδοση %1. Commit hash: %1 Διάπραξη hash: %1 toxcore version: %1 Έκδοση του toxcore: %1 Qt version: %1 Qt έκδοση: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Μια λίστα με όλα τα γνωστά θέματα μπορεί να βρεθεί στο %1 μας στο Github. Αν ανακαλύψετε κάποιο σφάλμα ή κενό ασφαλείας στο εσωτερικό του qTox, παρακαλούμε να το αναφέρετε σύμφωνα με τις οδηγίες στο % 2 του άρθρου wiki μας. Click here to report a bug. Κάντε κλικ εδώ για να αναφέρετε ένα σφάλμα. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Δείτε μια πλήρη λίστα απο %1 στο Github bug-tracker Replaces `%1` in the `A list of all known…` ανιχνευτής - σφαλμάτων Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Σύνταξη Χρήσιμης Αναφοράς Σφαλμάτων contributors Replaces `%1` in `See a full list of…` συντελεστές AboutFriendForm Dialog Παράθυρο διαλόγου username συνθηματικό χρήστη status message μήνυμα κατάστασης Used aliases: Χρησιμοποιούμενα ψευδώνυμα: HISTORY OF ALIASES ΙΣΤΟΡΙΚΟ ΨΕΥΔΩΝΥΜΩΝ Automatically accept files from contact if set Αυτόματη λήψη αρχείων από μια επαφή, εάν είναι επιλεγμένο Auto accept files Αυτόματη αποδοχή αρχείων Default directory to save files: Προεπιλεγμένος κατάλογος για την αποθήκευση αρχείων: Auto accept for this contact is disabled Η αυτόματη αποδοχή είναι απενεργοποιημένη για αυτήν την επαφή Auto accept call: Αυτόματη αποδοχή κλήσης: Manual Όχι αυτόματα Audio Ήχος Audio + Video Ήχος + Βίντεο Automatically accept group chat invitations from this contact if set. Αυτόματη αποδοχή προσκλήσεων ομαδικής συνομιλίας από αυτήν την επαφή, εάν είναι επιλεγμένο. Auto accept group invites Αυτόματη αποδοχή προσκλήσεων ομάδας Remove history (operation can not be undone!) Διαγραφή του ιστορικού (η ενέργεια αυτή δεν μπορεί να αναιρεθεί!) Notes Σημειώσεις Input field for notes about the contact Πεδίο εισαγωγής σημειώσεων σχετικών με την επαφή You can save comment about this contact here. Μπορείτε να αποθηκεύσετε ένα σχόλιο σχετικά με αυτήν την επαφή εδώ. History removed Το ιστορικό διεγράφη Choose an auto accept directory popup title Επιλέξτε κατάλογο για την αυτόματη αποδοχή <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Επιβεβαίωση Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Έκδοση License Άδεια Authors Δημιουργοί Known Issues Γνωστά Προβλήματα Open update download link Update available qTox is up to date ✓ AddFriendForm Couldn't add friend Αδυναμία προσθήκης φίλου/ης Add Friends Προσθέστε Φίλους Send friend request Στείλτε αίτημα φιλίας Invalid Tox ID format Μη έγκυρη μορφή Tox Ταυτότητας (ID) Add a friend Προσθέστε ένα φίλο Friend requests Αιτήματα φιλίας Accept Αποδοχή Reject Απόρριψη Tox ID, either 76 hexadecimal characters or name@example.com Τox Ταυτότητα (ID), είτε 76 δεκαεξαδικούς χαρακτήρες ή name@example.com Type in Tox ID of your friend Πληκτρολογήστε την Tox Ταυτότητα (ID) του φίλου σας Friend request message Μήνυμα αιτήματος φιλίας Type message to send with the friend request or leave empty to send a default message Πληκτρολογήστε ένα μήνυμα για να σταλεί με το αίτημα φιλίας ή αφήστε κενό για να στείλετε ένα προεπιλεγμένο μήνυμα %1 Tox ID is invalid or does not exist Toxme error Το %1 Tox ID δεν είναι έγκυρο ή δεν υπάρχει You can't add yourself as a friend! When trying to add your own Tox ID as friend Δεν μπορείτε να προσθέσετε τον εαυτό σας ως επαφή ! Open contact list Άνοιγμα της λίστας επαφών Couldn't open file Αδυναμία ανοίγματος του αρχείου Couldn't open the contact file Error message when trying to open a contact list file to import Αδυναμία ανοίγματος του αρχείου λίστας επαφών Invalid file Μη έγκυρο αρχείο We couldn't find any contacts to import in this file! Δεν βρέθηκαν επαφές προς εισαγωγή σε αυτό το αρχείο ! Tox ID Tox ID of the person you're sending a friend request to Tox Ταυτότητα (ID) either 76 hexadecimal characters or name@example.com Tox ID format description είτε 76 δεκαεξαδικοί χαρακτήρες ή name@example.com Message The message you send in friend requests Μήνυμα Open Button to choose a file with a list of contacts to import Άνοιγμα αρχείου Send friend requests Στείλτε αίτημα αποδοχής %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 εδώ! Θέλεις να συνομιλήσουμε στο Tox; Import a list of contacts, one Tox ID per line Εισαγωγή λίστας επαφών, ένα Tox ID ανά γραμμή Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Έτοιμοι προς εισαγωγή %n επαφής, πατήστε αποστολή προς επιβεβαίωση Έτοιμοι προς εισαγωγή %n επαφών, πατήστε αποστολή προς επιβεβαίωση Import contacts Εισαγωγή επαφών AdvancedForm Advanced Για προχωρημένους Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Εκτός και αν %1 ξέρετε τι κάνετε, παρακαλούμε να %2 αλλάξετε τίποτα εδώ. Αλλαγές εδώ μπορεί να οδηγήσουν σε προβλήματα με το qTox, ακόμη και σε απώλεια των δεδομένων σας, π.χ. ιστορικό. really στ' αλήθεια not μην IMPORTANT NOTE ΣΗΜΑΝΤΙΚΗ ΣΗΜΕΙΩΣΗ Reset settings Επαναφορά ρυθμίσεων All settings will be reset to default. Are you sure? Θα γίνει επαναφορά όλων των ρυθμίσεων στην προεπιλεγμένη τους θέση. Είστε σίγουρος/η; Yes Ναι No Όχι Call active popup title Ενεργή κλήση You can't disconnect while a call is active! popup text Δεν μπορείτε να αποσυνδεθείτε όταν μια κλήση είναι ενεργή! Save File Αποθήκευση Αρχείου Logs (*.log) Αρχεία καταγραφής (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Αποθήκευση των ρυθμίσεων στον τρέχoντα φάκελο καταλόγου αντί για τον συνηθισμένο φάκελο conf Make Tox portable Κάντε το Τοx φορητό Reset to default settings Επαναφορά στις προεπιλεγμένες ρυθμίσεις Portable Φορητό Connection Settings Ρυθμίσεις Σύνδεσης Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Ενεργοποίηση IPv6 (συνιστάται) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Απενεργοποιώντας αυτό επιτρέπετε τη χρήση του Tox μέσα από π.χ. το Tor. Ωστόσο, αυτό προσθέτει παραπάνω φορτίο στο δίκτυο του Tox, γι' αυτό απενεργοποιήστε το μόνο όταν είναι αναγκαίο. Enable UDP (recommended) Text on checkbox to disable UDP Ενεργοποίηση UDP (συνιστάται) Proxy type: Τύπος διακομιστή μεσολάβησης: Address: Text on proxy addr label Διεύθυνση: Port: Text on proxy port label Θύρα: None Κανένα SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Επανασύνδεση Debug Εντοπισμός σφαλμάτων Export Debug Log Εξαγωγή του αρχείου καταγραφής εντοπισμού σφαλμάτων Copy Debug Log Αντιγραφή του αρχείου καταγραφής εντοπισμού σφαλμάτων Enable LAN discovery ChatForm Send a file Αποστολή ενός αρχείου Unable to open Αδυναμία ανοίγματος qTox wasn't able to open %1 Το qTox δεν κατάφερε να ανοίξει το %1 Bad idea Κακή ιδέα %1 calling Κλήση από τον/ην %1 Failed to open temporary file Temporary file for screenshot Αποτυχία ανοίγματος προσωρινού αρχείου qTox wasn't able to save the screenshot Το qTox δεν κατάφερε να σώσει το στιγμιότυπο εικόνας Call with %1 ended. %2 Η κλήση με τον/ην %1 τερματίστηκε. %2 Call duration: Διάρκεια κλήσης: Calling %1 Κλήση %1 %1 is typing Ο/η %1 πληκτρολογεί Copy Αντιγραφή You're trying to send a sequential file, which is not going to work! Προσπαθείτε να στείλετε ένα σειριακό αρχείο, το οποίο δεν πρόκειται να δουλέψει! %1 is now %2 e.g. "Dubslow is now online" Ο/η %1 είναι τώρα %2 Call with %1 ended unexpectedly. %2 Η κλήση προς %1 τερματίσθηκε απροσδόκητα. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Αδυναμία έναρξης ηχητικής κλήσης Start audio call Έναρξη ηχητικής κλήσης End audio call Τερματισμός ηχητικής κλήσης Cancel audio call Ματαίωση ηχητικής κλήσης Accept audio call Αποδοχή ηχητικής κλήσης Can't start video call Αδυναμία έναρξης της βιντεοκλήσης Start video call Έναρξη βιντεοκλήσης End video call Τερματισμός βιντεοκλήσης Cancel video call Ματαίωση βιντεοκλήσης Accept video call Αποδοχή βιντεοκλήσης Sound can be disabled only during a call Ο ήχος μπορεί να απενεργοποιηθεί μόνο κατά τη διάρκεια μιας κλήσης Unmute call Κατάργηση σίγασης κλήσης Mute call Σίγαση κλήσης Microphone can be muted only during a call Το μικρόφωνο μπορεί να απενεργοποιηθεί (κατάσταση σίγασης) μόνο κατά τη διάρκεια μιας κλήσης Unmute microphone Ενεργοποίηση (κατάργηση σίγασης) μικροφώνου Mute microphone Απενεργοποίηση (σίγαση) μικροφώνου ChatLog pending σε εκκρεμότητα Copy Αντιγραφή Select all Επιλογή όλων ChatTextEdit Type your message here... Πληκτρολογήστε το μήνυμα σας εδώ... CircleWidget Rename circle Menu for renaming a circle Μετονομασία κύκλου Remove circle Menu for removing a circle Κατάργηση κύκλου Open all in new window Άνοιγμα όλων σε νέο παράθυρο Core /me offers friendship, "%1" /me προσφέρει φιλία, "%1" Invalid Tox ID Error while sending friendship request Μη έγκυρη Tox ταυτότητα (ID) You need to write a message with your request Error while sending friendship request Χρειάζεται να γράψετε ένα μήνυμα με το αίτημα σας Your message is too long! Error while sending friendship request Το μήνυμα σας είναι πολύ μεγάλο! Friend is already added Error while sending friendship request Ο/η φίλος/η έχει ήδη προστεθεί Groupchat %1 DesktopNotify New message Νέο μήνυμα Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Φόρμα 10Mb 10Mβ 0kb/s 0κβ/δ ETA:10:10 ΕΧΜ:10:10 Filename Όνομα αρχείου Waiting to send... file transfer widget Αναμονή για αποστολή... Accept to receive this file file transfer widget Αποδοχή λήψης του αρχείου Location not writable Title of permissions popup Η τοποθεσία δεν είναι εγγράψιμη You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Δεν έχετε άδεια να γράψετε σε αυτή την θέση. Επιλέξτε μία άλλη, ή ακυρώστε την αποθήκευση. Paused file transfer widget Σε παύση Resuming... file transfer widget Συνεχίζεται... Open file Άνοιγμα αρχείου Open file directory Άνοιγμα του αρχείου καταλόγου Pause transfer Παύση μεταφοράς Cancel transfer Ακύρωση μεταφοράς Resume transfer Συνέχιση μεταφοράς Accept transfer Αποδοχή μεταφοράς Save a file Title of the file saving dialog Αποθήκευση ενός αρχείου Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Αρχεία που μεταφέρθηκαν Downloads Λήψεις Uploads Μεταφορτώσεις FriendListWidget Today Σήμερα Yesterday Εχθές Last 7 days Τελευταίες 7 ημέρες This month Αυτόν το μήνα Older than 6 Months Παλαιότερα από 6 μήνες Never Ποτέ FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Αίτημα φιλίας Someone wants to make friends with you Κάποιος/α θέλει να γίνει φίλος/η σας User ID: Ταυτότητα χρήστη: Friend request message: Μήνυμα αιτήματος φιλίας: Accept Accept a friend request Αποδοχή Reject Reject a friend request Απόρριψη FriendWidget Open chat in new window Άνοιγμα συνομιλίας σε νέο παράθυρο Remove chat from this window Αφαίρεση συνομιλίας απ' αυτό το παράθυρο Invite to group Menu to invite a friend to a groupchat Πρόσκληση στην ομάδα Move to circle... Menu to move a friend into a different circle Μετακίνηση στον κύκλο... To new circle Σε νέο κύκλο Remove from circle '%1' Αφαίρεση από τον κύκλο '%1' Move to circle "%1" Μετακίνηση στον κύκλο "%1" Set alias... Επιλογή ψευδωνύμου... Auto accept files from this friend context menu entry Αυτόματη αποδοχή αρχείων από αυτόν/η το φίλο/η Remove friend Menu to remove the friend from our friendlist Κατάργηση φίλου Choose an auto accept directory popup title Επιλέξτε έναν φάκελο κατάλογου για αυτόματη αποδοχή New message Νέο μήνυμα Online Συνδεδεμένος/η Away Απών Busy Απασχολημένος/η Offline Εκτός σύνδεσης To new group Σε νέα ομάδα Invite to group '%1' Πρόσκληση στην ομάδα '%1' Show details Εμφάνιση λεπτομερειών GeneralForm Choose an auto accept directory popup title Επιλέξτε έναν φάκελο αυτόματης αποδοχής General Γενικά GeneralSettings General Settings Γενικές Ρυθμίσεις The translation may not load until qTox restarts. Η μετάφραση ενδέχεται να μην φορτωθεί μέχρι να γίνει επανεκκίνηση του qTox. Language: Γλώσσα: Start qTox on operating system startup (current profile). Εκκίνηση του qTox κατά την εκκίνηση του λειτουργικού συστήματος (τρέχον προφίλ). Autostart Αυτόματη εκκίνηση Enable light tray icon. toolTip for light icon setting Ενεργοποίηση φωτισμού περιοχής κατάστασης. Light icon Φωτισμός εικονιδίου Show system tray icon Εμφάνιση εικονίδιου περιοχής ειδοποιήσεων συστήματος qTox will start minimized in tray. toolTip for Start in tray setting Το qTox θα εκκινηθεί ελαχιστοποιημένο στη περιοχή κατάστασης. Start in tray Εκκίνηση στη περιοχή κατάστασης After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Κάνοντας κλικ στην ελαχιστοποιήση (_) το qTox θα ελαχιστοποιηθεί στα κρυφά εικονίδια, αντί στη γραμμή εργασιών του συστήματος. Minimize to tray Ελαχιστοποίηση στη περιοχή κατάστασης After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Κάνοντας κλικ στο κλείσιμο (Χ) το qTox θα ελαχιστοποιηθεί στα κρυφά εικονίδια του πλαισίου συστήματος, αντί να κλείσει. Close to tray Κλείσιμο στη περιοχή κατάστασης Your status is changed to Away after set period of inactivity. Η κατάσταση σας αλλάζει σε "Απών" μετά απ' το καθορισμένο χρονικό διάστημα αδράνειας. Auto away after (0 to disable): Αυτόματη αλλαγή κατάστασης σε "Απών" μετά από (0 για απενεργοποίηση): Set to 0 to disable Ορίστε στο 0 για να το απενεργοποιήσετε Set where files will be saved. Ορίστε που θα αποθηκεύονται τα αρχεία. Default directory to save files: Προεπιλεγμένος κατάλογος για την αποθήκευση αρχείων: You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Μπορείτε να το ρυθμίσετε ανά-φίλο κάνοντας δεξί κλικ σε αυτούς. Autoaccept files Αυτόματη αποδοχή αρχείων Show contacts' status changes Προβολή αλλαγών της κατάστασης των επαφών Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Save chat log Αποθήκευση αρχείου καταγραφής της συνομιλίας Cleared Εκκαθαρίστηκε Send message Στείλτε μήνυμα Smileys Χαμογελ. προσωπάκια Send file(s) Στείλτε αρχείο(α) Send a screenshot Στείλτε ένα στιγμιότυπο εικόνας Clear displayed messages Καθαρισμός προβεβλημένων μηνυμάτων Quote selected text Παράθεση επιλεγμένου κειμένου Copy link address Αντιγραφή διεύθυνσης συνδέσμου Confirmation Επιβεβαίωση You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Φόρτωση ιστορικού συνομιλίας... Export to file Εξαγωγή σε αρχείο GenericNetCamView Tox video Tox βίντεο Show Messages Εμφάνιση Μηνυμάτων Hide Messages Απόκρυψη Μηνυμάτων Full Screen Toggle video preview Mute audio Mute microphone Απενεργοποίηση (σίγαση) μικροφώνου End video call Τερματισμός βιντεοκλήσης Exit full screen GroupChatForm %1 has set the title to %2 Ο/η %1 όρισε το θέμα σε %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Ομάδες Create new group Δημιουργία νέας ομάδας Group invites Ομαδικές προσκλήσεις GroupInviteWidget Invited by %1 on %2 at %3. Προσκεκλημένος από% 1% 2 σε% 3. Join Συμμετοχή Decline Απόρριψη GroupWidget Open chat in new window Άνοιγμα συνομιλίας σε νέο παράθυρο Remove chat from this window Αφαίρεση συνομιλίας απ' αυτό το παράθυρο Set title... Ορίστε τίτλο... Quit group Menu to quit a groupchat Εγκαταλείψτε την ομάδα %n user(s) in chat Number of users in chat New Message Online Συνδεδεμένος/η IdentitySettings Public Information Δημόσιες πληροφορίες Tox ID Tox Ταυτότητα (ID) This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Αυτή η δέσμη των χαρακτήρων λέει σε άλλα προγράμματα-πελάτες Tox πώς να επικοινωνήσουν μαζί σας. Μοιραστείτε το με τους φίλους σας για να επικοινωνήστε. Your Tox ID (click to copy) Το Tox ID σας (κάντε κλικ για να το αντιγράψετε) This QR code contains your Tox ID. You may share this with your friends as well. Αυτός ο κωδικός QR περιέχει το Tox ID σας. Μπορείτε να το μοιραστείτε και με τους φίλους σας. Save image Αποθήκευση εικόνας Copy image Αντιγραφή εικόνας Profile Προφίλ Rename profile. tooltip for renaming profile button Μετονομασία προφίλ. Rename rename profile button Μετονομασία Delete profile. delete profile button tooltip Διαγραφή προφίλ. Delete delete profile button Διαγραφή Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Σας επιτρέπει να εξάγετε το Tox προφίλ σας σε ένα αρχείο. Το προφίλ δεν περιέχει το ιστορικό σας. Export export profile button Εξαγωγή Go back to the login screen tooltip for logout button Επιστρέψτε στην οθόνη σύνδεσης Logout import profile button Αποσύνδεση Remove password Αφαίρεση του κωδικού πρόσβασης Change password Αλλαγή κωδικού πρόσβασης Server Διακομιστής Hide my name from the public list Απόκρυψη του ονόματος μου από τη δημόσια λίστα Register Εγγραφή Your password Ο κωδικός πρόσβασης σας Update Ενημέρωση Register on ToxMe Εγγραφείτε στο ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Όνομα για την ΤοxMe υπηρεσία. Optional. Something about you. Or your cat. Tooltip for the Biography text. Προαιρετικό. Κάτι για εσάς. Ή τη γάτα σας. Optional. Something about you. Or your cat. Tooltip for the Biography field. Προαιρετικό. Κάτι για εσάς. Ή τη γάτα σας. ToxMe service to register on. υπηρεσία ΤoxMe για να να εγγραφείτε. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Οι ToxMe καταχωρήσεις είναι δημοσίως ορατές, εάν δεν οριστούν. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Κατάργηση κωδικού πρόσβασης και κρυπτογράφησης από το προφίλ σας. Name input Όνομα εισόδου Name visible to contacts Όνομα ορατό στις επαφές Status message input Εισαγωγή μηνύματος κατάστασης Status message visible to contacts Μήνυμα κατάστασης ορατό στις επαφές Your Tox ID Η Τοx Ταυτότητα (ID) σας Save QR image as file Αποθήκευση QR εικόνας ως αρχείο Copy QR image to clipboard Αντιγραφή QR εικόνας στο πρόχειρο ToxMe username to be shown on ToxMe ToxMe όνομα χρήστη που θα εμφανίζεται στην ToxMe Optional ToxMe biography to be shown on ToxMe Προαιρετικό ToxMe βιογραφικό να εμφανίζεται ToxMe ToxMe service address Διεύθυνση υπηρεσίας ToxMe Visibility on the ToxMe service Ορατότητα στην υπηρεσία ΤoxMe Password Κωδικός πρόσβασης Update ToxMe entry Ενημέρωση ToxMe εισόδου Rename profile. Μετονομασία προφίλ. Delete profile. Διαγραφή προφίλ. Export profile Εξαγωγή προφίλ Remove password from profile Καταργήστε τον κωδικό πρόσβασης από το προφίλ Change profile password Αλλαγή κωδικού πρόσβασης προφίλ My name: Το όνομα μου: My status: Η κατάσταση μου: My username Το όνομα χρήστη μου My biography Η βιογραφία μου My profile Το προφίλ μου LoadHistoryDialog Load History Dialog Φόρτωση Ιστορικού Διαλόγου Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Όνομα χρήστη: Password: Κωδικός πρόσβασης: Confirm: Επιβεβαίωση: Password strength: %p% Ισχύς κωδικού πρόσβασης: %p% Create Profile Δημιουργία Προφίλ If the profile does not have a password, qTox can skip the login screen Εάν το προφίλ δεν έχει κωδικό πρόσβασης, το qTox μπορεί να παραλείψει την οθόνη σύνδεσης Load automatically Αυτόματη φόρτωση Load Φόρτωση New Profile Νέο Προφίλ Load Profile Φόρτωση Προφίλ Couldn't create a new profile Αδυναμία δημιουργίας νέου προφίλ The username must not be empty. Το όνομα χρήστη δεν πρέπει να είναι κενό. The password must be at least 6 characters long. Ο κωδικός πρόσβασης πρέπει να είναι τουλάχιστον 6 χαρακτήρες. The passwords you've entered are different. Please make sure to enter same password twice. Οι κωδικοί πρόσβασης που καταχωρήσατε είναι διαφορετικοί. Παρακαλώ βεβαιωθείτε ότι εισάγετε τον ίδιο κωδικό και τις δύο φορές. A profile with this name already exists. Υπάρχει ήδη ένα προφίλ με αυτό το όνομα. Couldn't load this profile Αδυναμία φόρτωσης αυτού του προφίλ This profile is already in use. Αυτό το προφίλ είναι ήδη σε χρήση. Wrong password. Λάθος κωδικός πρόσβασης. Import Εισαγωγή Password protected profiles can't be automatically loaded. Τα προφίλ που προστατεύονται με κωδικό πρόσβασης δεν μπορούν να φορτώσουν αυτόματα. Couldn't load profile Αδυναμία φόρτωσης προφίλ There is no selected profile. You may want to create one. Δεν υπάρχει επιλεγμένο προφίλ. Ίσως θέλετε να δημιουργήσετε ένα. Username input field Πεδίο εισαγωγής ονόματος χρήστη Password input field, you can leave it empty (no password), or type at least 6 characters Πεδίο εισαγωγής κωδικού πρόσβασης, μπορείτε να το αφήσετε κενό (χωρίς κωδικό πρόσβασης), ή πληκτρολογήστε τουλάχιστον 6 χαρακτήρες Password confirmation field Πεδίο επιβεβαίωσης κωδικού Create a new profile button Δημιουργήστε ένα νέο κουμπί προφίλ Profile list Λίστα προφίλ List of profiles Λίστα των προφίλ Password input Εισαγωγή κωδικού πρόσβασης Load automatically checkbox Αυτόματη φόρτωση πλαισίου ελέγχου Import profile Εισαγωγή προφίλ Load selected profile button Φόρτωση επιλεγμένου κουμπιού προφίλ New profile creation page Δημιουργία νέας σελίδας προφίλ Loading existing profile page Φόρτωση υπάρχουσας σελίδας προφίλ MainWindow ... ... Add friends Προσθήκη φίλων Create a group chat Δημιουργήστε μια ομάδα συνομιλίας View completed file transfers Προβολή ολοκληρωμένων μεταφορών αρχείων Change your settings Αλλαγή των ρυθμίσεων Close Κλείσιμο Your name Το όνομα σας Your status Η κατάσταση σας Open profile Άνοιγμα προφίλ Open profile page when clicked Άνοιγμα της σελίδας προφίλ όταν πατηθεί Status message input Εισαγωγή μηνύματος κατάστασης Set your status message that will be shown to others Ορίστε το μήνυμα κατάστασης σας που θα εμφανίζεται στους άλλους Status Κατάσταση Set availability status Ορισμός κατάστασης διαθεσιμότητας Contact search Αναζήτηση επαφών Contact search input for known friends Αναζήτηση επαφών εισόδου για γνωστούς φίλους Sorting and visibility Ταξινόμηση και προβολή Set friends sorting and visibility Ορίστε τους φίλους με ταξινόμηση και προβολή Open Add friends page Άνοιγμα σελίδας Προσθήκης φίλων Groupchat Ομαδική συνομιλία Open groupchat management page Άνοιγμα σελίδας διαχείρισης ομαδικής συνομιλίας File transfers history Ιστορικό αρχείου μεταφορών Open File transfers history Άνοιγμα ιστορικού Αρχείου μεταφορών Settings Ρυθμίσεις Open Settings Άνοιγμα Ρυθμίσεων Nexus View OS X Menu bar Προβολή Window OS X Menu bar Παράθυρο Minimize OS X Menu bar Ελαχιστοποίηση Bring All to Front OS X Menu bar Μεταφορά Όλων στο Προσκήνιο Exit Fullscreen Έξοδος από Πλήρη οθόνη Enter Fullscreen Είσοδος σε Πλήρη οθόνη NotificationEdgeWidget Unread message(s) Μη αναγνωσμένο μήνυμα Μη αναγνωσμένα μηνύματα PasswordEdit CAPS-LOCK ENABLED CAPS LOCK ΕΝΕΡΓΟΠΟΙΗΜΕΝΟ PrivacyForm Confirmation Επιβεβαίωση Do you want to permanently delete all chat history? Θέλετε να διαγράψετε μόνιμα όλο το ιστορικό συνομιλίας; Privacy Προστασία προσωπικών δεδομένων PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Οι φίλοι σας θα μπορούν να δουν όταν πληκτρολογείτε. Send typing notifications Αποστολή ειδοποιήσεων πληκτρολόγησης Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Η διατήρηση του ιστορικού συνομιλίας είναι ακόμα σε ανάπτυξη. Μπορεί να υπάρξουν αλλαγές στην μορφή αποθήκευσης, οι οποίες μπορεί να οδηγήσουν σε απώλεια δεδομένων. Keep chat history Διατήρηση ιστορικού συνομιλίας NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam Το NoSpam είναι μέρος της Tox ταυτότητας (ID) σας. Αν σας βομβαρδίζουν με αιτήματα φιλίας, καλό θα ήταν να αλλάξετε το NoSpam σας. Δεν θα μπορούν να σας προσθέσουν με το παλιό σας ID, αλλά θα κρατήσετε τους υπάρχοντες φίλους σας. NoSpam Ανεπιθύμητη επικοινωνία (NoSpam) NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Το NoSpam είναι μέρος της ταυτότητας του ID σας και μπορείτε να το αλλάξετε κατά βούληση. Αν σας βομβαρδίζουν με αιτήματα φιλίας, αλλάξτε το NoSpam. Generate random NoSpam Δημιουργία τυχαίου NoSpam Privacy Ιδιωτικό απόρρητο BlackList Λίστα ανεπιθύμητων (Black list) Filter group message by group member's public key. Put public key here, one per line. Ταξινόμηση μηνυμάτων ομάδας βάσει του δημοσίου κλειδιού του μέλους. Βάλτε το δημόσιο κλειδί εδώ, ένα ανά γραμμή. Profile Failed to derive key from password, the profile won't use the new password. Απέτυχε η εξαγωγή κλειδιού από τον κωδικό πρόσβασης, το προφίλ δεν θα χρησιμοποιήσει τον νέο κωδικό πρόσβασης. Couldn't change password on the database, it might be corrupted or use the old password. Δεν ήταν δυνατή η αλλαγή του κωδικού πρόσβασης στη βάση δεδομένων, ενδέχεται να είναι κατεστραμμένο ή να χρησιμοποιεί τον παλιό κωδικό πρόσβασης. Toxing on qTox Παρουσία στο qTox ProfileForm Current profile: Τρέχον προφίλ: Remove Κατάργηση Error Σφάλμα Choose a profile picture Επιλέξτε μια εικόνα προφίλ Unable to open this file. Αδύνατο το άνοιγμα αυτού του αρχείου. Unable to read this image. Αδύνατη η ανάγνωση αυτής της εικόνας. The supplied image is too large. Please use another image. Η επιλεγμένη εικόνα είναι πολύ μεγάλη. Παρακαλώ χρησιμοποιήστε μια άλλη εικόνα. Rename "%1" renaming a profile Μετονομασία "%1" Couldn't rename the profile to "%1" Αδυναμία μετονομασίας του προφίλ σε "%1" Location not writable Title of permissions popup Η τοποθεσία δεν είναι εγγράψιμη You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Δεν έχετε άδεια να γράψετε σε αυτήν τη τοποθεσία. Επιλέξτε μια άλλη ή ακυρώστε την αποθήκευση. Failed to copy file Απέτυχε η αντιγραφή αρχείου The file you chose could not be written to. Δεν ήταν δυνατή η εγγραφή στο αρχείο που επιλέξατε. Really delete profile? deletion confirmation title Θέλετε πραγματικά να διαγράψετε το προφίλ; Are you sure you want to delete this profile? deletion confirmation text Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το προφίλ; Save save qr image Αποθήκευση Save QrCode (*.png) save dialog filter Αποθήκευση του QrCode (*.png) Nothing to remove Τίποτα προς αφαίρεση Your profile does not have a password! Το προφίλ σας δεν έχει κωδικό πρόσβασης! Really delete password? deletion confirmation title Θέλετε πραγματικά να διαγράψετε τον κωδικό πρόσβασης; Please enter a new password. Παρακαλώ εισάγετε έναν νέο κωδικό πρόσβασης. Files could not be deleted! deletion failed title Δεν ήταν δυνατή η διαγραφή αρχείων! Register (processing) Εγγραφή (επεξεργασία) Update (processing) Ενημέρωση (επεξεργασία) Done! Έγινε! Account %1@%2 updated successfully Ο λογαριασμός %1@%2 ενημερώθηκε με επιτυχία Successfully added %1@%2 to the database. Save your password Επιτυχής προσθήκη %1@%2 στη βάση δεδομένων. Αποθηκεύστε τον κωδικό πρόσβασης σας Toxme error Σφάλμα Toxme Register Εγγραφή Update Ενημέρωση Change password button text Αλλαγή κωδικού πρόσβασης Set profile password button text Ορισμός κωδικού πρόσβασης προφίλ Current profile location: %1 Τρέχουσα τοποθεσία προφίλ: %1 Couldn't change password Αδύνατη αλλαγή του κωδικού πρόσβασης This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Αυτή η ακολουθία χαρακτήρων βοηθά άλλους χρήστες Tox να έρθουν σε επαφή μαζί σας. Κοινοποιήστε την στους φίλους σας για να επικοινωνήσετε. Το αναγνωριστικό ID περιλαμβάνει τον κωδικό AntiSpam (μπλέ), και τον κωδικό επαλήθευσης (checksum) (γκρι). Empty path is unavaliable Κενή διαδρομή μη διαθέσιμη Failed to rename Αποτυχία μετονομασίας Profile already exists Αυτό το προφίλ υπάρχει ήδη A profile named "%1" already exists. Υπάρχει ήδη ένα προφίλ με το όνομα "%1" . Empty name Κενό όνομα Empty name is unavaliable Κενό όνομα μη διαθέσιμο Empty path Κενή διαδρομή Couldn't change password on the database, it might be corrupted or use the old password. Δεν ήταν δυνατή η αλλαγή του κωδικού πρόσβασης στη βάση δεδομένων, ενδέχεται να είναι κατεστραμμένη ή να χρησιμοποιεί τον παλιό κωδικό πρόσβασης. Export profile Εξαγωγή προφίλ Tox save file (*.tox) save dialog filter Αποθήκευση αρχείου Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Τα ακόλουθα αρχεία δεν μπορούν να διαγραφούν: Please manually remove them. deletion failed text part 2 Παρακαλώ διαγράψτε τα χειροκίνητα. Are you sure you want to delete your password? deletion confirmation text Είστε βέβαιοι ότι θέλετε να διαγράψετε τον κωδικό πρόσβασής σας; Images (%1) filetype filter Εικόνες (%1) ProfileImporter Import profile import dialog title Εισαγωγή προφίλ Tox save file (*.tox) import dialog filter Αποθήκευση αρχείου Tox (*.tox) Ignoring non-Tox file popup title Αγνόηση μη-Tox αρχείου Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Προειδοποίηση: Έχετε επιλέξει ένα αρχείο που δεν είναι αποθηκευτικό αρχείο Tox. Αγνοήστε το. Profile already exists import confirm title Το προφίλ υπάρχει ήδη A profile named "%1" already exists. Do you want to erase it? import confirm text Υπάρχει ήδη ένα προφίλ με όνομα "%1". Θέλετε να σβηστεί; File doesn't exist Το αρχείο δεν υπάρχει Profile doesn't exist Το προφίλ δεν υπάρχει Profile imported Το προφίλ εισήχθη %1.tox was successfully imported Το %1.tox εισάχθηκε επιτυχώς QApplication Ok Εντάξει Cancel Ακύρωση Yes Ναι No Όχι LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Αριστερά προς τα δεξιά (ΑΠΔ) QMessageBox Couldn't add friend Αδύνατη προσθήκης φίλου/ης %1 is not a valid Toxme address. %1 δεν είναι έγκυρη διεύθυνση Toxme. You can't add yourself as a friend! When trying to add your own Tox ID as friend Δεν μπορείτε να προσθέσετε τον εαυτό σας ως φίλο! QObject Tox URI to parse Tox URI για ανάλυση Starts new instance and loads specified profile. Ξεκινά νέο συμβάν και φορτώνει το επιλεγμένο προφίλ. profile προφίλ %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 εδώ! Θέλεις να μιλήσουμε στο Tox; None No camera device set Κανένα Desktop Desktop as a camera input for screen sharing Επιφάνεια εργασίας Default Προεπιλεγμένο Blue Μπλε Olive Λαδί Red Κόκκινο Violet Βιολετί Incoming call... Εισερχόμενη κλήση... Server doesn't support Toxme Ο διακομιστής δεν υποστηρίζει το Toxme You're making too many requests. Wait an hour and try again Κάνετε πάρα πολλές αιτήσεις. Αναμένετε μία ώρα και προσπαθήστε ξανά This name is already in use Αυτό το όνομα χρησιμοποιείται ήδη This Tox ID is already registered under another name Αυτή η Tox Ταυτότητα (ID) έχει ήδη καταχωρηθεί με άλλο όνομα Please don't use a space in your name Παρακαλώ μην χρησιμοποιείτε κενό διάστημα στο όνομα σας Password incorrect Λάθος κωδικός πρόσβασης You can't use this name Δεν μπορείτε να χρησιμοποιήσετε αυτό το όνομα Name not found Το όνομα δεν βρέθηκε Tox ID not sent Η Tox Ταυτότητα (ID) δεν αποστάλθηκε That user does not exist Αυτός ο χρήστης δεν υπάρχει Error Σφάλμα qTox couldn't open your chat logs, they will be disabled. Το qTox δεν μπόρεσε να ανοίξει τα αρχεία καταγραφής των συνομιλιών σας, γι' αυτό θα απενεργοποιηθούν. Problem with HTTPS connection Πρόβλημα με τη σύνδεση HTTPS Internal ToxMe error Εσωτερικό σφάλμα ToxMe Reformatting text in progress.. Επαναδιαμόρφωση κειμένου σε εξέλιξη .. Starts new instance and opens the login screen. Εκκινεί μία νέα συνεδρία και ανοίγει το παράθυρο διαλόγου σύνδεσης. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status Συνδεδεμένος/η away contact status απών busy contact status Απασχολημένος/η offline contact status εκτός σύνδεσης blocked contact status RemoveFriendDialog Remove friend Κατάργηση φίλου Also remove chat history Επίσης να καταργηθεί το ιστορικό συνομιλιών Remove Κατάργηση Are you sure you want to remove %1 from your contacts list? Είστε βέβαιοι ότι θέλετε να καταργήσετε τον/ην %1 από τη λίστα επαφών σας; Remove all chat history with the friend if set Αφαιρέστε όλο το ιστορικό συνομιλίας με το φίλο, εάν είναι ορισμένο ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Κάντε κλικ και σύρετε για να επιλέξτε μια περιοχή. Πατήστε %1 για να εμφανίστε/αποκρύψτε το παραθύρο qTox ή %2 για ακύρωση. Space [Space] key on the keyboard Κενό Escape [Escape] key on the keyboard Διαφυγή (Escape) Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Πατήστε %1 για να στείλετε ένα στιγμιότυπο εικόνας της επιλεγμένης περιοχής, %2 για να εμφανίστε/αποκρύψτε το παράθυρο qTox, ή %3 για ακύρωση. Enter [Enter] key on the keyboard Εισαγωγή (Enter) SearchForm The text could not be found. Start SearchSettingsForm Form Φόρμα Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Ορίστε τον κωδικό πρόσβασής σας Confirm: Επιβεβαίωση: Password: Κωδικός πρόσβασης: Password strength: %p% Ισχύς κωδικού πρόσβασης: %p% The password is too short Ο κωδικός πρόσβασης είναι πολύ μικρός The password doesn't match. Ο κωδικός πρόσβασης δεν ταιριάζει. Confirm password Επιβεβαίωση κωδικού πρόσβασης Confirm password input Επιβεβαιώστε την εισαγωγή κωδικού πρόσβασης Password input Εισαγωγή κωδικού πρόσβασης Password input field, minimum 6 characters long Πεδίο εισαγωγής κωδικού πρόσβασης, ελάχιστο μήκος 6 χαρακτήρες Settings Circle #%1 Κύκλος #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Προσθέστε ένα φίλο Do you want to add %1 as a friend? Θέλετε να προσθέσετε τον/ην %1 ως φίλο; User ID: Ταυτότητα χρήστη (ID): Friend request message: Μήνυμα αιτήματος φιλίας: Send Send a friend request Αποστολή Cancel Don't send a friend request Ακύρωση UserInterfaceForm None Κανένα User Interface Διεπαφή χρήστη UserInterfaceSettings Chat Συνομιλία Base font: Βασική γραμματοσειρά: px εικονοστοιχεία Size: Μέγεθος: New text styling preference may not load until qTox restarts. Η νέα προτίμηση του στυλ κειμένου ενδέχεται να μην φορτωθεί μέχρι να γίνει επανεκκίνηση του qTox. Text Style format: Είδος μορφής κειμένου: Select text styling preference. Επιλέξτε προτιμώμενο είδος κειμένου. Plaintext Απλό κείμενο Show formatting characters Εμφάνιση χαρακτήρων μορφοποίησης Don't show formatting characters Να μην εμφανίζονται οι χαρακτήρες μορφοποίησης New message Νέο μήνυμα Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Ανοίξτε το παράθυρο του qTox όταν λαμβάνετε ένα νέο μήνυμα και κανένα παράθυρο δεν θα είναι ανοιχτό ακόμα. Open window Ανοιχτό παράθυρο Contact list Λίστα επαφών If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Εάν επιλεχθούν, οι ομαδικές συνομιλίες θα τοποθετούνται στην κορυφή της λίστας φίλων, διαφορετικά θα τοποθετούνται κάτω από τους συνδεδεμένους φίλους. Place groupchats at top of friend list Τοποθέτηση των ομαδικών συνομιλιών στην κορυφή της λίστας φίλων Your contact list will be shown in compact mode. toolTip for compact layout setting Η λίστα επαφών σας θα εμφανίζεται σε συμπαγή λειτουργία. Compact contact list Συμπαγής λίστα επαφών Multiple windows mode Λειτουργία πολλαπλών παραθύρων Open each chat in an individual window Άνοιγμα κάθε συνομιλίας σε ξεχωριστό παράθυρο Emoticons Προσωπάκια Use emoticons Χρησιμοποιήστε προσωπάκια Smiley Pack: Text on smiley pack label Πακέτο για χαμογ. προσωπάκια: Emoticon size: Μέγεθος για προσωπάκια: px εικονοστοιχεία Theme Θέμα Style: Στυλ: Theme color: Χρώμα θέματος: Timestamp format: Μορφή χρονικής σήμανσης: Date format: Μορφή ημερομηνίας: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Εάν είναι ενεργοποιημένο, κάθε επαφή χωρίς εικονίδιο (avatar), θα εμφανίζεται με ένα αυτόματα παραγόμενο βάσει του Tox ID, αντί για την προκαθορισμένη εικόνα. Απαιτείται επανεκκίνηση για να τεθεί σε εφαρμογή. Use identicons instead of empty avatars Χρήση εικονιδίων αντί κενών avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Αναπαραγωγή ήχου Play sound while Busy Αναπαραγωγή ήχου ενόσω Απασχολημένος Notify via desktop notifications Hide message sender and contents Widget Status Κατάσταση toxcore failed to start, the application will terminate after you close this message. Το toxcore απέτυχε να ξεκινήσει, η εφαρμογή θα τερματιστεί αφού κλείσετε αυτό το μήνυμα. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Το toxcore απέτυχε να ξεκινήσει με αυτές τις ρυθμίσεις του διακομιστή μεσολάβησης. Το qTox δεν μπορεί να εκτελεστεί! Παρακαλούμε τροποποιήστε τις ρυθμίσεις σας και κάντε επανεκκίνηση. Executable file popup title Εκτελέσιμο αρχείο You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Έχετε ζητήσει απ' το qTox να ανοίξει ένα εκτελέσιμο αρχείο. Τα εκτελέσιμα αρχεία μπορούν ενδεχομένως να βλάψουν τον υπολογιστή σας. Είστε βέβαιοι ότι θέλετε να ανοίξετε αυτό το αρχείο; Your name Το όνομα σας Couldn't request friendship Το αίτημα φιλίας δεν μπόρεσε να σταλεί Message failed to send Αποτυχία αποστολής μηνύματος Add new circle... Προσθέστε νέο κύκλο... By Name Βάση Ονόματος By Activity Βάση Δραστηριότητας All Όλα Online Συνδεδεμένος/η Offline Εκτός σύνδεσης Friends Φίλοι Groups Ομάδες Search Contacts Αναζήτηση Επαφών Online Button to set your status to 'Online' Συνδεδεμένος/η Away Button to set your status to 'Away' Απών Busy Button to set your status to 'Busy' Απασχολημένος/η Filter... Φίλτρο... File Αρχείο Edit Επεξεργασία Contacts Επαφές Change Status Αλλαγή κατάστασης Edit Profile Επεξεργασία προφίλ Log out Αποσύνδεση Add Contact... Προσθήκη επαφής... Next Conversation Επόμενη Συνομιλία Previous Conversation Προηγούμενη Συνομιλία Groupchat #%1 Ομαδική συνομιλία #%1 Create new group... Δημιουργία νέας ομάδας... %n New Friend Request(s) %n Νέο Αίτημα Φιλίας %n Νέες Αιτήσεις Φιλίας %n New Group Invite(s) %n Νέα Πρόσκληση σε Ομάδα %n Νέες Προσκλήσεις σε Ομάδα Logout Tray action menu to logout user Αποσύνδεση Exit Tray action menu to exit tox Έξοδος Show Tray action menu to show qTox window Εμφάνιση Add friend title of the window Προσθήκη φίλου Group invites title of the window Ομαδικές προσκλήσεις File transfers title of the window Μεταφορές αρχείων Settings title of the window Ρυθμίσεις My profile title of the window Το προφίλ μου Failed to send file "%1" Αποτυχία αποστολής αρχείου "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/eo.ts000066400000000000000000003074471415623743500155360ustar00rootroot00000000000000 AVForm Default resolution Defaŭlta ekrandistingivo Audio/Video Sono/Video Disabled Malebligita Select region Elektu regionon Screen %1 Ekrano %1 Audio Settings Agordoj de sono Gain Playback device Use slider to set volume of your speakers. Capture device Volume Sonforteco Video Settings Agordoj de video Video device Videa aparato Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Resolution Distingivo Rescan devices Reskani aparatojn Test Sound Testi sonon Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Medium (32 kbps) Low (16 kbps) Very low (8 kbps) Threshold AboutForm About Pri Original author: %1 Originala aŭtoro: %1 You are using qTox version %1. Vi uzas qTox versio %1. Commit hash: %1 Enmeta haketo: %1 toxcore version: %1 Versio de toxcore: %1 Qt version: %1 Versio de Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Vidu plenan liston de %1 ĉe Github bug-tracker Replaces `%1` in the `A list of all known…` Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` contributors Replaces `%1` in `See a full list of…` kontribuintoj AboutFriendForm Dialog Dialogo username uzantnomo status message statmesaĝo Used aliases: Kromnomoj uzataj: HISTORY OF ALIASES HISTORIO DE KROMNOMOJ Automatically accept files from contact if set Auto accept files Default directory to save files: Auto accept for this contact is disabled Auto accept call: Manual Manlibro Audio Audio + Video Automatically accept group chat invitations from this contact if set. Auto accept group invites Remove history (operation can not be undone!) Notes Notoj Input field for notes about the contact You can save comment about this contact here. History removed Choose an auto accept directory popup title <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Konfirmo Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Versio License Permesilo Authors Aŭtoroj Known Issues Konataj problemoj Open update download link Update available qTox is up to date ✓ AddFriendForm Couldn't add friend Invalid Tox ID format Add Friends Send friend request Add a friend Aldoni amikon Friend requests Amikiĝpetoj Accept Akcepti Reject Malakcepti Tox ID, either 76 hexadecimal characters or name@example.com Type in Tox ID of your friend Friend request message Type message to send with the friend request or leave empty to send a default message %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description Message The message you send in friend requests Mesaĝo Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Jen %1! Toksu min, eble? Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced Speciala Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not IMPORTANT NOTE GRAVA AVERTO Reset settings All settings will be reset to default. Are you sure? Yes Jes No Ne Call active popup title You can't disconnect while a call is active! popup text Save File Konservi dosieron Logs (*.log) Protokoloj (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Make Tox portable Reset to default settings Portable Portebla Connection Settings Konektaj agordoj Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Ŝalti IPv6 (indas) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP Proxy type: Address: Text on proxy addr label Adreso: Port: Text on proxy port label Pordo: None Neniu SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Rekonekti Debug Malcimigi Export Debug Log Copy Debug Log Enable LAN discovery ChatForm Send a file Sendi dosieron Unable to open Ne eblas malfermi qTox wasn't able to open %1 qTox ne sukcesis malfermi %1 Bad idea Malbona ideo %1 calling Calling %1 Failed to open temporary file Temporary file for screenshot Malsukcesis malfermi nedaŭran dosieron qTox wasn't able to save the screenshot qTox ne povis konservi la ekrankaptaĵon Call with %1 ended. %2 Call duration: %1 is typing %1 tajpas Copy Kopii You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" %1 nun estas %2 Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Start audio call End audio call Cancel audio call Accept audio call Can't start video call Start video call End video call Cancel video call Accept video call Sound can be disabled only during a call Unmute call Mute call Microphone can be muted only during a call Unmute microphone Malsilentigi mikrofonon Mute microphone Silentigi mikrofonon ChatLog pending pritraktota Copy Kopii Select all Selekti ĉion ChatTextEdit Type your message here... Tajpu vian mesaĝon ĉi tie... CircleWidget Rename circle Menu for renaming a circle Alinomi rondon Remove circle Menu for removing a circle Forigi rondon Open all in new window Malfermi ĉion en nova fenestro Core /me offers friendship, "%1" /me ofertas amikecon, "%1" Invalid Tox ID Error while sending friendship request You need to write a message with your request Error while sending friendship request Vi bezonas skribi mesaĝon en via peto Your message is too long! Error while sending friendship request Via mesaĝo tro longas! Friend is already added Error while sending friendship request Tiu amiko jam estis aldonita Groupchat %1 DesktopNotify New message Nova mesaĝo Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 Restanta tempo:10:10 Filename Dosiernomo Waiting to send... file transfer widget Atendante por sendi... Accept to receive this file file transfer widget Akceptu por ricevi ĉi tiun dosieron Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Paused file transfer widget Resuming... file transfer widget Open file Malfermi dosieron Open file directory Malfermi dosierujon Pause transfer Cancel transfer Resume transfer Accept transfer Save a file Title of the file saving dialog Konservi dosieron Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Downloads Elŝutoj Uploads Alŝutoj FriendListWidget Today Hodiaŭ Yesterday Hieraŭ Last 7 days Pasintaj 7 tagoj This month Ĉi-monate Older than 6 Months Antaŭ 6 monatoj Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Amikiĝpeto Someone wants to make friends with you Iu volas amikiĝi kun vi User ID: Uzanta identigilo: Friend request message: Amikiĝpeta mesaĝo: Accept Accept a friend request Akcepti Reject Reject a friend request Malakcepti FriendWidget Open chat in new window Malfermi babilejon en nova fenestro Remove chat from this window Invite to group Menu to invite a friend to a groupchat Inviti al grupo To new group Al nova grupo Invite to group '%1' Inviti al la grupo '%1' Move to circle... Menu to move a friend into a different circle Transloki al la rondo... To new circle Al nova rondo Remove from circle '%1' Move to circle "%1" Set alias... Auto accept files from this friend context menu entry Remove friend Menu to remove the friend from our friendlist Show details Montri detalojn Choose an auto accept directory popup title New message Nova mesaĝo Online Enrete Away Fore Busy Okupite Offline Elreta GeneralForm Choose an auto accept directory popup title General GeneralSettings General Settings The translation may not load until qTox restarts. Language: Lingvo: Start qTox on operating system startup (current profile). Autostart Enable light tray icon. toolTip for light icon setting Light icon Show system tray icon qTox will start minimized in tray. toolTip for Start in tray setting Start in tray After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Minimize to tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Close to tray Your status is changed to Away after set period of inactivity. Auto away after (0 to disable): Set to 0 to disable Set where files will be saved. Default directory to save files: You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Autoaccept files Show contacts' status changes Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Save chat log Cleared Send message Smileys Miensimboloj Send file(s) Send a screenshot Clear displayed messages Quote selected text Copy link address Confirmation Konfirmo You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Export to file GenericNetCamView Tox video Tox-video Show Messages Montri mesaĝojn Hide Messages Kaŝi mesaĝojn Full Screen Toggle video preview Mute audio Mute microphone Silentigi mikrofonon End video call Exit full screen GroupChatForm %1 has set the title to %2 %1 titolis ĝin %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Grupoj Create new group Group invites Grupo-invitoj GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Open chat in new window Malfermi babilejon en nova fenestro Remove chat from this window Set title... Titoli... Quit group Menu to quit a groupchat Eliri grupon %n user(s) in chat Number of users in chat New Message Online IdentitySettings Public Information Publika informado Tox ID Tox-identigilo This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Your Tox ID (click to copy) This QR code contains your Tox ID. You may share this with your friends as well. Save image Konservi bildon Copy image Kopii bildon Server Servilo Hide my name from the public list Register Your password Via pasvorto Update Ĝisdatigi Profile Profilo Rename profile. tooltip for renaming profile button Alinomi profilon. Rename rename profile button Alinomi Delete profile. delete profile button tooltip Forigi profilon. Delete delete profile button Forigi Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Export export profile button Go back to the login screen tooltip for logout button Logout import profile button Elsaluti Remove password Change password Ŝanĝi pasvorton Register on ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. Alinomi profilon. Delete profile. Forigi profilon. Export profile Remove password from profile Change profile password My name: My status: My username My biography My profile LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Uzantnomo: Password: Pasvorto: Confirm: Konfirmi: Password strength: %p% Create Profile Krei profilon If the profile does not have a password, qTox can skip the login screen Load automatically Import Load New Profile Nova profilo Load Profile Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Password protected profiles can't be automatically loaded. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Malĝusta pasvorto. Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Via nomo Your status Via stato ... ... Add friends Aldoni amikojn Create a group chat Krei grupbabilejon View completed file transfers Change your settings Ŝanĝi viajn agordojn Close Fermi Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings Agordoj Open Settings Nexus View OS X Menu bar Vidi Window OS X Menu bar Fenestro Minimize OS X Menu bar Minimumigi Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Confirmation Konfirmo Do you want to permanently delete all chat history? Privacy Privateco PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Privacy Privateco BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox Toksante ĉe qTox ProfileForm Current profile: Nuna profilo: Remove Choose a profile picture Error Eraro Unable to open this file. Unable to read this image. The supplied image is too large. Please use another image. Rename "%1" renaming a profile Alinomi "%1" Couldn't rename the profile to "%1" Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Are you sure you want to delete this profile? deletion confirmation text Files could not be deleted! deletion failed title Change password button text Ŝanĝi pasvorton Set profile password button text Agordi profilan pasvorton Save save qr image Konservi Save QrCode (*.png) save dialog filter Konservi QrKodon (*.png) Nothing to remove Nenio forigebla Your profile does not have a password! Via profilo ne havas pasvorton! Really delete password? deletion confirmation title Ĉu vere forigi pasvorton? Please enter a new password. Register (processing) Update (processing) Done! Farite! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Ĝisdatigi Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists La profilo jam ekzistas A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter Bildoj (%1) ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title La profilo jam ekzistas A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Bone Cancel Nuligi Yes Jes No Ne LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Dekstren QMessageBox Couldn't add friend %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend QObject Tox URI to parse Starts new instance and loads specified profile. profile profilo Server doesn't support Toxme Problem with HTTPS connection You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect Malĝusta pasvorto You can't use this name Vi ne rajtas uzi ĉi tiun nomon Name not found Nomo ne trovita Tox ID not sent That user does not exist Internal ToxMe error %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Jen %1! Toksu min, eble? Error Eraro qTox couldn't open your chat logs, they will be disabled. None No camera device set Neniu Desktop Desktop as a camera input for screen sharing Labortablo Default Defaŭlto Blue Blua Olive Olivkolora Red Ruĝa Violet Violkolora Incoming call... Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status enrete away contact status fore busy contact status okupite offline contact status elrete blocked contact status RemoveFriendDialog Remove friend Also remove chat history Remove Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Konfirmu: Password: Pasvorto: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 Rondo #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Aldoni amikon Do you want to add %1 as a friend? Ĉu vi volas aldoni %1 kiel amiko? User ID: Uzanta identigilo: Friend request message: Amikiĝpeta mesaĝo: Send Send a friend request Sendi Cancel Don't send a friend request Nuligi UserInterfaceForm None Neniu User Interface Uzanta fasono UserInterfaceSettings Chat Babilejo Base font: Defaŭlta tiparo: px rastrumeroj Size: Grando: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message Nova mesaĝo Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Malfermi fenestron Contact list Kontaktlisto If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Miensimboloj Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px rastrumeroj Theme Style: Stilo: Theme color: Timestamp format: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Status toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Executable file popup title You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Your name Via nomo Couldn't request friendship Ne eblis amikiĝpeti Groupchat #%1 Grupbabilejo #%1 Message failed to send Mesaĝo fiaskis dum sendado Create new group... Krei novan grupon... Add new circle... Aldoni novan rondon... %n New Friend Request(s) %n nova amikiĝpeto %n New Group Invite(s) %n nova grupo-invito By Name Per nomo By Activity Per aktiveco All Ĉio Online Enrete Offline Elrete Friends Amikoj Groups Grupoj Search Contacts Serĉi kontaktojn Online Button to set your status to 'Online' Enretaj Away Button to set your status to 'Away' Fore Busy Button to set your status to 'Busy' Okupitaj Logout Tray action menu to logout user Elsaluti Exit Tray action menu to exit tox Eliri Filter... Filtri... File Dosiero Edit Redakti Contacts Kontaktoj Change Status Edit Profile Redakti profilon Log out Elsaluti Add Contact... Aldoni kontakton... Next Conversation Malantaŭa retbabilo Previous Conversation Antaŭa retbabilo Show Tray action menu to show qTox window Montri Add friend title of the window Aldoni amikon Group invites title of the window Grupo-invitoj File transfers title of the window Dosiertransigoj Settings title of the window Agordoj My profile title of the window Failed to send file "%1" Malsukcesis sendi dosieron "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/es.ts000066400000000000000000003313641415623743500155350ustar00rootroot00000000000000 AVForm Audio/Video Audio/Vídeo Default resolution Resolución predeterminada Disabled Deshabilitado Select region Seleccionar región Screen %1 Pantalla %1 Audio Settings Opciones de audio Gain Ganancia Playback device Dispositivo de reproducción Use slider to set volume of your speakers. Usar el control deslizante para ajustar el volumen de los altavoces. Capture device Dispositivo de captura Volume Volumen Video Settings Opciones de vídeo Video device Dispositivo de vídeo Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Establezca la resolución de la cámara. Valores más altos mejoran la calidad de vídeo que tus amigos pueden ver. Ten en cuenta que una mejor calidad de vídeo requiere una mejor conexión a Internet. Si tu conexión no es suficiente para soportar una calidad de vídeo alta, se pueden producir problemas con las videollamadas. Resolution Resolución Rescan devices Volver a detectar dispositivos Test Sound Comprobar sonido Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Activar el panel experimental de gestión del sonido con soporte para cancelación de eco, necesita reiniciar qTox para tener efecto. Enable experimental audio backend Activar el panel experimental de gestión del sonido Audio quality Calidad de audio Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Calidad de audio transmitido. Reduzca esta configuración si su ancho de banda no es lo suficientemente alto o si desea reducir el uso de Internet. High (64 kbps) Alto (64 kbps) Medium (32 kbps) Medio (32 kbps) Low (16 kbps) Bajo (16 kbps) Very low (8 kbps) Muy bajo (8 kbps) Threshold Límite AboutForm About Acerca de Original author: %1 Autor original: %1 You are using qTox version %1. Está usando qTox versión %1. Commit hash: %1 Confirmación de hash: %1 toxcore version: %1 Versión de toxcore: %1 Qt version: %1 Versión de Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Puedes encontrar una lista de los problemas conocidos en nuestro %1 en GitHub. Si encuentras un error o una vulnerabilidad de seguridad en qTox, por favor repórtalo de acuerdo a las directrices en nuestro artículo de la wiki %2. Click here to report a bug. Clic aquí para reportar un error. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Lista completa de %1 en Github bug-tracker Replaces `%1` in the `A list of all known…` Localizar error Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Escribir informes de errores necesarios contributors Replaces `%1` in `See a full list of…` colaboradores AboutFriendForm Dialog Diálogo username nombre del usuario status message mensaje de estado Used aliases: Alias usados: HISTORY OF ALIASES HISTORIAL DE ALIAS Automatically accept files from contact if set Aceptar automáticamente archivos de los contactos si esta establecido Auto accept files Aceptar archivos automáticamente Default directory to save files: Directorio predeterminado para guardar archivos: Auto accept for this contact is disabled Recepción automática deshabilitada para este contacto Auto accept call: Aceptar llamadas automáticamente: Manual Manual Audio Audio Audio + Video Audio y vídeo Automatically accept group chat invitations from this contact if set. Aceptar automáticamente invitaciones de chat de grupo de este contacto si esta establecido. Auto accept group invites Aceptar automáticamente invitaciones de grupos Remove history (operation can not be undone!) Eliminar historial (¡no se puede deshacer!) Notes Notas Input field for notes about the contact Campo para introducir notas sobre el contacto You can save comment about this contact here. Puedes guardar comentarios acerca de este contacto aquí. History removed Historial eliminado Choose an auto accept directory popup title Seleccione un directorio y aceptar automáticamente <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Esta es la clave pública de su amigo, utilícela para verificar su identidad a través de otro canal. No puedes enviarla a otras personas para que puedan agregar este contacto.</p></body></html> Public key (not ToxID): Clave pública (no el ToxID): Confirmation Confirmación Are you sure to remove %1 chat history? ¿Estás seguro de eliminar %1 historial de chat? Failed to remove chat history with %1! ¡Error al eliminar el historial de chat con %1! AboutSettings Version Versión License Licencia Authors Autores Known Issues Problemas conocidos Open update download link Abrir enlace de descarga de actualización Update available Actualización disponible qTox is up to date ✓ qTox está actualizado ✓ AddFriendForm Add Friends Agregar amigos Send friend request Enviar solicitud de amistad Add a friend Agregar un amigo Friend requests Solicitudes de amistad Accept Aceptar Reject Rechazar Couldn't add friend No se pudo agregar el amigo Invalid Tox ID format Formato inválido de ID de Tox Tox ID, either 76 hexadecimal characters or name@example.com ID de Tox, ya sea 76 caracteres hexadecimales o nombre@ejemplo.com Type in Tox ID of your friend Escribe el ID de Tox de tu amigo Friend request message Mensaje para solicitud de amistad Type message to send with the friend request or leave empty to send a default message Escribe el mensaje a enviar junto con la petición de amistad o déjalo vacío para enviar el mensaje por defecto %1 Tox ID is invalid or does not exist Toxme error %1 ID de Tox no es válida o no existe You can't add yourself as a friend! When trying to add your own Tox ID as friend ¡No puedes agregarte a ti como amigo! Open contact list Abrir lista de contactos Couldn't open file No se pudo abrir el archivo Couldn't open the contact file Error message when trying to open a contact list file to import No se pudo abrir el archivo de contacto Invalid file Archivo inválido We couldn't find any contacts to import in this file! ¡No se pudo encontrar ningún contacto para importar en este archivo! Tox ID Tox ID of the person you're sending a friend request to ID de Tox either 76 hexadecimal characters or name@example.com Tox ID format description 76 caracteres hexadecimales o nombre@ejemplo.com Message The message you send in friend requests Mensaje Open Button to choose a file with a list of contacts to import Abrir Send friend requests Enviar solicitudes de amistad %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! ¡Hola, soy %1! ¿Deseas agregarme en Tox? Import a list of contacts, one Tox ID per line Importar lista de contactos, un ID de Tox por línea Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Listo para importar %n contacto, clic en enviar para confirmar Listo para importar %n contactos, clic en enviar para confirmar Import contacts Importar contactos AdvancedForm Advanced Avanzado Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. A menos que %1 sepas lo que estás haciento, por favor %2 cambies nada aquí. qTox podría empezar a tener problemas e incluso podrías perder datos, como tu historial. really realmente not no IMPORTANT NOTE NOTA IMPORTANTE Reset settings Restablecer configuración All settings will be reset to default. Are you sure? La configuración va a ser restablecida a los valores predeterminados. ¿Estás seguro? Yes No No Call active popup title Llamada activa You can't disconnect while a call is active! popup text ¡No puedes desconectarte mientras haya una llamada activa! Save File Guardar archivo Logs (*.log) Registros (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Guardar la configuración en el directorio actual en vez del predeterminado Make Tox portable No debería ser "Make qTox portable"? Hacer Tox portable Reset to default settings Restablecer configuración predeterminada Portable Portable Connection Settings Opciones de conexión Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Habilitar IPv6 (recomendado) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Desactivar esto permite, por ejemplo, el uso de Tox a través de Tor. Hazlo sólo en caso de ser necesario. Enable UDP (recommended) Text on checkbox to disable UDP Habilitar UDP (recomendado) Proxy type: Tipo de proxy: Address: Text on proxy addr label Dirección: Port: Text on proxy port label Puerto: None Ninguno SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Reconectar Debug Depurar Export Debug Log Exportar registro de depuración Copy Debug Log Copiar registro de depuración Enable LAN discovery Habilitar la detección de LAN ChatForm Send a file Enviar un archivo qTox wasn't able to open %1 qTox no pudo abrir %1 Unable to open No se pudo abrir Bad idea Mala idea %1 calling %1 llamando Failed to open temporary file Temporary file for screenshot Error al abrir el archivo temporal qTox wasn't able to save the screenshot qTox no pudo guardar la captura de pantalla Call with %1 ended. %2 Llamada con %1 terminada. %2 Call duration: Duración de la llamada: Calling %1 Llamando a %1 %1 is typing %1 está escribiendo Copy Copiar You're trying to send a sequential file, which is not going to work! Estás tratando de enviar un archivo consecutivo ¡lo que no va a funcionar! %1 is now %2 e.g. "Dubslow is now online" %1 ahora está %2 Call with %1 ended unexpectedly. %2 Llamada con %1 terminó inesperadamente. %2 Filename contained illegal characters Nombre del archivo contenía caracteres no válidos Illegal characters have been changed to _ so you can save the file on windows. Los caracteres no válidos fueran cambiados a _ para que puedas guardar el archivo en windows. ChatFormHeader Can't start audio call No se puede iniciar la llamada de audio Start audio call Iniciar llamada de audio End audio call Terminar llamada de audio Cancel audio call Cancelar llamada de audio Accept audio call Aceptar llamada de audio Can't start video call No se puede iniciar la videollamada Start video call Iniciar videollamada End video call Terminar videollamada Cancel video call Cancelar videollamada Accept video call Aceptar videollamada Sound can be disabled only during a call El sonido sólo puede ser desactivado durante una llamada Unmute call Dejar de silenciar llamada Mute call Silenciar llamada Microphone can be muted only during a call El micrófono se puede silenciar sólo durante una llamada Unmute microphone Dejar de silenciar el micrófono Mute microphone Silenciar micrófono ChatLog Copy Copiar Select all Seleccionar todo pending pendiente ChatTextEdit Type your message here... Ingresa tu mensaje aquí... CircleWidget Rename circle Menu for renaming a circle Renombrar círculo Remove circle Menu for removing a circle Eliminar círculo Open all in new window Abrir todos en una nueva ventana Core /me offers friendship, "%1" /me ofrece amistad, "%1" Invalid Tox ID Error while sending friendship request ID de Tox inválida You need to write a message with your request Error while sending friendship request Tienes que escribir un mensaje para la solicitud Your message is too long! Error while sending friendship request ¡Tu mensaje es demasiado largo! Friend is already added Error while sending friendship request Amigo ya fue agregado Groupchat %1 Conversación en grupo %1 DesktopNotify New message Nuevo mensaje Incoming file transfer Transferencia de archivo entrante Friend request received Solicitud de amistad recibida New group message Nuevo mensaje de grupo Group invite received Invitación de grupo recibida FileTransferWidget Form Plantilla 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 ETA:10:10 Filename Nombre del archivo Waiting to send... file transfer widget Envío en espera... Accept to receive this file file transfer widget Presiona aceptar para recibir este archivo Location not writable Title of permissions popup Ubicación no escribible You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup No tienes permiso de escritura. Elige otra ubicación o cancela el envío. Resuming... file transfer widget Reanudando... Cancel transfer Cancelar transferencia Pause transfer Pausar transferencia Paused file transfer widget En pausa Open file Abrir archivo Open file directory Abrir directorio del archivo Resume transfer Reanudar transferencia Accept transfer Aceptar transferencia Save a file Title of the file saving dialog Guardar un archivo Remote Paused file transfer widget Remoto pausado FilesForm Transferred Files "Headline" of the window Transferencias Downloads Descargas Uploads Subidas FriendListWidget Today Hoy Yesterday Ayer Last 7 days Últimos 7 días This month Este mes Older than 6 Months Más de 6 meses Never Nunca FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Solicitud de amistad Someone wants to make friends with you Alguien desea agregarte como amigo User ID: ID del usuario: Friend request message: Mensaje de solicitud de amistad: Accept Accept a friend request Aceptar Reject Reject a friend request Rechazar FriendWidget Invite to group Menu to invite a friend to a groupchat Invitar al grupo To new group A un nuevo grupo Invite to group '%1' Invitar al grupo '%1' Move to circle... Menu to move a friend into a different circle Mover al círculo... To new circle A un nuevo círculo Remove from circle '%1' Eliminar del círculo "%1" Move to circle "%1" Mover al círculo "%1" Set alias... Establecer alias... Auto accept files from this friend context menu entry Aceptar archivos de este amigo automáticamente Show details Mostrar detalles New message Nuevo mensaje Online Conectado Away Ausente Busy Ocupado Offline Desconectado Choose an auto accept directory popup title Elige un directorio para descargas automáticas Open chat in new window Abrir chat en una nueva ventana Remove chat from this window Quitar chat de esta ventana Remove friend Menu to remove the friend from our friendlist Eliminar amigo GeneralForm General General Choose an auto accept directory popup title Elige un directorio para transferencias automáticas GeneralSettings General Settings Opciones generales The translation may not load until qTox restarts. Puede que necesites reiniciar qTox para activar la traducción. Start in tray Iniciar en la bandeja Close to tray Cerrar a la bandeja Minimize to tray Minimizar a la bandeja Show contacts' status changes Mostrar cambios de estado de amigos Auto away after (0 to disable): Ausente después de (0 para desactivar): Set to 0 to disable 0 para desactivar Language: Idioma: Enable light tray icon. toolTip for light icon setting Habilitará un ícono claro en la bandeja. Light icon Ícono claro qTox will start minimized in tray. toolTip for Start in tray setting qTox se iniciará minimizado en la bandeja. After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Después de presionar cerrar (X) qTox minimizará a la bandeja, en lugar de cerrarse. After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Después de presionar a minimizar (_) qTox minimizará a la bandeja, en lugar de la barra de tareas del sistema. Autostart Iniciar automáticamente Set where files will be saved. Establece dónde se guardarán los archivos. Your status is changed to Away after set period of inactivity. Tu estado cambia a 'Ausente' después del período de inactividad establecido. Start qTox on operating system startup (current profile). Iniciar qTox (usando el perfil actual) junto con el sistema operativo. Show system tray icon Mostrar ícono en la bandeja You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Se puede configurar de forma personalizada con clic derecho sobre el amigo. Autoaccept files Aceptar archivos automáticamente Default directory to save files: Directorio predeterminado para guardar archivos: Check for updates Verificar actualizaciones Spell checking Corrección ortográfica Max autoaccept file size (0 to disable): Tamaño máximo de archivo con aceptación automática (0 para deshabilitar): MB MB GenericChatForm Send message Enviar mensaje Smileys Emoticonos Send file(s) Enviar archivo(s) Send a screenshot Enviar captura de pantalla Save chat log Guardar historial de chat Clear displayed messages Borrar mensajes actuales Cleared Borrado Quote selected text Citar texto seleccionado Copy link address Copiar la dirección del enlace Confirmation Confirmación You are sure that you want to clear all displayed messages? ¿Estás seguro de que desea borrar todos los mensajes mostrados? Search in text Buscar en el texto Go to current date Ir a la fecha actual Load chat history... Cargar historial de chat... Export to file Exportar a fichero GenericNetCamView Tox video Vídeo Tox Show Messages Mostrar mensajes Hide Messages Ocultar mensajes Full Screen Pantalla completa Toggle video preview Exhibir vista previa de vídeo Mute audio Silenciar el audio Mute microphone Silenciar micrófono End video call Terminar videollamada Exit full screen Salir de pantalla completa GroupChatForm %1 has set the title to %2 %1 ha establecido el título a: %2 %1 has joined the group %1 se ha unido al grupo %1 is now known as %2 %1 ahora es conocido como %2 %1 has left the group %1 ha dejado el grupo %n user(s) in chat Number of users in chat %n usuario en el chat %n usuarios en el chat mute mudo unmute activar el sonido GroupInviteForm Groups Grupos Create new group Crear un nuevo grupo Group invites Invitaciones a grupos GroupInviteWidget Invited by %1 on %2 at %3. Invitado por %1 en %2 a %3. Join Unirse Decline Rechazar GroupWidget Set title... Establecer título... Open chat in new window Abrir chat en una nueva ventana Remove chat from this window Quitar chat de esta ventana Quit group Menu to quit a groupchat Dejar el grupo %n user(s) in chat Number of users in chat %n usuario en el chat %n usuarios en el chat New Message Nuevo Mensaje Online Conectado IdentitySettings Public Information Información pública Tox ID ID de Tox This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Tox usa este grupo de caracteres para saber cómo has de ser contactado. Compártelo con tus amigos para poder comunicarte. Your Tox ID (click to copy) Tu ID de Tox (haz clic para copiarla) Profile Perfil Rename profile. tooltip for renaming profile button Aquí puedes renombrar tu perfil actual. Delete profile. delete profile button tooltip Eliminará tu perfil actual. Go back to the login screen tooltip for logout button Volver a la pantalla de inicio de sesión Logout import profile button Cerrar sesión Remove password Eliminar contraseña Change password Cambiar contraseña This QR code contains your Tox ID. You may share this with your friends as well. Este código QR contiene tu ID de Tox. Puedes compartirlo con tus amigos. Save image Guardar imagen Copy image Copiar imagen Rename rename profile button Renombrar Export export profile button Exportar Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Te permite exportar tu perfil Tox a un archivo. El perfil no contiene tu historial. Delete delete profile button Eliminar Server Servidor Hide my name from the public list Ocultar mi nombre de la lista pública Register Registrar Your password Tu contraseña Update Actualizar Register on ToxMe Registrase en ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Nombre para el servicio ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Opcional. Algo sobre ti. O tu gato. Optional. Something about you. Or your cat. Tooltip for the Biography field. Opcional. Algo sobre ti. O tu gato. ToxMe service to register on. Servicio ToxMe en el que registrarse. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Si no está establecido, los accesos a ToxMe son visibles de manera pública. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Eliminar tu contraseña y cifrado de tu perfil. Name input Introducir nombre Name visible to contacts Nombre visible para los contactos Status message input Introducir mensaje de estado Status message visible to contacts Mensaje de estado visible para todos los contactos Your Tox ID Tu ID de Tox Save QR image as file Guardar la imagen QR como un archivo Copy QR image to clipboard Copiar la imagen QR en el portapapeles del sistema ToxMe username to be shown on ToxMe El nombre de usuario de ToxMe para ser mostrado en ToxMe Optional ToxMe biography to be shown on ToxMe Opcional biografía de ToxMe para ser mostrada en ToxMe ToxMe service address Dirección del servicio ToxMe Visibility on the ToxMe service Visibilidad en el servicio ToxMe Password Contraseña Update ToxMe entry Actualizar acceso a ToxMe Rename profile. Renombrar perfil. Delete profile. Elimina perfil. Export profile Exportar perfil Remove password from profile Eliminar contraseña del perfil Change profile password Cambiar la contraseña del perfil My name: Mi nombre: My status: Mi estado: My username Mi nombre de usuario My biography Mi biografía My profile Mi perfil LoadHistoryDialog Load History Dialog Cargar historial Load history Cargar historial from de to para (about 100 messages are loaded) (aproximadamente 100 mensajes están cargados) Select Date Dialog Diálogo Seleccionar fecha Select a date Seleccione una fecha LoginScreen Username: Usuario: Password: Contraseña: Confirm: Confirmar: Password strength: %p% Robustez de la contraseña: %p% Create Profile Crear perfil If the profile does not have a password, qTox can skip the login screen Si el perfil no está protegido con contraseña, qTox puede saltarse la pantalla de inicio de sesión Load automatically Iniciar sesión automáticamente Load Iniciar Load Profile Cargar perfil New Profile Nuevo perfil Couldn't create a new profile No se pudo crear un nuevo perfil The username must not be empty. El nombre de usuario no puede estar vacío. The password must be at least 6 characters long. La contraseña tiene que tener al menos 6 caracteres. The passwords you've entered are different. Please make sure to enter same password twice. Las contraseñas ingresadas no coinciden. Verifica que sea la misma en ambos recuadros. A profile with this name already exists. Ya existe un perfil con ese nombre. Couldn't load this profile No se pudo cargar el perfil This profile is already in use. Ese perfil ya está en uso. Wrong password. Contraseña incorrecta. Couldn't load profile No se pudo cargar el perfil There is no selected profile. You may want to create one. No has seleccionado ningún perfil. Tal vez quieras crear uno. Import Importar Password protected profiles can't be automatically loaded. Perfiles protegidos con contraseña no pueden ser cargados automáticamente. Username input field Campo para introducir el nombre del usuario Password input field, you can leave it empty (no password), or type at least 6 characters Campo de entrada de la contraseña, puedes dejarlo vacío (sin contraseña), o escribir al menos 6 caracteres Password confirmation field Campo de confirmación de contraseña Create a new profile button Botón para crear un perfil nuevo Profile list Lista de perfiles List of profiles Lista de perfiles Password input Introducir contraseña Load automatically checkbox Cargar automáticamente casilla de selección Import profile Importar perfil Load selected profile button Botón para cargar perfil seleccionado New profile creation page Nueva página de creación de perfil Loading existing profile page Cargando página de perfil existente MainWindow ... ... Add friends Agregar amigos Create a group chat Crear un chat grupal View completed file transfers Ver transferencias de archivos completadas Change your settings Configurar Close Cerrar Your name Tu nombre Your status Tu estado Open profile Abrir perfil Open profile page when clicked Abrir página de perfil al hacer clic Status message input Introducir mensaje de estado Set your status message that will be shown to others Establezca su mensaje de estado que se mostrará a los demás Status Estado Set availability status Establecer estado de disponibilidad Contact search Buscar contacto Contact search input for known friends Buscar contactos para amigos conocidos Sorting and visibility Clasificación y visibilidad Set friends sorting and visibility Establecer clasificación y visibilidad de tus amigos Open Add friends page Abrir página de amigos agregados Groupchat Chat grupal Open groupchat management page Abrir página de administración de chat grupal File transfers history Historial de transferencias de archivos Open File transfers history Abrir historial de transferencias de archivos Settings Opciones Open Settings Abrir opciones Nexus View OS X Menu bar Ver Window OS X Menu bar Ventana Minimize OS X Menu bar Minimizar Bring All to Front OS X Menu bar Traer todos al frente Exit Fullscreen Salir de pantalla completa Enter Fullscreen Pantalla completa NotificationEdgeWidget Unread message(s) Mensaje no leído Mensajes no leídos PasswordEdit CAPS-LOCK ENABLED BLOQ MAYÚS ACTIVO PrivacyForm Privacy Privacidad Confirmation Confirmación Do you want to permanently delete all chat history? ¿Deseas eliminar permanentemente el historial del chat? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Tus amigos podrán ver cuándo estás escribiendo. Send typing notifications Enviar notificaciones de tecleo Keep chat history Guardar historial del chat NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam es parte de tu ID de Tox. Si estás recibiendo solicitudes de amistad no deseadas, considera cambiar tu NoSpam. Ya no va a ser posible que te agreguen con tu vieja ID, pero no vas a perder a los amigos que ya hayas agregado. NoSpam Anti-spam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam es una parte de tu ID de Tox que puedes cambiar cuando desees. Si estás recibiendo solicitudes de amistad no deseadas, cambia tu NoSpam. Generate random NoSpam Generar NoSpam aleatorio Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Mantener un historial de chat es una función aún en desarrollo. Es posible que haya cambios en el formato de guardado, lo que puede generar una pérdida de datos. Privacy Privacidad BlackList Lista negra Filter group message by group member's public key. Put public key here, one per line. Filtrar mensajes de grupo por clave pública de miembros del grupo. Ponga la clave pública aquí, una por línea. Profile Failed to derive key from password, the profile won't use the new password. Error al derivar la clave de la contraseña, el perfil no usará la nueva contraseña. Couldn't change password on the database, it might be corrupted or use the old password. No se pudo cambiar la contraseña en la base de datos, podría estar dañada o usar la contraseña anterior. Toxing on qTox Toxeando con qTox ProfileForm Current profile: Perfil actual: Choose a profile picture Elige una imagen de perfil Error Error Unable to open this file. No fue posible abrir el archivo. Unable to read this image. No fue posible leer la imagen. The supplied image is too large. Please use another image. La imagen seleccionada es demasiado grande. Por favor usa otra. Rename "%1" renaming a profile Renombrar "%1" Couldn't rename the profile to "%1" No se pudo renombrar el perfil a "%1" Location not writable Title of permissions popup Ubicación no escribible You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup No tienes permiso de escritura. Elige otra ubicación o cancela la operación. Failed to copy file No se pudo copiar el archivo The file you chose could not be written to. No se pudo escribir al archivo elegido. Really delete profile? deletion confirmation title ¿Seguro que quieres eliminar el perfil? Nothing to remove Nada que eliminar Your profile does not have a password! ¡Tu perfil no tiene contraseña! Really delete password? deletion confirmation title ¿Seguro que quieres eliminar la contraseña? Please enter a new password. Ingresa tu nueva contraseña. Are you sure you want to delete this profile? deletion confirmation text ¿Estás seguro de que deseas eliminar este perfil? Save save qr image Guardar Save QrCode (*.png) save dialog filter Guardar código QR (*.png) Remove Eliminar Files could not be deleted! deletion failed title ¡No se pudieron eliminar los archivos! Register (processing) Registro (procesando) Update (processing) Actualización (procesando) Done! ¡Finalizado! Account %1@%2 updated successfully Cuenta %1@%2 actualizada exitósamente Successfully added %1@%2 to the database. Save your password Cuenta %1@%2 agregada exitosamente a la base de datos. Respalda tu contraseña Toxme error Error en Toxme Register Registrar Update Actualizar Change password button text Cambiar contraseña Set profile password button text Establecer contraseña del perfil Current profile location: %1 Ubicación del perfil actual: %1 Couldn't change password No se pudo cambiar la contraseña This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Este grupo de personajes le dice a otros clientes de Tox cómo contactarte. Compártelo con tus amigos para comunicarte. Este ID incluye el código NoSpam (en azul) y la suma de comprobación (en gris). Empty path is unavaliable Ruta vacía no está disponible Failed to rename Error al renombrar Profile already exists Perfil ya existe A profile named "%1" already exists. Un perfil llamado "%1" ya existe. Empty name Nombre vacío Empty name is unavaliable Nombre vacío no está disponible Empty path Ruta vacía Couldn't change password on the database, it might be corrupted or use the old password. No se pudo cambiar la contraseña en la base de datos, podría estar dañada o usar la contraseña anterior. Export profile Exportar perfil Tox save file (*.tox) save dialog filter Guardar archivo Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Los siguientes archivos no pudieron ser eliminados: Please manually remove them. deletion failed text part 2 Por favor eliminar manualmente. Are you sure you want to delete your password? deletion confirmation text ¿Seguro de que quieres eliminar tu contraseña? Images (%1) filetype filter Imágenes (%1) ProfileImporter Import profile import dialog title Importar perfil Tox save file (*.tox) import dialog filter Archivo Tox (*.tox) Ignoring non-Tox file popup title Ignorando archivo no Tox Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Advertencia: el archivo Tox seleccionado es inválido y ha sido ignorado. Profile already exists import confirm title Perfil ya existe A profile named "%1" already exists. Do you want to erase it? import confirm text Un perfil llamado "%1" ya existe. ¿Deseas eliminarlo? File doesn't exist Archivo no existe Profile doesn't exist El perfil no existe Profile imported Perfil importado %1.tox was successfully imported %1.tox ha sido importado exitosamente QApplication Ok Aceptar Cancel Cancelar Yes No No LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend No se pudo agregar el amigo %1 is not a valid Toxme address. %1 no es una dirección Toxme válida. You can't add yourself as a friend! When trying to add your own Tox ID as friend ¡No puedes agregarte a ti como amigo! QObject Tox URI to parse URI Tox a utilizar Starts new instance and loads specified profile. Inicia una nueva instancia de qTox y carga el perfil especificado. profile perfil Default Predeterminado Blue Azul Olive Oliva Red Rojo Violet Violeta Incoming call... Llamada entrante... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! ¡Hola, soy %1! ¿Deseas agregarme en Tox? None No camera device set Ninguno Desktop Desktop as a camera input for screen sharing Pantalla Server doesn't support Toxme El servidor no soporta Toxme Problem with HTTPS connection Problema con la conexión HTTPS You're making too many requests. Wait an hour and try again Estás haciendo demasiadas peticiones. Inténtalo de nuevo en una hora This name is already in use Ese nombre ya está en uso This Tox ID is already registered under another name Este ID de Tox ya fue registrada bajo otro nombre Please don't use a space in your name Espacios en el nombre no están permitidos Password incorrect Contraseña incorrecta You can't use this name No puedes usar ese nombre Name not found Nombre no encontrado Tox ID not sent No se envió el ID de Tox Internal ToxMe error Error interno de Toxme That user does not exist Ese usuario no existe Error Error qTox couldn't open your chat logs, they will be disabled. qTox no pudo abrir tus historiales de chat, serán deshabilitados. Reformatting text in progress.. Reformateo de texto en progreso... Starts new instance and opens the login screen. Inicia una nueva instancia y abre la pantalla de inicio de sesión. Dark Oscuro Dark blue Azul oscuro Dark olive Verde oscuro Dark red Rojo oscuro Dark violet Morado Failed to load profile automatically. No se pudo cargar el perfil automáticamente. online contact status conectado away contact status ausente busy contact status ocupado offline contact status desconectado blocked contact status bloqueado RemoveFriendDialog Remove friend Eliminar amigo Also remove chat history Eliminar también el historial del chat Remove Eliminar Are you sure you want to remove %1 from your contacts list? ¿Estás seguro de que deseas eliminar a %1 de tu lista de contactos? Remove all chat history with the friend if set Eliminar todo el historial del chat con el amigo si está establecido ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Cliquea y arrastra para seleccionar una región. Presiona %1 para mostar/ocultar la ventana de qTox, o %2 para cancelar. Space [Space] key on the keyboard Espacio Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Presiona %1 para enviar una captura de pantalla de la selección, %2 para mostar/ocultar la ventana de qTox, o %3 para cancelar. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Texto no encontrado. Start Iniciar SearchSettingsForm Form Formulario Start search: Iniciar búsqueda: from the end desde el final from the beginning desde el principio after date después de la fecha before date antes de la fecha 00.00.0000 00/00/0000 Case sensitive Distingue mayúsculas y minúsculas Whole words only Sólo palabras enteras Use regular expressions Usar expresiones comunes SetPasswordDialog Set your password Establecer tu contraseña The password is too short La contraseña es muy corta The password doesn't match. La contraseña no coincide. Confirm: Confirmar: Password: Contraseña: Password strength: %p% Robustez de la contraseña: %p% Confirm password Confirmar contraseña Confirm password input Confirmar contraseña introducida Password input Introducir contraseña Password input field, minimum 6 characters long Campo para introducir contraseña, con una longitud mínima de 6 caracteres Settings Circle #%1 Círculo #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Agregar un amigo Do you want to add %1 as a friend? ¿Deseas agregar a %1 como amigo? User ID: ID del usuario: Friend request message: Mensaje de solicitud de amistad: Send Send a friend request Enviar Cancel Don't send a friend request Cancelar UserInterfaceForm None Ninguno User Interface Interfaz de usuario UserInterfaceSettings Chat Chat Base font: Fuente: px px Size: Tamaño: New text styling preference may not load until qTox restarts. Las nuevas preferencias de estilo de texto pueden requerir que reinicies qTox para ser activadas. Text Style format: Estilo de formato de texto: Select text styling preference. Seleccionar el estilo en que el texto va a ser mostrado. Plaintext Texto plano Show formatting characters Mostrar caracteres de formato Don't show formatting characters No mostrar caracteres de formato New message Mensaje nuevo Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Abrir una ventana de qTox al recibir un nuevo mensaje si no hay ninguna ya abierta. Open window Abrir ventana Contact list Lista de contactos If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Mostrar chat grupales al inicio de la lista, en vez del final. Place groupchats at top of friend list Chats grupales al comienzo de la lista de amigos Your contact list will be shown in compact mode. toolTip for compact layout setting Mostrar tu lista de amigos en modo compacto. Compact contact list Lista compacta de amigos Multiple windows mode Modo multiventana Open each chat in an individual window Abrir una nueva ventana para cada chat Emoticons Emoticones Use emoticons Utilizar emoticones Smiley Pack: Text on smiley pack label Paquete de emoticones: Emoticon size: Tamaño de emoticon: px px Theme Tema Style: Estilo: Theme color: Color del tema: Timestamp format: Formato de las marcas temporales: Date format: Formato de la fecha: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Si está activado, cada contacto sin un avatar tendrá un avatar generado según su ID de Tox en lugar de una imagen predeterminada. Requiere reiniciar para aplicar. Use identicons instead of empty avatars Utilice identicones en lugar de avatares vacíos Use colored nicknames in chats Usar apodos coloridos en los chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Mostrar una notificación cuando reciba un nuevo mensaje y la ventana no esté seleccionada. Notify Notificar Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Sólo notificar sobre nuevos mensajes en los chat de grupo cuando seas mencionado. Group chats only notify when mentioned Sólo notificar de chat de grupo cuando seas mencionado Play sound Reproducir audio Play sound while Busy Reproducir sonido mientras Ocupado Notify via desktop notifications Notificar a través de notificaciones de escritorio Hide message sender and contents Ocultar el remitente y el contenido del mensaje Widget Online Conectados Online Button to set your status to 'Online' Conectado Away Button to set your status to 'Away' Ausente Busy Button to set your status to 'Busy' Ocupado toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Se produjo un error al iniciar toxcore con la configuración actual de proxy. Por favor modifica la configuración y reinicia qTox. Create new group... Crear un nuevo grupo... Add new circle... Agregar un nuevo círculo... %n New Friend Request(s) %n Nueva solicitud de amistad %n Nuevas solicitudes de amistad %n New Group Invite(s) %n Nueva invitación a grupo %n Nuevas invitaciones a grupos By Name Por nombre By Activity Por actividad All Todos Offline Desconectados Friends Amigos Groups Grupos Search Contacts Buscar amigos File Archivo Edit Profile Editar perfil Change Status Cambiar estado Log out Cerrar sesión Edit Editar Filter... Filtrar... Contacts Amigos Add Contact... Agregar amigo... Next Conversation Siguiente conversación Previous Conversation Conversación previa Executable file popup title Archivo ejecutable You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Has seleccionado que qTox abra un archivo ejecutable. Los archivos ejecutables pueden ser dañinos para tu computador. ¿Estás seguro de que quieres abrirlo? Couldn't request friendship No se pudo solicitar amistad toxcore failed to start, the application will terminate after you close this message. Se produjo un error al iniciar toxcore; el programa terminará al cerrar este mensaje. Your name Tu nombre Status Estado Message failed to send Falló el envío del mensaje Logout Tray action menu to logout user Cerrar sesión Exit Tray action menu to exit tox Salir Groupchat #%1 Chat grupal #%1 Show Tray action menu to show qTox window Mostrar Add friend title of the window Agregar amigo Group invites title of the window Invitaciones a grupos File transfers title of the window Transferencias de archivos Settings title of the window Opciones My profile title of the window Mi perfil Failed to send file "%1" No se pudo enviar el archivo "%1" File sent Archivo enviado sent you a friend request. le envió una solicitud de amistad. invites you to join a group. le invita a unirse a un grupo. qTox/translations/et.ts000066400000000000000000003256321415623743500155370ustar00rootroot00000000000000 AVForm Default resolution Vaikelahutus Audio/Video Heli/Video Disabled Välja lülitatud Select region Vali ala Screen %1 Ekraan %1 Audio Settings Heliseaded Gain Võimendus Playback device Mahamängiv seade Use slider to set volume of your speakers. Kasuta liugurit, et seada kõlarite helitaset. Capture device Salvestav seade Volume Helitugevus Video Settings Videoseaded Video device Videoseade Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Määra oma kaamera lahutus. Mida kõrgemad on väärtused, seda kõrgema kvaliteediga pilt võib sinu sõpradeni jõuda. Pea siiski meeles, et kvaliteetsem pilt eeldab ka kiiremat internetiühendust. Vahel võib sinu ühendus olla hea videopildi edastamiseks liialt vilets, mis omakorda võib tekitada videokõnede pidamisel probleeme. Resolution Lahutus Rescan devices Otsi seadmeid Test Sound Testi heli Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Lubab eksperimentaalse, kaja summutava audio toe; aktiveerimiseks tuleb qTox uuesti käivitada. Enable experimental audio backend Luba eksperimentaalne audiotugi Audio quality Heli kvaliteet Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Saadetava heli kvaliteet. Vähenda, kui sulle eraldatud ribalaius ei ole piisav või sa soovid vähendada Interneti ühenduse kasutust. High (64 kbps) Kõrge (64 kbit/s) Medium (32 kbps) Keskmine (32 kbit/s) Low (16 kbps) Madal (16 kbit/s) Very low (8 kbps) Väga madal (8 kbit/s) Threshold Lävi AboutForm About Programmist lähemalt Original author: %1 Esialgne autor: %1 You are using qTox version %1. Kasutad qToxi versiooni %1. Commit hash: %1 Commit räsi: %1 toxcore version: %1 toxcore'i versioon: %1 Qt version: %1 Qt versioon: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Nimekirja kõikidest teadaolevatest probleemidest saab näha meie %1, mis asub keskkonnas nimega Github. Kui avastad qToxis vea või turvaaugu, anna sellest teada vastavalt meie %2 wiki artiklis antud juhistele. Click here to report a bug. Veast teatamiseks klõpsa siia. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Vaata %1 täielikku loendit keskkonnas Github bug-tracker Replaces `%1` in the `A list of all known…` veahaldussüsteem Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Asjalike vearaportite koostamine (inglise keeles) contributors Replaces `%1` in `See a full list of…` abilised AboutFriendForm Dialog Dialoog username kasutajanimi status message olekuteade Used aliases: Kasutatud hüüdnimed: HISTORY OF ALIASES HÜÜDNIMEDE AJALUGU Automatically accept files from contact if set Kui valitud, võetakse failid antud kontaktilt automaatselt vastu Auto accept files Võta failid automaatselt vastu Default directory to save files: Failide salvestamise vaikekaust: Auto accept for this contact is disabled Failide automaatne vastuvõtt sellelt kontaktilt on keelatud Auto accept call: Võta kõne automaatselt vastu: Manual Käsitsi Audio Heli Audio + Video Heli + video Automatically accept group chat invitations from this contact if set. Kui on valitud, siis nõustu automaatselt grupivestluste kutsetega sellelt kontaktilt. Auto accept group invites Nõustu automaatselt grupikutsetega Remove history (operation can not be undone!) Eemalda ajalugu (toimingut ei saa hiljem tühistada) Notes Märkmed Input field for notes about the contact Väli, kuhu saab kontakti kohta lisada märkmeid You can save comment about this contact here. Siia saad selle kontakti kohta kommentaare kirjutada. History removed Ajalugu kustutatud Choose an auto accept directory popup title Vali automaatselt vastuvõetavate failide salvestuskaust <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>See on su sõbra avalik võti. Kasuta seda tema tuvastamiseks mõne teise kanali kaudu. Sa ei saa seda saata teistele inimestele, et nad saaksid lisata ta kontaktiks.</p></body></html> Public key (not ToxID): Avalik võti (mitte ToxID): Confirmation Kinnitus Are you sure to remove %1 chat history? Kas soovite kindlasti eemaldada vestlusajaloo partneriga %1? Failed to remove chat history with %1! Partneri %1 vestlusajaloo eemaldamine nurjus! AboutSettings Version Versioon License Litsents Authors Autorid Known Issues Teadaolevad probleemid Open update download link Ava värskenduse allalaadimise link Update available Värskendus on saadaval qTox is up to date ✓ qTox on ajakohane ✓ AddFriendForm Couldn't add friend Sõbra lisamine ebaõnnestus Invalid Tox ID format Vigane Tox ID vorming Add Friends Lisa sõpru Send friend request Saada sõbrakutse Add a friend Lisa sõber Friend requests Kutsed sõpradelt Accept Nõustu Reject Keeldu Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, kas 76 kuueteistkümnendsüsteemi märki või nimi@mingiserver.com Type in Tox ID of your friend Sisesta oma sõbra Tox ID Friend request message Sõbrakutsele lisatav sõnum Type message to send with the friend request or leave empty to send a default message Sisesta sõbrakutse sõnum, või jäta tühjaks, et saata vaikimisi kutse %1 Tox ID is invalid or does not exist Toxme error Tox ID %1 on vigane või seda ei ole olemas You can't add yourself as a friend! When trying to add your own Tox ID as friend Ennast pole võimalik sõbraks lisada! Open contact list Ava kontaktide loend Couldn't open file Faili ei saanud avada Couldn't open the contact file Error message when trying to open a contact list file to import Kontaktide faili ei saanud avada Invalid file Vigane fail We couldn't find any contacts to import in this file! Imporditavaid kontakte ei suudetud selle failist leida! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description kas 76 kuueteistkümnendsüsteemi märki või nimi@mingiaadress.com Message The message you send in friend requests Sõnum Open Button to choose a file with a list of contacts to import Ava Send friend requests Saatke sõbrakutseid %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 siin! Ehk liitud minuga Tox keskkonnas? Import a list of contacts, one Tox ID per line Impordi kontaktide loend, üks Tox ID real Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) %n kontakti importimiseks valmis, kinnitamiseks vajuta saada %n kontakti importimiseks valmis, kinnitamiseks vajuta saada Import contacts Impordi kontaktid AdvancedForm Advanced Edasijõudnud Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Kui sa %1 tea, mida teed, siis palun %2 muuda siin midagi. Siin tehtud muudatused võivad põhjustada probleeme qToxi töös ja isegi sinu andmete, nt ajaloo, kadumise. really tõesti not ära IMPORTANT NOTE TÄHTIS TEADE Reset settings Lähtesta seaded All settings will be reset to default. Are you sure? Taastatakse vaikeseaded. Kas oled kindel, et tahad seda teha? Yes Jah No Ei Call active popup title Kõne käib You can't disconnect while a call is active! popup text Sa ei saa võrgust lahkuda, kui kõne käib! Save File Salvesta fail Logs (*.log) Logid (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Salvesta seaded vaikekausta asemel töökausta Make Tox portable Muuda Tox teisaldatavaks Reset to default settings Taasta vaikeseaded Portable Teisaldatav Connection Settings Ühenduse seaded Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Toeta IPv6 protokolli (soovitatav) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Selle väljalülitamine võimaldab näiteks kasutada toxi üle Tor võrgu. Samas koormab väljalülitamine Toxi võrku, seega tee seda vaid vajaduse korral. Enable UDP (recommended) Text on checkbox to disable UDP Toeta UDP protokolli (soovitatav) Proxy type: Puhverserveri tüüp: Address: Text on proxy addr label Aadress: Port: Text on proxy port label Port: None Määramata SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Ühendu uuesti Debug Veaotsing Export Debug Log Ekspordi veaotsingu logifail Copy Debug Log Kopeeri veaotsingu logifail Enable LAN discovery Luba kohtvõrgu tuvastamine ChatForm Send a file Saada fail Unable to open Avamine luhtus qTox wasn't able to open %1 qTox ei suutnud avada faili %1 Bad idea See on vilets mõte %1 calling %1 helistab Calling %1 Helistan kasutajale %1 Failed to open temporary file Temporary file for screenshot Ei suutnud avada ajutist faili qTox wasn't able to save the screenshot qTox ei suutnud ekraanitõmmist salvestada Call with %1 ended. %2 Kõne kasutajaga %1 lõppes. %2 Call duration: Kõne kestvus: %1 is typing %1 on kirjutamas Copy Kopeeri You're trying to send a sequential file, which is not going to work! Sa üritad saata järjestikfaili, kuid see ei ole võimalik! %1 is now %2 e.g. "Dubslow is now online" %1 on nüüd %2 Call with %1 ended unexpectedly. %2 Kõne %1-ga lõppes ootamatult. %2 Filename contained illegal characters Failinimi sisaldas keelatud märke Illegal characters have been changed to _ so you can save the file on windows. Keelatud märgid asendati märgiga _ , et saaksid faili Windowsis salvestada. ChatFormHeader Can't start audio call Häälkõnet ei saa alustada Start audio call Alusta häälkõnet End audio call Lõpeta häälkõne Cancel audio call Katkesta häälkõne Accept audio call Võta häälkõne vastu Can't start video call Videokõnet ei saa alustada Start video call Alusta videokõnet End video call Lõpeta videokõne Cancel video call Katkesta videokõne Accept video call Võta videokõne vastu Sound can be disabled only during a call Heli saab välja lülitada vaid kõne ajal Unmute call Lülita kõne heli sisse Mute call Vaigista kõne Microphone can be muted only during a call Mikrofoni saab vaigistada vaid kõne ajal Unmute microphone Lülita mikrofon sisse Mute microphone Vaigista mikrofon ChatLog pending ootel Copy Kopeeri Select all Vali kõik ChatTextEdit Type your message here... Kirjuta siia oma teade... CircleWidget Rename circle Menu for renaming a circle Nimeta suhtlusring ümber Remove circle Menu for removing a circle Eemalda suhtlusring Open all in new window Ava kõik uues aknas Core /me offers friendship, "%1" peaks tõlkima? /me pakub sõprust, "%1" Invalid Tox ID Error while sending friendship request Vale Tox ID You need to write a message with your request Error while sending friendship request Pead oma kutsele ka teate lisama Your message is too long! Error while sending friendship request Sinu teade on liialt pikk! Friend is already added Error while sending friendship request Sõber on juba lisatud Groupchat %1 Grupivestlus %1 DesktopNotify New message Uus sõnum Incoming file transfer Sissetulev failiedastus Friend request received Sõbrakutse on vastu võetud New group message Uus grupisõnum Group invite received Grupikutse on vastu võetud FileTransferWidget Form Aken 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 EPA:10:10 Filename Faili nimi Waiting to send... file transfer widget Ootan, et saata... Accept to receive this file file transfer widget Nõustu, et faili vastu võtta Location not writable Title of permissions popup Asukohta ei saa kirjutada You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Sul ei ole sellesse asukohta kirjutamiseks õigusi. Vali mõni muu või katkesta salvestamine. Paused file transfer widget Pausil Resuming... file transfer widget Jätkan... Open file Ava fail Open file directory Ava faili sisaldav kaust Pause transfer Pane ülekanne pausile Cancel transfer Katkesta ülekanne Resume transfer Jätka ülekannet Accept transfer Nõustu ülekandega Save a file Title of the file saving dialog Salvesta fail Remote Paused file transfer widget Partner peatatud FilesForm Transferred Files "Headline" of the window Ülekantud failid Downloads Allalaadimised Uploads Üleslaadimised FriendListWidget Today Täna Yesterday Eile Last 7 days Viimased 7 päeva This month Sel kuul Older than 6 Months Vanemad kui 6 kuud Never Mitte kunagi FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Sõbrakutse Someone wants to make friends with you Keegi tahab sinu sõbraks hakata User ID: Kasutaja ID: Friend request message: Sõbrakutsele lisatud teade: Accept Accept a friend request Nõustu Reject Reject a friend request Keeldu FriendWidget Open chat in new window Ava vestlus uues aknas Remove chat from this window Haagi vestlus sellest aknast lahti Invite to group Menu to invite a friend to a groupchat Kutsu gruppi Move to circle... Menu to move a friend into a different circle Liiguta suhtlusringi... To new circle Uude suhtlusringi Remove from circle '%1' Eemalda suhtlusringist nimega %1 Move to circle "%1" Liiguta suhtlusringi nimega %1 Set alias... Sea hüüdnimi... Auto accept files from this friend context menu entry Võta selle sõbra failid automaatselt vastu Remove friend Menu to remove the friend from our friendlist Eemalda sõber Show details Näita üksikasju Choose an auto accept directory popup title Vali kaust, kuhu automaatselt vastuvõetavad failid paigutatakse New message Uus sõnum Online Vestlusvalmis Away Eemal Busy Hõivatud Offline Ühendamata To new group Uude gruppi Invite to group '%1' Kutsu gruppi „%1“ GeneralForm Choose an auto accept directory popup title Vali kaust, kuhu automaatselt vastuvõetavad failid laetakse General Üldine GeneralSettings General Settings Üldseaded The translation may not load until qTox restarts. Tõlke aktiveerimiseks tuleb qTox võib-olla uuesti käivitada. Language: Keel: Start qTox on operating system startup (current profile). Käivita qTox koos operatsioonisüsteemiga (kasutades käesolevat profiili). Autostart Automaatkäivitus Enable light tray icon. toolTip for light icon setting Kasuta süsteemisalves heledat ikooni. Light icon Hele ikoon Show system tray icon Kuva süsteemisalve ikooni qTox will start minimized in tray. toolTip for Start in tray setting qTox on käivitudes nähtav vaid süsteemisalves. Start in tray Käivita süsteemisalves After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Pärast akna vähendamise nupu (_) vajutamist ei taandu qTox mitte tegumiribale, vaid süsteemisalve. Minimize to tray Vähenda süsteemisalve After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Pärast sulgemisnupu (X) vajutamist taandub qTox süsteemisalve, mitte ei sulgu. Close to tray Sulgemisel taandu süsteemisalve Your status is changed to Away after set period of inactivity. Sinu olekuks märgitakse pärast määratud aja möödumist "Eemal". Auto away after (0 to disable): Märgi olekuks "eemal", kui olen eemal (eiramiseks kirjuta 0): Set to 0 to disable Funktsiooni väljalülitamiseks sea väärtuseks 0 Set where files will be saved. Määra, kuhu failid salvestada. Default directory to save files: Vaikekaust failide salvestamiseks: You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Sõbral paremklõpsu tehes saab seda seadistust iga kontakti puhul eraldi muuta. Autoaccept files Nõustu automaatselt failide vastuvõtmisega Show contacts' status changes Näita muutusi kontaktide olekus Check for updates Kontrolli värskendusi Spell checking Õigekirja kontroll Max autoaccept file size (0 to disable): Automaatselt akepteeritud faili maksimaalne suurus (0 - piiranguteta): MB MB GenericChatForm Save chat log Salvesta vestluse logi Cleared Eemaldatud Send message Saada sõnum Smileys Emotikonid Send file(s) Saada fail(e) Send a screenshot Saada kuvatõmmis Clear displayed messages Eemalda kuvatud teated Quote selected text Tsiteeri valitud tekst Copy link address Kopeeri viida aadress Confirmation Kinnitus You are sure that you want to clear all displayed messages? Kas soovite kindlasti kustutada kõik kuvatud sõnumid? Search in text Otsi tekstist Go to current date Mine tänasele kuupäevale Load chat history... Laadi vestluse ajalugu... Export to file Ekspordi faili GenericNetCamView Tox video Tox video Show Messages Kuva sõnumeid Hide Messages Peida sõnumid Full Screen Täisekraan Toggle video preview Video eelvaate lülitamine Mute audio Vaigista heli Mute microphone Vaigista mikrofon End video call Lõpeta videokõne Exit full screen Välju täisekraanilt GroupChatForm %1 has set the title to %2 %1 seadis pealkirjaks %2 %1 has joined the group %1 liitus grupiga %1 is now known as %2 %1 on nüüdsest tuntud kui %2 %1 has left the group %1 lahkus grupist %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Grupid Create new group Loo uus grupp Group invites Grupikutsed GroupInviteWidget Invited by %1 on %2 at %3. Kutse edastas %1 kuupäeval %2 kl %3. Join Ühine Decline Keeldu GroupWidget Open chat in new window Ava vestlus uues aknas Remove chat from this window Haagi vestlus sellest aknast lahti Set title... Sea pealkiri... Quit group Menu to quit a groupchat Lahku grupivestlusest %n user(s) in chat Number of users in chat New Message Online Vestlusvalmis IdentitySettings Public Information Avalik teave Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip See märgijada annab teistele Tox võrgu liikmetele teada, kuidas sinuga ühendust saada. Sõpradega suhtlemiseks jaga seda nendega. Your Tox ID (click to copy) Sinu Tox ID (klõpsa, et lõikemälusse kopeerida) This QR code contains your Tox ID. You may share this with your friends as well. See QR kood sisaldab sinu Tox ID-d. Sa võid ka seda sõpradega jagada. Save image Salvesta pilt Copy image Kopeeri pilt lõikemällu Profile Profiil Rename profile. tooltip for renaming profile button Nimeta profiil ümber. Rename rename profile button Nimeta ümber Delete profile. delete profile button tooltip Kustuta profiil. Delete delete profile button Kustuta Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Võimaldab kopeerida sinu Toxi profiili faili. Profiil ei sisalda vestluste ajalugu. Export export profile button Ekspordi Go back to the login screen tooltip for logout button Mine tagasi sisselogimise aknasse Logout import profile button Logi välja Remove password Eemalda salasõna Change password Muuda salasõna Server Server Hide my name from the public list Ära kuva minu nime avalikus nimekirjas Register Registreeri Your password Sinu salasõna Update Värskenda Register on ToxMe Registreeri end ToxMe's Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Kasutajanimi ToxMe teenuses. Optional. Something about you. Or your cat. Tooltip for the Biography text. Valikuline. Midagi sinust. Või su kassist. Optional. Something about you. Or your cat. Tooltip for the Biography field. Valikuline. Midagi sinust. Või su kassist. ToxMe service to register on. ToxMe teenus, kuhu registreeruda. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Kui märget ei ole, on su nimi ToxMe loendis kõigile näha. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Eemalda oma profiilist parool ja krüpteerimine. Name input Nime sisestamine Name visible to contacts Kontaktidele nähtav nimi Status message input Olekusõnumi sisestamine Status message visible to contacts Kontaktidele kuvatav olekusõnum Your Tox ID Sinu Tox ID Save QR image as file Salvesta QR kujutis faili Copy QR image to clipboard Kopeeri QR kujutis lõikelauale ToxMe username to be shown on ToxMe ToxMe kasutajanimi, mida kuvada ToxMes Optional ToxMe biography to be shown on ToxMe Valikuline ToxMe elulugu, mida kuvada ToxMes ToxMe service address ToxMe teenuse aadress Visibility on the ToxMe service Kuvamine ToxMe teenuses Password Salasõna Update ToxMe entry Värskenda ToxMe kirjet Rename profile. Nimeta profiil ümber. Delete profile. Kustuta profiil. Export profile Ekspordi profiil Remove password from profile Kustuta parool profiilist Change profile password Muuda profiili salasõna My name: Minu nimi: My status: Minu olek: My username Minu kasutajanimi My biography Minu elulugu My profile Minu profiil LoadHistoryDialog Load History Dialog Vestluste ajaloo laadimise aken Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Kasutajanimi: Password: Salasõna: Confirm: Korda salasõna: Password strength: %p% Salasõna tugevus: %p% Create Profile Loo profiil If the profile does not have a password, qTox can skip the login screen Kui profiil pole salasõnaga kaitstud, võib qTox sisselogimise aknast mööda minna, kui seda soovid Load automatically Lae automaatselt Load Lae New Profile Uus profiil Load Profile Lae profiil Couldn't create a new profile Uue profiili loomine ebaõnnestus The username must not be empty. Kasutajanimi ei tohi olla tühi. The password must be at least 6 characters long. Salasõna peab olema vähemalt 6 märki pikk. The passwords you've entered are different. Please make sure to enter same password twice. Sisestatud salasõnad ei kattu. Palun veendu, et sisestad mõlemal korral sama salasõna. A profile with this name already exists. Selle nimega profiil on juba olemas. Couldn't load profile Profiili laadimine luhtus There is no selected profile. You may want to create one. Profiili pole valitud. Võimalik, et peaksid selle looma. Couldn't load this profile Selle profiili laadimine luhtus This profile is already in use. Seda profiili juba kasutatakse. Wrong password. Vale salasõna. Import Impordi Password protected profiles can't be automatically loaded. Salasõnaga kaitstud profiile ei saa automaatselt laadida. Username input field Kasutajanime sisestusväli Password input field, you can leave it empty (no password), or type at least 6 characters Salasõna sisestusväli. Jäta tühjaks (ilma paroolita), või kirjuta vähemalt 6 tähemärki Password confirmation field Salasõna kinnituse väli Create a new profile button Uue profiili loomise nupp Profile list Profiilide loend List of profiles Profiilide loend Password input Salasõna sisestamine Load automatically checkbox Laadi automaatselt märkekast Import profile Impordi profiil Load selected profile button Laadi valitud profiili nupp New profile creation page Uue profiili loomise leht Loading existing profile page Laadi olemasoleva profiili leht MainWindow Your name Sinu nimi Your status Olekuteade ... ... Add friends Lisa sõpru Create a group chat Loo grupivestllus View completed file transfers Kuva lõpetatud failiülekandeid Change your settings Muuda oma seadeid Close Sulge Open profile Ava profiil Open profile page when clicked Klõpsamisel ava profiili leht Status message input Olekusõnumi sisestamine Set your status message that will be shown to others Määra teistele kuvatav olekusõnum Status Olek Set availability status Määra saadavuse olek Contact search Kontaktide otsing Contact search input for known friends Tuntud sõprade kontakti otsingu sisend Sorting and visibility Sortimine ja nähtavus Set friends sorting and visibility Määra sõprade sortimine ja nähtavus Open Add friends page Ava sõprade lisamise leht Groupchat Grupivestlus Open groupchat management page Ava grupivestluste haldamise leht File transfers history Failide edastamise ajalugu Open File transfers history Ava failide edastamise ajalugu Settings Seaded Open Settings Ava seaded Nexus View OS X Menu bar Vaade Window OS X Menu bar Aken Minimize OS X Menu bar Vähenda Bring All to Front OS X Menu bar Too kõik esile Exit Fullscreen Ära kuva üle kogu ekraani Enter Fullscreen Kuva üle kogu ekraani NotificationEdgeWidget Unread message(s) Parandada Lugemata sõnum Lugemata sõnumit PasswordEdit CAPS-LOCK ENABLED SUURTÄHED ON AKTIVEERITUD PrivacyForm Confirmation Kinnitus Do you want to permanently delete all chat history? Kas soovite vestluste ajalugu jäädavalt kustutada? Privacy Privaatsus PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Sinu sõbrad näevad, kui sa kirjutad. Send typing notifications Saada kirjutamise kohta signaal Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Vestluste ajaloo logimine on veel arendatav funktsioon. Võimalik, et salvestamise vorming muutub, mis võib omakorda kaasa tuua andmekao. Keep chat history Säilita vestluste ajalugu NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam kuulub sinu ID juurde. Kui saad veidraid sõbrakutseid, peaksid NoSpam väärtust muutma. Inimesed ei saa sind seejärel enam sinu vana ID-d kasutades sõbraks lisada, ent sinu praegused kontaktid säilivad. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam on osa sinu ID stringist, mida saab soovi korral muuta. Kui saad hulgaliselt soovimatuid sõbrakutseid, muuda seda väärtust. Generate random NoSpam Loo juhuslik NoSpam Privacy Privaatsus BlackList Must nimekiri Filter group message by group member's public key. Put public key here, one per line. Filtreeri grupi sõnum grupiliikme avaliku võtme alusel. Sisesta avalik võti siia, üks rea kohta. Profile Failed to derive key from password, the profile won't use the new password. Salasõnast ei suudetud võtit tuletada; profiil ei kasuta uut salasõna. Couldn't change password on the database, it might be corrupted or use the old password. Salasõna vahetamine andmebaasis nurjus; andmebaas võib olla rikutud või kasutada vana salasõna. Toxing on qTox Toxib rakendusega qTox ProfileForm Current profile: Käesolev profiil: Remove Eemalda Choose a profile picture Vali profiilipilt Error Viga Unable to open this file. Ei suutnud seda faili avada. Unable to read this image. Ei suuda seda pilti lugeda. The supplied image is too large. Please use another image. Pakutud pilt on liialt suur. Palun kasuta teist pilti. Rename "%1" renaming a profile Nimeta "%1" ümber Couldn't rename the profile to "%1" Ei suutnud profiili kujule "%1" ümber nimetada Location not writable Title of permissions popup Asukoht pole kirjutatav You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Sul ei ole sellesse asukohta kirjutamiseks õigusi. Vali mõni muu või katkesta salvestamine. Failed to copy file Faili kopeerimine luhtus The file you chose could not be written to. Faili, mille sa valisid, ei ole võimalik kirjutada. Really delete profile? deletion confirmation title Kas soovid tõesti profiili kustutada? Are you sure you want to delete this profile? deletion confirmation text Oled kindel, et soovid seda profiili kustutada? Save save qr image Salvesta Save QrCode (*.png) save dialog filter Salvesta Qr kood (*.png) Nothing to remove Pole midagi eemaldada Your profile does not have a password! Sinu profiil pole salasõnaga kaitstud! Really delete password? deletion confirmation title Soovid tõesti salasõna eemaldada? Please enter a new password. Palun sisesta uus salasõna. Files could not be deleted! deletion failed title Faile ei suudetud kustutada! Register (processing) Registreerimine (töötlemisel) Update (processing) Uuendamine (töötlemisel) Done! Valmis! Account %1@%2 updated successfully Konto %1@%2 väskendus oli edukas Successfully added %1@%2 to the database. Save your password %1@%2 lisati edukalt andmebaasi. Salvesta oma salasõna Toxme error Toxme viga Register Registreeri Update Värskenda Change password button text Muuda salasõna Set profile password button text Määra profiili salasõna Current profile location: %1 Praeguse profiili asukoht: %1 Couldn't change password Salasõna vahetamine nurjus This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). See kogumik märke ütleb teistele Toxi klientidele, kuidas sinuga ühenduda. Oma sõpradega suhtlemiseks jaga seda neile. See ID sisaldab NoSpam koodi (sinine) ja kontrollsumma (hall). Empty path is unavaliable Tühja otsingurada pole Failed to rename Ümbernimetamine luhtus Profile already exists Profiil on juba olemas A profile named "%1" already exists. Profiil "%1" on juba olemas. Empty name Tühi nimi Empty name is unavaliable Tühja nime pole Empty path Tühi rada Couldn't change password on the database, it might be corrupted or use the old password. Salasõna vahetamine andmebaasis nurjus; andmebaas võib olla rikutud või kasutada vana salasõna. Export profile Ekspordi profiil Tox save file (*.tox) save dialog filter Tox profiili fail (*.tox) The following files could not be deleted: deletion failed text part 1 Alljärgnevate failide kustutamine nurjus: Please manually remove them. deletion failed text part 2 Palun kustuta need käsitsi. Are you sure you want to delete your password? deletion confirmation text Oled sa kindel, et soovid oma salasõna kustutada? Images (%1) filetype filter Pildid (%1) ProfileImporter Import profile import dialog title Impordi profiil Tox save file (*.tox) import dialog filter Tox profiili fail (*.tox) Ignoring non-Tox file popup title Eiran mitte-Toxi faili Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Hoiatus: faili eratakse, sest oled valinud faili, mida Tox ei ole salvestanud. Profile already exists import confirm title Profiil on juba olemas A profile named "%1" already exists. Do you want to erase it? import confirm text Profiil nimega "%1" on juba olemas. Soovid seda kustutada? File doesn't exist Faili pole olemas Profile doesn't exist Profiili pole olemas Profile imported Profiil imporditi %1.tox was successfully imported %1.tox imporditi edukalt QApplication Ok Olgu Cancel Katkesta Yes Jah No Ei LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout V→P QMessageBox Couldn't add friend Sõbra lisamine ebaõnnestus %1 is not a valid Toxme address. %1 pole õige Toxme aadress. You can't add yourself as a friend! When trying to add your own Tox ID as friend Ennast pole võimalik sõbraks lisada! QObject Tox URI to parse Toxi URI, mida kasutada Starts new instance and loads specified profile. Pole parim tõlge Käivitab uue üksuse ja laeb määratud profiili. profile profiil Server doesn't support Toxme Serveril puudub Toxme tugi You're making too many requests. Wait an hour and try again Teed liiga palju päringuid. Oota tunnike ja proovi uuesti This name is already in use See nimi on juba kasutuses This Tox ID is already registered under another name See Tox ID on juba teise nime all registreeritud Please don't use a space in your name Palun ära kasuta oma nimes tühikut Password incorrect Salasõna on vale You can't use this name Sa ei saa seda nime kasutada Name not found Nime ei leitud Tox ID not sent Tox ID-d ei saadetud That user does not exist Seda kasutajat pole olemas %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 siin! Ehk liitud minuga Tox keskkonnas? Error Viga qTox couldn't open your chat logs, they will be disabled. qTox ei suutnud sinu vestluste logisid avada, need deaktiveeritakse. None No camera device set Määramata Desktop Desktop as a camera input for screen sharing Töölaud Default Vaikimisi Blue Sinine Olive Oliivikarva Red Punane Violet Violetne Incoming call... Sissetulev kõne... Problem with HTTPS connection Probleemid HTTPS ühendusega Internal ToxMe error Sisemine Toxme viga Reformatting text in progress.. Toimub teksti vormindamine... Starts new instance and opens the login screen. Käivitab uue koopia ja avab sisselogimise ekraani. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status vestlusvalmis away contact status eemal busy contact status hõivatud offline contact status ühendamata blocked contact status RemoveFriendDialog Remove friend Eemalda sõber Also remove chat history Kustuta ka vestluste ajalugu Remove Eemalda Are you sure you want to remove %1 from your contacts list? Oled kindel, et soovid %1 oma kontaktide nimekirjast eemaldada? Remove all chat history with the friend if set Kui määratud, siis eemalda sõbraga seotud vestluste ajalugu ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Ala valaimiseks klõpsa ja lohista. Vajuta %1, et qToxi aken peita/kuvada, või klahvi %2, et katkestada. Space [Space] key on the keyboard tühikut Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Vajuta %1 klahvi, et saata kuvatõmmis valitud alast, %2, et qToxi aken peita/kuvada või klahvi %3, et katkestada. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form Vorm Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Sea salasõna Confirm: Korda salasõna: Password: Salasõna: Password strength: %p% Salasõna tugevus: %p% The password is too short Salasõna on liialt lühike The password doesn't match. Salasõnad ei kattu. Confirm password Kinnita salasõna Confirm password input Kinnita salasõna sisend Password input Salasõna sisend Password input field, minimum 6 characters long Salasõna sisestusväli, vähemalt 6 tähemärki pikk Settings Circle #%1 Suhtlusring #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Lisa sõber Do you want to add %1 as a friend? Soovid sa %1 sõbraks lisada? User ID: Kasutaja ID: Friend request message: Sõbrakutsele lisatav teade: Send Send a friend request Saada Cancel Don't send a friend request Katkesta UserInterfaceForm None Määramata User Interface Kasutajaliides UserInterfaceSettings Chat Vestlus Base font: Aluskirjatüüp: px px Size: Suurus: New text styling preference may not load until qTox restarts. Uued tekstikujunduse eelistused võivad jõustuda alles siis, kui qTox käivitatakse uuesti. Text Style format: Teksti kujundus: Select text styling preference. Vali teksti kujunduse eelistus. Plaintext Lihttekst Show formatting characters Kuva vormindusmärgid Don't show formatting characters Peida vormindusmärgid New message Uus sõnum Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Ava qToxi aken, kui saabub uus sõnum ja ühtki akent pole veel avatud. Open window Ava aken Contact list Kontaktide nimistu If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Kui see valik on aktiivne, pannakse grupivestlused sõbranimekirja algusesse, vastasel juhul asetatakse need sisselogitud sõprade järele. Place groupchats at top of friend list Aseta grupivestlused sõbranimekirja algusesse Your contact list will be shown in compact mode. toolTip for compact layout setting Sinu kontaktide nimekirja kuvatakse vähendatud kujul. Compact contact list Kompaktne kontaktide nimekiri Multiple windows mode Mitme akna tugi Open each chat in an individual window Ava iga vestlus eraldi aknas Emoticons Tujunäod Use emoticons Kasuta emotikone Smiley Pack: Text on smiley pack label Emotikonide pakk: Emoticon size: Emotikonide suurus: px px Theme Teema Style: Stiil: Theme color: Teema värv: Timestamp format: Ajatempli vorming: Date format: Kuupäeva vorming: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Kui see valik on aktiivne, siis luuakse kõigile ilma avatarita kasutajatele avatar vastavalt nende Tox ID-le. Aktiveerimine eeldab programmi taaskäivitamist. Use identicons instead of empty avatars Kasuta tühjade avataride asemel identiteedipilte Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Helide esitamine Play sound while Busy Helide esitamine hõivatud olekus Notify via desktop notifications Hide message sender and contents Widget Status Olek toxcore failed to start, the application will terminate after you close this message. toxcore ei käivitunud. Kui selle teate sulgete, rakendus sulgub. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore ei suutnud sinu puhverserveri seadetega käivituda. qTox ei saa töötada; palun korrigeeri seadistust ja taaskäivita rakendus. Executable file popup title Käivitatav fail You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Oled palunud qToxil avada käivitatava faili. Sellised failid võivad teoreetiliselt sinu arvutit kahjustada. Kas oled kindel, et soovid faili avada? Your name Sinu nimi Couldn't request friendship Ei suutnud sõbrustamispalvet edastada Message failed to send Sõnumi saatmine luhtus Add new circle... Lisa uus suhtlusring... By Name Nime järgi By Activity Tegevuse järgi All Kõik Online Vestlusvalmis Offline Ühendamata Friends Sõbrad Groups Grupid Search Contacts Otsi kontaktide seast Online Button to set your status to 'Online' Vestlusvalmis Away Button to set your status to 'Away' Eemal Busy Button to set your status to 'Busy' Hõivatud Logout Tray action menu to logout user Logi välja Exit Tray action menu to exit tox Sulge Filter... Filtreeri... File Fail Edit Redigeeri Contacts Kontaktid Change Status Muuda olekut Edit Profile Muuda profiili Log out Logi välja Add Contact... Lisa kontakt... Next Conversation Järgmine vestlus Previous Conversation Eelmine vestlus Groupchat #%1 Grupivestlus #%1 Create new group... Loo uus grupp... %n New Friend Request(s) %n uus sõbra kutse %n uut sõbra kutset %n New Group Invite(s) %n uus grupi kutse %n uut gruppide kutset Show Tray action menu to show qTox window Kuva Add friend title of the window Lisa sõber Group invites title of the window Grupikutsed File transfers title of the window Failiülekanded Settings title of the window Seaded My profile title of the window Minu profiil Failed to send file "%1" Faili "%1" saatmine nurjus File sent sent you a friend request. invites you to join a group. qTox/translations/fa.ts000066400000000000000000003640111415623743500155070ustar00rootroot00000000000000 AVForm Audio/Video صدا/ویدیو Default resolution تفکیک‌پذیری پیش‌فرض Disabled غیرفعال Select region منطقه را انتخاب کنید Screen %1 صفحه نمایش %1 Audio Settings تنظیمات صدا Gain بهره تقویت Playback device دستگاه پخش Use slider to set volume of your speakers. از نوار لغزنده برای تنظیم صدای بلندگو‌های خود استفاده کنید. Capture device دستگاه ضبط Volume حجم صدا Video Settings تنظیمات ویدیو Video device دستگاه ویدیو Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. تنظیم تفکیک‌پذیری دوربین. مقدار های بیشتر باعث کیفیت بیشتر تصاویر ویدیویی می شوند. توجه داشته باشید که برای کیفیت تصویری بهتر به اتصال اینترنت بهتر نیاز دارید. گاهی اوقات ممکن است کیفیت اینترنت شما به اندازه کافی برای برقراری ارتباط ویدیویی با کیفیت مناسب نباشد. که ممکن است باعث مشکلاتی در تماس های ویدیویی شود. Resolution تفکیک‌پذیری Rescan devices بررسی دوباره دستگاه‌ها Test Sound آزمایش صدا Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. امکانات صدای آزمایشی با قابلیت حذف اکوی صدا را فعال میکند، که بعد از فعال سازی نیاز است qTox راه اندازی مجدد شود. Enable experimental audio backend فعال سازی امکانات صدای آزمایشی Audio quality کیفیت صدا Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. کیفیت صدای ارسالی. در صورتی که اینترنت پر سرعت ندارید یا میخواهید مصرف اینترنت را کاهش دهید،این مقدار را کاهش دهید. High (64 kbps) بالا (64 کیلوبیت بر ثانیه) Medium (32 kbps) متوسط (32 کیلوبیت بر ثانیه) Low (16 kbps) پایین (16 کیلوبیت بر ثانیه) Very low (8 kbps) خیلی پایین (8 کیلوبیت بر ثانیه) Threshold آستانه AboutForm About درباره Original author: %1 خالق اصلی: %1 You are using qTox version %1. شما در حال استفاده از qTox ویراست %1 هستید. Commit hash: %1 هش پروسه: %1 toxcore version: %1 نسخه toxcore: %1 Qt version: %1 نسخه Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` لیست تمامی مسایل شناسایی شده را میتوانید در %1 روی گیت هاب مشاهده کنید. اگر با خطایی مواجه میشوید یا یک خطای امنیتی در qTox پیدا میکنید، لطفا این مسایل را بر اساس راهنمای موجود در %2 به ما گزارش کنید. Click here to report a bug. برای گزارش یک خطای امنیتی اینجا کلیلک کنید. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` لیست کاملی از %1 در گیت هاب مشاهده نمایید bug-tracker Replaces `%1` in the `A list of all known…` باگ-تراکر Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` نوشتن گزارش خطای کاربردی contributors Replaces `%1` in `See a full list of…` مشارکت کنندگان AboutFriendForm Dialog دیالوگ username نام کاربری status message پیام وضعیت Used aliases: نام های مستعار استفاده شده: HISTORY OF ALIASES تاریخچه اسامی مستعار Automatically accept files from contact if set در صورت تنظیم به شکل خودکار فایل ها را از مخاطب دریافت کن Auto accept files دریافت خودکار فایلها Default directory to save files: پوشه پیش فرض ذخیره فایلها: Auto accept for this contact is disabled در صورت غیر فعال بودن به شکل خودکار برای این مخاطب قبول کن Auto accept call: به شکل خودکار تماس ها را دریافت کن: Manual دستی Audio صدا Audio + Video صدا + تصویر Automatically accept group chat invitations from this contact if set. اگر تنظیم شده است به شکل خودکار دعوت به گروه ها را از این مخاطب قبول کن. Auto accept group invites به شکل خودکار دعوت به گروه ها را قبول کن Remove history (operation can not be undone!) تاریخچه را پاک کن (این عمل غیر قابل بازگشت میباشد) Notes یادداشت ها Input field for notes about the contact مکان درج یادداشت برای مخاطب You can save comment about this contact here. شما میتوانید در این مکان برای این مخاطب کامنت بگذارید. History removed تاریخچه پاک شد Choose an auto accept directory popup title پوشه دریافت خودکار را انتخاب کنید <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>این کلید عمومی دوست شماست، از آن برای تایید هویتشان از کانال دیگری، استفاده کنید. شما نبایستی این کلید را به دیگران ارسال کنید، زیرا که آنها را قادر به افزدون این مخاطب می‌کند.</p></body></html> Public key (not ToxID): کلید عمومی (نه شناسه Tox ): Confirmation تأیید Are you sure to remove %1 chat history? آیا برای پاک کردن %1 از سابقه گفت‌و‌گو اطمینان دارید؟ Failed to remove chat history with %1! حذف سابقه گفت‌و‌گو حاوی %1 موفق نبود! AboutSettings Version نسخه License لیسانس Authors بانیان Known Issues مشکلات شناسایی شده Open update download link لینک دانلود به‌روز‌رسانی را باز کن Update available به‌روز‌رسانی در دسترس است qTox is up to date ✓ qTox به‌روز است✓ AddFriendForm Add Friends اضافه کردن دوستان Invalid Tox ID format فرمت Tox ID اشتباه است Send friend request ارسال درخواست دوستی Add a friend یک دوست اضافه کنید Friend requests درخواست های دوستی Accept پذیرش Reject رد Couldn't add friend اضافه کردن دوست موفقیت آمیز نبود Tox ID, either 76 hexadecimal characters or name@example.com Tox ID، یا باید 76 کاراکتر مبانی 16 باشد یا به صورت name@example.com ارایه شود Type in Tox ID of your friend Tox ID دوست خود را تایپ کنید Friend request message پیام درخواست دوستی Type message to send with the friend request or leave empty to send a default message پیامی که میخواهید به همراه درخواست دوستی ارسال کنید را تایپ کنید یا برای ارسال پیام پیش فرض این فیلد را خالی بگذارید %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID یا اشتباه است یا وجود ندارد You can't add yourself as a friend! When trying to add your own Tox ID as friend شما نمیتوانید خودتان را به عنوان دوست اضافه کنید! Open contact list باز کردن لیست مخاطبان Couldn't open file فایل باز نشد Couldn't open the contact file Error message when trying to open a contact list file to import فایل مخاطب باز نشد Invalid file فایل قابل قبول نیست We couldn't find any contacts to import in this file! مخاطبی برای ایمپورت کردن این فایل پیدا نشد! Tox ID Tox ID of the person you're sending a friend request to Tox ID شخصی که به او درخواست دوستی میفرستید either 76 hexadecimal characters or name@example.com Tox ID format description یا 76 کاراکتر مبنای 16 یا name@example.com Message The message you send in friend requests پیام Open Button to choose a file with a list of contacts to import باز کردن Send friend requests ارسال درخواست دوستی %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! سلام من %1 هستم! هستی تو Tox با هم باشیم؟ Import a list of contacts, one Tox ID per line ایمپورت کردن لیستی از مخاطبان، در هر خط یک Tox ID Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) در حال ایمپورت کردن %n مخاطب، در صورت تایید روی ارسال کلیک کنید Import contacts ایمپورت کردن مخاطبان AdvancedForm Advanced پیشرفته Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. در صورتی که %1 میدانید چه کار دارید میکنید، لطفا گزینه ای را در این قسمت تغیر %2 ندهید. اعمال تغیرات در این قسمت ممکن است در کار qTox مشکل ایجاد کند، و حتی منجر به از دست رفتن داده ها مانند تاریخچه گفت و گو ها شود. really واقعا not نه IMPORTANT NOTE اخطار مهم Reset settings بازگرداندن تنظیمات پیش فرض All settings will be reset to default. Are you sure? تمامی تنظیمات به حالت پیش فرض بر خواهند گشت. آیا مطمئن هستید؟ Yes بله No خیر Call active popup title تماس در جریان است You can't disconnect while a call is active! popup text شما نمیتوانید وقتی تماس در جریان است ارتباط خود را قطع کنید! Save File ذخیره کردن فایل Logs (*.log) لاگ (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox تنظیمات را در پوشه کاری ذخیره کن به جای پوشه تنظیمات Make Tox portable Tox را پرتابل کن Reset to default settings بازگشت به تنظیمات پیش فرض Portable پرتابل Connection Settings تنظیمات ارتباط Enable IPv6 (recommended) Text on a checkbox to enable IPv6 فعال سازی IPv6 (توصیه میشود) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip غیرفعال سازی این مورد امکان استفاده از Tox روی Tor را فراهم میکند. اما موجب افزایش بار کاری شبکه Tox میشود، بنابراین تنها در صورت لزوم تیک کنار این آپشن را حذف کنید. Enable UDP (recommended) Text on checkbox to disable UDP فعال سازی UDP (توصیه میشود) Proxy type: نوع پراکسی: Address: Text on proxy addr label IP آدرس: Port: Text on proxy port label پورت: None هیچکدام SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button ارتباط مجدد Debug خطایابی Export Debug Log اکسپورت کردن لاگ خطاها Copy Debug Log لاگ خطایابی را کپی کن Enable LAN discovery اکتشاف LAN را فعال کن ChatForm Send a file یک فایل ارسال کنید qTox wasn't able to open %1 qTox نتوانست %1 را باز کند Unable to open نتوانست باز کند Bad idea ایده بدی است %1 calling %1 در حال تماس Calling %1 درحال تماس گرفتن با %1 Failed to open temporary file Temporary file for screenshot باز کردن فایل موقت، موفق نبود qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox نتوانست عکس نماگرفت را ذخیره کند Call with %1 ended. %2 پایان تماس با %1. %2 Call duration: مدت تماس: %1 is typing %1 در حال نوشتن است Copy کپی You're trying to send a sequential file, which is not going to work! شما در تلاش برای ارسال یک فایل سلسله مراتبی هستید که، که امکان پذیر نیست! %1 is now %2 e.g. "Dubslow is now online" %1 الان %2 Call with %1 ended unexpectedly. %2 تماس با %1 ناگهانی و غیر منتظره تموم شد. %2 Filename contained illegal characters نام فایل حاوی کاراکتر‌های غیر‎‌مجاز است Illegal characters have been changed to _ so you can save the file on windows. کاراکتر‌های غیرمجاز به _ تغیبر یافت که بتوانید فایل را در ویندوز ذخیره کنید. ChatFormHeader Can't start audio call امکان تماس صوتی فراهم نیست Start audio call تماس صوتی را شروع کن End audio call پایان تماس صوتی Cancel audio call کنسل کردن تماس صوتی Accept audio call پذیرش تماس صوتی Can't start video call امکان تماس تصویری فراهم نیست Start video call آغاز تماس تصویری End video call پایان تماس تصویری Cancel video call کنسل کردن تماس تصویری Accept video call پذیرش تماس تصویری Sound can be disabled only during a call صدا تنها در جریان یک تماس میتواند غیرفعال شود Unmute call تماس را صدا دار کن Mute call صدای تماس را قطع کن Microphone can be muted only during a call میکروفن تنها در جریان تماس میتواند قطع شود Unmute microphone میکروفن را روشن کن Mute microphone میکروفن را خاموش کن ChatLog Copy کپی Select all همه را انتخاب کن pending در صف انتظار ChatTextEdit Type your message here... پیام خود را اینجا بنویسید... CircleWidget Rename circle Menu for renaming a circle تغیر نام حلقه Remove circle Menu for removing a circle حذف حلقه Open all in new window همه را در پنجره جدید باز کن Core /me offers friendship, "%1" /me درخواست دوستی دارد، «%1» Invalid Tox ID Error while sending friendship request شناسه Tox نامعتبر است You need to write a message with your request Error while sending friendship request باید ضمن درخواست خود یک پیام نیز ارسال نمایید Your message is too long! Error while sending friendship request پیام شما خیلی طولانی است! Friend is already added Error while sending friendship request این دوست از قبل وجود دارد Groupchat %1 گفت‌و‌گوی گروهی %1 DesktopNotify New message پیام جدید Incoming file transfer انتقال فایل ورودی Friend request received درخواست دوستی دریافت شد New group message پیام گروهی جدید Group invite received دعوت به گروه دریافت شد FileTransferWidget Form Ausgelassen فرم 10Mb Ausgelassen 10مگابایت 0kb/s Ausgelassen 0کیلوبیت بر ثانیه ETA:10:10 Ausgelassen زمان انجام کار: 10:10 Filename Ausgelassen اسم فایل Waiting to send... file transfer widget در انتظار ارسال... Accept to receive this file file transfer widget پذیرش و شروع دریافت این فایل Location not writable Title of permissions popup این مکان قابلیت نوشتن (رایت) ندارد You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup شما نمیتوانید در آن مکان بنویسید. مکان دیگری انتخاب کنید، یا دیالگ ذخیره را کنسل کنید. Resuming... file transfer widget از سر گیری... Cancel transfer کنسل کردن انتقال Pause transfer مکث کردن در انتقال Paused file transfer widget در انتظار Open file باز کردن فایل Open file directory باز کردن پوشه فایل Resume transfer ادامه انتقال Accept transfer پذیرش انتقال Save a file Title of the file saving dialog ذخیره کردن یک فایل Remote Paused file transfer widget دسترسی راه دور دچار وقفه شد FilesForm Transferred Files "Headline" of the window فایلهای انتقال یافته Downloads دانلودها Uploads آپلودها FriendListWidget Today امروز Yesterday دیروز Last 7 days 7 روز گذشته This month این ماه Older than 6 Months قدیمی تر از 6 ماه Never هیچ وقت FriendRequestDialog Friend request Title of the window to aceept/deny a friend request درخواست های دوستی Someone wants to make friends with you شخصی درخواست دوستی با شما را دارد User ID: شناسه کاربری: Friend request message: پیام درخواست دوستی: Accept Accept a friend request پذیرش Reject Reject a friend request رد FriendWidget Invite to group Menu to invite a friend to a groupchat دعوت به گروه Move to circle... Menu to move a friend into a different circle انتقال به حلقه... To new circle به یک حلقه جدید Remove from circle '%1' حذف از حلقه «%1» Move to circle "%1" انتقال به حلقه «%1» Open chat in new window بازکردن چت در یک پنجره جدید Remove chat from this window حذف چت از این پنجره To new group به یک گروه جدید Invite to group '%1' دعوت به گروه «%1» Set alias... انتخاب نام مستعار... Auto accept files from this friend context menu entry فایلهای ارسالی این دوست را به شکل خودکار پذیرش کن Remove friend Menu to remove the friend from our friendlist حذف دوست Show details نمایش جزئیات Choose an auto accept directory popup title یک پوشه برای فایلهایی که به صورت خودکار دریافت میشوند انتخاب کنید New message پیام جدید Online آنلاین Away پای سیستم نیست Busy سرش شلوغه Offline Ausgelassen دستگاهش خاموشه GeneralForm General عمومی Choose an auto accept directory popup title یک پوشه برای دریافت فایلها به شکل خودکار انتخاب کنید GeneralSettings General Settings تنظیمات عمومی The translation may not load until qTox restarts. ترجمه تا زمانی که qTox باز راه اندازی نشود بارگزاری نخواهد شد. Language: زبان: Show system tray icon آیکن کنار ساعت را نشان بده Enable light tray icon. toolTip for light icon setting آیکن کنار ساعت دارای رنگ روشن باشد. Light icon آیکن دارای رنگ روشن qTox will start minimized in tray. toolTip for Start in tray setting qTox در زمان آغاز تنها به صورت آیکن شده باشد. Start in tray در زمان آغاز تنها به صورت آیکن باشد After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting بعد از کلیک کردن روی دکمه بستن (X) برنامه به آیکن کنار ساعت انتقال بیاید، به جای اینکه کاملا بسته شود. Close to tray بستن به آیکن کنار ساعت After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting بعد از کلیک روی کوچک کردن (_) برنامه به آیکن کنار ساعت انتقال یابد، به جای اینکه در نوار برنامه ها دیده شود. Minimize to tray کوچک کردن به آیکن کنار ساعت Autostart آغاز به شکل خودکار Set where files will be saved. فایلها باید در کجا ذخیره شوند. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip این تنظیمات را میتوان برای هر دوست با راست کلیک کردن روی آنها مشخص کرد. Autoaccept files فایلها را به شکل خودکار بپذیر Set to 0 to disable به 0 تغییر بدهید تا غیر فعال شود Your status is changed to Away after set period of inactivity. وضعیت شما بعد از مدت زمان عدم فعالیتی که تنظیم میکنید به «پای سیستم نیست» تغیر خواهد کرد. Auto away after (0 to disable): به شکل خودکار وضعیت را به «پای سیستم نیست» تغیر بده (0 برای غیرفعال شدن): Show contacts' status changes تغیر وضعیت مخاطب را نشان بده Start qTox on operating system startup (current profile). راه اندازی qTox در زمان ورود به سیستم عامل (کاربر کنونی). Default directory to save files: پوشه پیش فرض ذخیره فایلها: Check for updates بررسی به‌روز‌رسانی‌ها Spell checking بررسی املا Max autoaccept file size (0 to disable): حجم بیشینه فایل برای تایید خودکار (0 برای غیر‌فعال کردن): MB مگابایت GenericChatForm Send message ارسال پیام Smileys ایموجی Send file(s) ارسال فایل(ها) Send a screenshot ارسال یک نماگرفت Save chat log ذخیره سازی لاگ چت Clear displayed messages پاک کردن پیام های نشان داده شده Cleared پاک شد Quote selected text نقل قول کردن متن انتخاب شده Copy link address کپی کردن لینک Confirmation تأیید You are sure that you want to clear all displayed messages? آیا برای حذف همگی پیام‌های نمایش‌داده‌شده اطمینان دارید؟ Search in text جست‌و‌جو در متن Go to current date به تاریخ امروز برو Load chat history... تاریخچه گفت‌و‌گو را بارگذاری کن... Export to file در یک فایل ذخیره کن GenericNetCamView Tox video ویدئو Tox Show Messages نشان دادن پیام ها Hide Messages مخفی کردن پیام ها Full Screen تمام‌صفحه Toggle video preview تغییر وضعیت پیش‌نمایش ویدیو Mute audio صدا را قطع کن Mute microphone میکروفون را قطع کن End video call قطع تماس تصویری Exit full screen خروج از حالت تمام‌صفحه GroupChatForm %1 has set the title to %2 %1 عنوان را به %2 تغییر داد %1 has joined the group %1 به گروه پیوست %1 is now known as %2 %1 اکنون با عنوان %2 شناخته می‌شود %1 has left the group %1 گروه را ترک کرد %n user(s) in chat Number of users in chat mute بی‌صدا unmute رفع حالت بی‌صدا GroupInviteForm Groups گروه ها Create new group ساخت یک گروه جدید Group invites دعوت به گروه ها GroupInviteWidget Invited by %1 on %2 at %3. توسط %1 در %2 به %3 دعوت شدید. Join ملحق شدن Decline رد کردن GroupWidget Set title... تخصیص عنوان... Open chat in new window باز کردن گفت و گو در یک پنجره جدید Remove chat from this window حذف چت از این پنجره Quit group Menu to quit a groupchat جدا شدن از گروه %n user(s) in chat Number of users in chat New Message پیام جدید Online آنلاین IdentitySettings Public Information اطلاعات عمومی Tox ID شناسه ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip این مجموعه از حروف به سایر کاربران Tox و کلاینت های آنها میگوید که چگونه با شما ارتباط برقرار کنند. برای ارتباط با دوستانتان این رشته از حروف را به آنها بدهید. Your Tox ID (click to copy) شناسه (ID) کاربری Tox شما (کلیک کنید تا کپی شود) Profile پروفایل Rename profile. tooltip for renaming profile button تغیر نام پروفایل. Go back to the login screen tooltip for logout button بازگشت به صفحه ورود Logout import profile button خروج Remove password حذف رمز Change password تغییر رمز This QR code contains your Tox ID. You may share this with your friends as well. این تصویر QR شناسه Tox شما را شامل میشود. میتوانید به جای رشته حروف این تصویر را با دوستان خود به اشتراک بگذارید. Save image ذخیره کردن تصویر Copy image کپی کردن تصویر Rename rename profile button تغییر نام Delete profile. delete profile button tooltip حذف پروفایل. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button به شما اجازه میدهد که پروفایل Tox خود را در یک فایل ذخیره کنید. این پروفایل شامل تاریخچه گفت و گو های شما نمیشود. Export export profile button ذخیره سازی Delete delete profile button حذف کردن Server سرور Hide my name from the public list اسم من را در لیست عمومی نشان نده Register ثبت نام Your password رمز شما Update به روز رسانی Register on ToxMe ثبت نام در ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. نام منتخب شما برای سرویس ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. اجباری نیست. یک عبارت تصادفی راجع به خودتان یا حیوان خانگی اتان. Optional. Something about you. Or your cat. Tooltip for the Biography field. اجباری نیست. یک عبارت تصادفی راجع به خودتان یا حیوان خانگی اتان. ToxMe service to register on. سرویس ToxMe که میخواهید روی آن ثبت نام کنید. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. اگر تنظیم نشود، اطلاعات ToxMe توسط عموم قابل روئیت خواهند بود. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. رمز و رمزنگاری را از پروفایل شخصی اتان حذف کنید. Name input ورودی اسم Name visible to contacts اسمی که توسط مخاطبان قابل روئیت خواهد بود Status message input ورودی پیام وضعیت Status message visible to contacts پیام وضعیت قابل روئیت توسط مخاطبین Your Tox ID شناسه Tox شما Save QR image as file ذخیره سازی تصویر QR در یک فایل Copy QR image to clipboard کپی کردن تصویر QR در حافظه کامپیوتر ToxMe username to be shown on ToxMe نام کاربری که میخواهید روی ToxMe نشان داده شود Optional ToxMe biography to be shown on ToxMe بیوگرافی که دوست دارید روی ToxMe نشان داده شود (اجباری نیست) ToxMe service address آدرس سامانه ToxMe Visibility on the ToxMe service وضعیت قابل روئیت بودن روی سامانه ToxMe Password رمز Update ToxMe entry به روز رسانی مدخل ToxMe Rename profile. تغییر نام پروفایل. Delete profile. حذف پروفایل. Export profile ذخیره کردن پروفایل Remove password from profile حذف رمز از پروفایل Change profile password تغییر رمز پروفایل My name: اسم من: My status: وضعیت من: My username نام کاربری من My biography بیوگرافی من My profile پروفایل من LoadHistoryDialog Load History Dialog بارگذاری دیالوگ تاریخچه Load history بارگذاری سابقه from از to به (about 100 messages are loaded) (حدود 100 پیام بار‌گذاری شده است) Select Date Dialog انتخاب تاریخ گفت‌و‌گو Select a date تاریخی را انتخاب کنید LoginScreen Username: نام کاربری: Password: ورود رمز: Confirm: ورود مجدد رمز: Password strength: %p% میزان امنیت رمز عبور: %p% Create Profile ساخت پروفایل If the profile does not have a password, qTox can skip the login screen اگر پروفایل دارای رمز عبور نباشد، qTox میتواند صفحه ورود را نشان ندهد Load automatically بارگذاری به شکل خودکار Load بارگذاری Load Profile بارگذاری پروفایل New Profile پروفایل جدید Couldn't create a new profile ساخت پروفایل جدید امکان پذیر نمیباشد The username must not be empty. نام کاربری نمیتواند خالی باشد. The password must be at least 6 characters long. رمز عبور باید حداقل 6 کاراکتر داشته باشد. The passwords you've entered are different. Please make sure to enter same password twice. رمز های عبوری که وارد کرده اید با هم تفاوت دارند. لطفا مطمئن شوید که رمز عبور یکسانی را در هر دو جعبه متن وارد کنید. A profile with this name already exists. پروفایلی با همین نام از قبل موجود است. Password protected profiles can't be automatically loaded. پروفایل هایی که با رمز عبور محافظت می شوند، قابلیت بارگذاری به شکل خودکار را ندارند. Couldn't load profile امکان بارگذاری پروفایل وجود ندارد There is no selected profile. You may want to create one. هیچ پروفایلی انتخاب نشده است. لطفا یک پروفایل ایجاد نمایید. Couldn't load this profile امکان بارگذاری این پروفایل وجود ندارد This profile is already in use. این پروفایل در حال استفاده میباشد. Wrong password. رمز عبور اشتباه است. Import ایمپورت کردن Username input field محل ورود نام کاربری Password input field, you can leave it empty (no password), or type at least 6 characters محل ورود رمز عبور، میتوانید این فیلد را خالی رها کنید (بدون رمز عبور)، یا حداقل 6 کاراکتر وارد نمایید Password confirmation field فیلد تایید رمز عبور Create a new profile button دکمه ایجاد یک پروفایل جدید Profile list لیست پروفایل ها List of profiles لیست پروفایل ها Password input مکان ورود رمز عبور Load automatically checkbox چک باکس بارگذاری خودکار Import profile ایمپورت کردن پروفایل Load selected profile button دکمه بارگذاری پروفایل انتخاب شده New profile creation page دکمه ایجاد یک پروفایل جدید Loading existing profile page بارگذاری صفحه پروفایل موجود MainWindow Your name نام شما Your status وضعیت شما ... Ausgelassen ... Add friends اضافه کردن دوستان Create a group chat ایجاد یک چت گروهی View completed file transfers مشاهده انتقال فایل های کامل شده Change your settings تنظیمات خود را تغییر دهید Close بستن Open profile باز کردن پروفایل Open profile page when clicked در صورت کلیک کردن صفحه پروفایل باز خواهد شد Status message input مکان ورود پیام وضعیت Set your status message that will be shown to others پیام وضعیتی که دوست دارید به دیگران نشان دهید را اینجا وارد نمایید Status وضعیت Set availability status وضعیت دسترسی را تنظیم کنید Contact search جست و جوی مخاطبین Contact search input for known friends جست و جوی مخاطبین برای دوستانی که آنها را میشناسید Sorting and visibility مرتب سازی و دید Set friends sorting and visibility تنظیم مرتب سازی و دید دوستان Open Add friends page بازکردن صفحه اضافه کردن دوستان Groupchat چت گروهی Open groupchat management page باز کردن صفحه مدیریت چت گروهی File transfers history تاریخچه انتقال فایل Open File transfers history تاریخچه بازکردن انتقال فایل Settings تنظیمات Open Settings بازکردن تنظیمات Nexus View OS X Menu bar نما Window OS X Menu bar پنجره Minimize OS X Menu bar کوچک کردن Bring All to Front OS X Menu bar همه پنجره ها را جلو بیاور Exit Fullscreen خروج از نمای تمام صفحه Enter Fullscreen ورود به نمای تمام صفحه NotificationEdgeWidget Unread message(s) پیام(های) خوانده نشده PasswordEdit CAPS-LOCK ENABLED دکمه بزرگ نویسی (Caps Lock) روشن است PrivacyForm Privacy حریم خصوصی Confirmation تأیید Do you want to permanently delete all chat history? آیا میخواهید تمام تاریخچه چت را حذف نمایید؟ PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting دوستان شما میتوانند مشاهده کنند که در حال تایپ کردن هستید. Send typing notifications ارسال گزارشهای «در حال تایپ» Keep chat history حفظ تاریخچه چت NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam اِلمان NoSpam بخشی از شناسه Tox شما است. اگر به شکل غیر قابل کنترلی پیشنهاد دوستی دریافت میکنید، میبایست NoSpam خود را عوض کنید. دیگر کسی نمیتواند شما را با استفاده از شناسه قدیمی شما به لیست دوستان خود اضافه کند، اما دوستان قدیمی خود را حفظ خواهید کرد. NoSpam تنظیم NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. اِلمان NoSpam بخشی از شناسه شما است که میتوانید آن را تغییر دهید. اگر به شکل ناخواسته و در حجم بالا درخواست های دوستی دریافت میکنید، NoSpam را تغییر دهید. Generate random NoSpam تولید NoSpam تصادفی Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting حفظ تارخچه چت در حال توسعه است. ذخیره تغیرات فرمت امکان پذیر است، اما میتواند به از دست دادن داده ها منجر شود. Privacy حریم خصوصی BlackList لیست سیاه Filter group message by group member's public key. Put public key here, one per line. فیلتر کردن پیام های گروهی با کلید عمومی اعضای گروه. کلید عمومی را اینجا وارد کنید. در هر خط یک کلید عمومی وارد شود. Profile Failed to derive key from password, the profile won't use the new password. ایجاد کلید با استفاده از کلمه عبور ناموفق بود، نمایه از کلمه عبور جدید استفاده نخواهد کرد. Couldn't change password on the database, it might be corrupted or use the old password. امکان تغییر رمز روی پایگاه داده ها وجود ندارد، امکان دارد این پایگاه خراب شده باشد، یا شاید باید از رمز قبلی خود استفاده کنید. Toxing on qTox در حال Tox کردن روی qTox ProfileForm Choose a profile picture انتخاب تصویر برای پروفایل Error خطا Rename "%1" renaming a profile تغییر نام "%1" Unable to open this file. امکان باز کردن این فایل وجود ندارد. Current profile: پروفایل فعلی: Remove حذف Unable to read this image. امکان دسترسی به این عکس فراهم نیست. The supplied image is too large. Please use another image. عکس انتخاب شده بیش از اندازه بزرگ است. تصویر دیگری انتخاب کنید. Couldn't rename the profile to "%1" امکان تغییر نام پروفایل به "%1" وجود ندارد Location not writable Title of permissions popup این مکان قابل دسترسی برای نوشتن نیست You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup شما اجازه نوشتن در این مکان را ندارید. پوشه دیگری را انتخاب کنید، یا پنجره ذخیره را کنسل کنید. Failed to copy file کپی کردن فایل نا‌موفق بود The file you chose could not be written to. فایلی که انتخاب کردید امکان نوشتن ندارد. Really delete profile? deletion confirmation title آیا میخواهید پروفایل پاک شود؟ Nothing to remove چیزی برای پاک کردن وجود ندارد Your profile does not have a password! پروفایل شما رمزی ندارد! Really delete password? deletion confirmation title آیا واقعا میخواهید رمز را پاک کنید؟ Please enter a new password. لطفا رمز جدیدی وارد کنید. Are you sure you want to delete this profile? deletion confirmation text آیا مطمئن هستید که میخواهید این پروفایل را پاک کنید؟ Save save qr image ذخیره کردن Save QrCode (*.png) save dialog filter ذخیره سازی QrCode (*.png) Files could not be deleted! deletion failed title این فایلها قابلیت پاک کردن ندارند! Register (processing) ثبت نام (در حال پردازش) Update (processing) به روز رسانی (در حال پردازش) Done! انجام شد! Account %1@%2 updated successfully اکانت %1@%2 با موفقیت به روز رسانی شد Successfully added %1@%2 to the database. Save your password %1@%2 با موفقیت به پایگاه داده ها اضافه شد. رمز خود را ذخیره کنید Toxme error خطای ToxMe Register ثبت نام Update به روز رسانی Change password button text تغییر رمز عبور Set profile password button text انتخاب رمز پروفایل Current profile location: %1 مکان فعلی پروفایل: %1 Couldn't change password امکان تغییر رمز وجود نداشت This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). این مجموعه حروف و نشانه ها به سایر کلاینت های Tox میگوید که چگونه شما را پیدا کنند. این مجموعه را برای ارتباط با دوستان خود با آنها در اشتراک بگذارید. این شناسه شامل کد NoSpam (آبی رنگ)، و چکسام (به رنگ خاکستری) میشود. Empty path is unavaliable مسیر خالی فراهم نیست Failed to rename تغییر نام ناموفق بود Profile already exists این پروفایل از قبل موجود است A profile named "%1" already exists. یک پروفایل با نام "%1" از قبل موجود است. Empty name نام خالی Empty name is unavaliable امکان استفاده از نام خالی فراهم نیست Empty path مسیر خالی Couldn't change password on the database, it might be corrupted or use the old password. امکان تغییر رمز روی پایگاه داده ها وجود نداشت، شاید به این دلیل که خراب شده است یا شاید باید از رمز قبلی ایتان استفاده کنید. Export profile ذخیره کردن پروفایل Tox save file (*.tox) save dialog filter ذخیره کردن به فرمت Tox (*.tox) The following files could not be deleted: deletion failed text part 1 امکان پاک کردن این فایلها فراهم نبود: Please manually remove them. deletion failed text part 2 لطفا به شکل دستی این فایلها را پاک کنید. Are you sure you want to delete your password? deletion confirmation text آیا مطمئن هستید که میخواهید رمز عبور خود را پاک کنید؟ Images (%1) filetype filter تصاویر (%1) ProfileImporter Import profile import dialog title ایمپورت کردن پروفایل Tox save file (*.tox) import dialog filter ذخیره کردن به فرمت Tox (*.tox) Ignoring non-Tox file popup title فایلهای غیر Tox را در نظر نگیر Warning: You have chosen a file that is not a Tox save file; ignoring. popup text اخطار: شما فایلی را انتخاب کرده اید که به فرمت Tox نیست؛ این فایل در نظر گرفته نمیشود. Profile already exists import confirm title این پروفایل از قبل موجود است A profile named "%1" already exists. Do you want to erase it? import confirm text پروفایلی با نام «%1» از قبل موجود است. آیا میخواهید آن را حذف کنید؟ File doesn't exist این فایل موجود نیست Profile doesn't exist پروفایل موجود نیست Profile imported پروفایل ایمپورت شد %1.tox was successfully imported فایل %1.tox با موفقیت ایمپورت شد QApplication Ok موافقم Cancel کنسل Yes بله No خیر LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout RTL QMessageBox Couldn't add friend اضافه کردن این دوست امکان پذیر نبود %1 is not a valid Toxme address. %1 یک آدرس معتبر ToxMe نبود. You can't add yourself as a friend! When trying to add your own Tox ID as friend شما نمیتوانید خودتان را به عنوان دوست اضافه کنید! QObject Tox URI to parse آدرس (URI ) Tox به جهت پردازش Starts new instance and loads specified profile. یک اجرای جدید از برنامه را ضمن بارگذاری پروفایل انتخاب شده ایجاد خواهد کرد. profile پروفایل Default پیش فرض Blue آبی Olive زیتونی Red قرمز Violet بنفش Incoming call... یک تماس ورودی... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 هستم! میشه در Tox با هم دوست باشیم؟ None No camera device set هیچکدام Desktop Desktop as a camera input for screen sharing دسکتاپ Server doesn't support Toxme این سرور از امکان ToxMe پشتیبانی نمیکند You're making too many requests. Wait an hour and try again شما در حال ارسال درخواست های زیادی هستید. لطفا یک ساعت صبر کرده و مجدد تلاش کنید This name is already in use این نام از قبل موجود است This Tox ID is already registered under another name این شناسه Tox توسط نام کاربری دیگری ثبت گردیده است و در حال استفاده است Please don't use a space in your name لطفا از فاصله خالی ( ) در نام خود استفاده نکنید Password incorrect رمز عبور اشتباه است You can't use this name شما نمیتوانید از این نام استفاده کنید Name not found این نام پیدا نشد Tox ID not sent شناسه Tox ارسال نشد That user does not exist این کاربر وجود ندارد Error خطا qTox couldn't open your chat logs, they will be disabled. امکان بازکردن لاگ های چت شما برای qTox وجود نداشت، این امکان غیر فعال میشود. Problem with HTTPS connection مشکل برقراری ارتباط HTTPS Internal ToxMe error خطای داخلی ToxMe Reformatting text in progress.. بازآرایی متن در جریان است.. Starts new instance and opens the login screen. یک اجرای جدید را آغاز خواهد کرد و صفحه ورود را نشان خواهد داد. Dark تیره Dark blue آبی تیره Dark olive زیتونی تیره Dark red قرمز تیره Dark violet بنفش تیره Failed to load profile automatically. بارگذاری خودکار نمایه موفق نبود. online contact status آنلاین away contact status بیرون مانده busy contact status مشغول offline contact status آفلاین blocked contact status مسدود RemoveFriendDialog Remove friend حذف دوست Also remove chat history همچنین سابقه گفت‌و‌گو را حذف کن Remove حذف Are you sure you want to remove %1 from your contacts list? آیا برای حذف %1 از لیست مخاطبین خود اطمینان دارید؟ Remove all chat history with the friend if set در صورت وجود تمام سابقه گفت‌و‌گو با دوست را حذف کن ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet برای انتخاب یک ناحیه روی صفحه کلیک کرده و به اطراف بکشید. کلید %1 را برای نمایش دادن/پنهان کردن صفحه qTox و یا کلید %2 را جهت لغو استفاده کنید. Space [Space] key on the keyboard کلید فاصله Escape [Escape] key on the keyboard کلید Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected کلید %1 را برای ارسال تصویر انتخابی از صفحه، %2 را برای نمایش دادن/پنهان کردن صفحه qTox و کلید %3 را جهت لغو استفاده کنید.. Enter [Enter] key on the keyboard کلید Enter SearchForm The text could not be found. متن مورد‌نظر یافت نمی‌شود. Start شروع SearchSettingsForm Form فرم Start search: شروع جست‌و‌جو: from the end از انتها from the beginning از ابتدا after date بعد از تاریخ before date قبل از تاریخ 00.00.0000 00.00.0000 Case sensitive حساس به بزرگ یا کوچک بودن حروف Whole words only فقط کلمات کامل Use regular expressions از عبارت‌های با قاعده استفاده کن SetPasswordDialog Set your password کلمه عبور خود را مشخص کنید Confirm: تأیید کلمه عبور: Password: کلمه عبور: Password strength: %p% امنیت کلمه عبور: %p% The password is too short کلمه عبور خیلی کوتاه است The password doesn't match. کلمه‌های عبور ورودی تطبیق ندارند. Confirm password تأیید کلمه عبور Confirm password input تأیید ورود کلمه عبور Password input ورود کلمه عبور Password input field, minimum 6 characters long مکان ورود رمز عبور، با طول حداقل 6 نویسه Settings Circle #%1 حلقه #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI افزودن یک دوست Do you want to add %1 as a friend? آیا موافقید که %1 را به عنوان یک دوست اضافه کنید؟ User ID: شناسه کاربری: Friend request message: پیام درخواست دوستی: Send Send a friend request ارسال Cancel Don't send a friend request لغو UserInterfaceForm None هیچ‌کدام User Interface رابط کاربری UserInterfaceSettings Chat گفت‌و‌گو Base font: قلم پایه: px پیکسل Size: اندازه: New text styling preference may not load until qTox restarts. سبک جدید ترجیحی برای متن تا راه اندازی مجدد qTox، بارگذاری نخواهد شد. Text Style format: قالب‌بندی سبک متن: Select text styling preference. سبک ترجیحی متن را انتخاب کنید. Plaintext متن ساده Show formatting characters نویسه‌های قالب‌بندی را نشان بده Don't show formatting characters نویسه‌های قالب‌بندی را پنهان کن New message پیام جدید Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting زمانی که یک پیام جدید دریافت می‌کنید و پنجره برنامه باز نیست، پنجره qTox را باز کن. Open window پنجره را باز کن Contact list لیست مخاطبین If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning اگر انتخاب شود، گفت‌وگو‌های گروهی در بالای لیست دوستان قرار میگیرند، در غیر این صورت، این لیست زیر لیست دوستان آنلاین نشان داده خواهد شد. Place groupchats at top of friend list گفت‌و‌گوهای گروهی را بالای لیست دوستان قرار بده Your contact list will be shown in compact mode. toolTip for compact layout setting لیست مخاطبین شما به صورت جمع‌و‌جور نمایش داده خواهد شد. Compact contact list لیست مخاطبین به صورت متراکم Multiple windows mode حالت چند‌پنجره‌ای Open each chat in an individual window هر گفت‌و‌گو را در یک پنجره مجزا باز کن Emoticons شکلک‌ها Use emoticons از شکلک‌ها استفاده کن Smiley Pack: Text on smiley pack label شکلک‌های لبخند: Emoticon size: اندازه شکلک: px پیکسل Theme زمینه Style: سبک: Theme color: رنگ زمینه: Timestamp format: قالب نمایش ساعت: Date format: قالب تاریخ: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons اگر فعال شود، هر مخاطب بدون چهرک (آواتار) به جای یک تصویر پیش فرض، یک چهرک تولید‌شده بر اساس شناسه Tox خود دریافت می‌کند. برای اعمال نیاز به راه‌اندازی دوباره دارد. Use identicons instead of empty avatars از تصاویر شناسه‌ای به جای چهرک‌(آواتار)های خالی استفاده کن Use colored nicknames in chats از اسامی مستعار رنگی در گفت‌و‌گو‌ها استفاده کن Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting زمانی که پیامی جدید دریافت می‌کنید و پنجره برنامه در حالت انتخاب نیست، اطلاعیه‌ای نمایش بده. Notify آگاه کن Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned تنها در مورد پیام‌های جدید گروه که در آنها نام برده شده‌اید، اطلاع بده. Group chats only notify when mentioned در گفت‌و‌گو‌های گروهی تنهای هنگام نام بردن اطلاع بده Play sound صدا پخش کن Play sound while Busy پخش صدا هنگامی که مشغولید Notify via desktop notifications از طریق آگهی‌های رایانه، اطلاع بده Hide message sender and contents محتوا و فرستنده پیام را مخفی کن Widget Online Button to set your status to 'Online' آنلاین Away Button to set your status to 'Away' پای سیستم نیست Busy Button to set your status to 'Busy' سرش شلوغه toxcore failed to start, the application will terminate after you close this message. راه اندازی toxcore نا‌موفق بود، برنامه بعد از اینکه شما این پیام را ببندید، بسته خواهد شد. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text راه‌اندازی toxcore با استفاده از تنظیمات پراکسی شما موفق نبود. امکان اجرای qTox وجود ندارد؛ لطفا تنظیمات خود را تغییر داده و برنامه را دوباره اجرا کنید. File فایل Edit Profile ویرایش پروفایل Change Status تغییر وضعیت Log out خروج از تاکس Edit ویرایش Logout Tray action menu to logout user خروج از تاکس Exit Tray action menu to exit tox خروج از برنامه qTox Filter... فیلتر... Contacts مخاطبین Add Contact... اضافه کردن مخاطب... Next Conversation گفت و گوی بعدی Previous Conversation گفت و گوی قبلی Executable file popup title فایل اجرایی You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text شما از qTox خواسته اید که یک فایل اجرایی را باز کند. فایل های اجرایی میتوانند به شکل بالقوه صدمه جدی به سیستم شما بزنند. آیا اطمینان دارید که میخواهید این فایل را اجرا کنید؟ Couldn't request friendship امکان درخواست دوستی وجود نداشت Status وضعیت Your name نام شما Message failed to send ارسال پیام موفق نبود Create new group... ساخت یک گروه جدید... Add new circle... اضافه کردن یک حلقه جدید... %n New Friend Request(s) %n درخواست دوستی جدید %n New Group Invite(s) %n دعوت به گروه By Name بر اساس نام By Activity بر اساس فعالیت All همه Online آنلاین Offline Ausgelassen دستگاهش خاموشه Friends دوستان Groups گروه ها Search Contacts جست و جوی مخاطبین Groupchat #%1 چت گروهی #%1 Show Tray action menu to show qTox window نمایش Add friend title of the window اضافه کردن دوست Group invites title of the window دعوتنامه های گروه ها File transfers title of the window فایلهای انتقالی Settings title of the window تنظیمات My profile title of the window پروفایل من Failed to send file "%1" ارسال فایل «%1» موفق نبود File sent فایل ارسال شد sent you a friend request. یک درخواست دوستی فرستاد. invites you to join a group. شما را به پیوستن به یک گروه دعوت می‌کند. qTox/translations/fi.ts000066400000000000000000003166771415623743500155360ustar00rootroot00000000000000 AVForm Audio/Video Ääni/Video Default resolution Oletustarkkuus Disabled Pois käytöstä Select region Valitse alue Screen %1 Näyttö %1 Audio Settings Ääniasetukset Gain Teho Playback device Toistolaite Use slider to set volume of your speakers. Aseta kaiuttimien äänenvoimakkuus liukusäätimellä. Capture device Tallennuslaite Volume Äänenvoimakkuus Video Settings Videoasetukset Video device Videolaite Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Aseta kamerasi tarkkuus. Korkeammilla arvoilla ystäväsi saavat paremman kuvanlaadun. Korkeampi kuvanlaatu vaatii kuitennii paremman internet-yhteyden. Joskus yhteytesi ei välttämättä ole tarpeeksi hyvä korkeaa kuvanlaatua varten, jolloin videopuheluissa saattaa ilmetä ongelmia. Resolution Tarkkuus Rescan devices Uudelleenhae laitteet Test Sound Testiääni Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Äänenlaatu Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Välitetty äänenlaatu. Alenna tätä asetusta jos sinun siirtonopeus ei ole tarpeeksi nopea tai jos haluat vähentää internetin käyttöä. High (64 kbps) Korkea (64 kbps) Medium (32 kbps) Low (16 kbps) Alhainen (16 kbps) Very low (8 kbps) Threshold Alaraja AboutForm About Tietoja Original author: %1 Alkuperäinen julkaisija: %1 You are using qTox version %1. Käytät qTox versiota %1. Commit hash: %1 Kommitti: %1 toxcore version: %1 toxcore:n versio: %1 Qt version: %1 Qt versio: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` bug-tracker Replaces `%1` in the `A list of all known…` Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` contributors Replaces `%1` in `See a full list of…` AboutFriendForm Dialog username käyttäjänimi status message Used aliases: HISTORY OF ALIASES Automatically accept files from contact if set Auto accept files Default directory to save files: Oletushakemisto tiedostoille: Auto accept for this contact is disabled Auto accept call: Automaattisesti hyväksy puhelu: Manual Audio Ääni Audio + Video Ääni + Video Automatically accept group chat invitations from this contact if set. Auto accept group invites Automaattisesti hyväksy ryhmäkutsut Remove history (operation can not be undone!) Notes Muistiinpanot Input field for notes about the contact You can save comment about this contact here. History removed Historia poistettu Choose an auto accept directory popup title Valitse hakemisto automaattisesti hyväksyttäville tiedostoille <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Vahvistus Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Versio License Lisenssi Authors Tekijät Known Issues Tiedetyt ongelmat Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Lisää kontakti Couldn't add friend Kontaktin lisääminen epäonnistui Invalid Tox ID format Virheellinen Tox ID Send friend request Lähetä kontaktipyyntö Add a friend Friend requests Accept Hyväksy Reject Hylkää Tox ID, either 76 hexadecimal characters or name@example.com Type in Tox ID of your friend Friend request message Type message to send with the friend request or leave empty to send a default message %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend Et voi lisätä itseäsi kaveriksesi! Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description joko 76 heksadesimaalimerkkiä tai nimi@esimerkki.com Message The message you send in friend requests Viesti Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 täällä! Toxaa minun kanssa? Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not IMPORTANT NOTE Reset settings All settings will be reset to default. Are you sure? Yes Kyllä No Ei Call active popup title Puhelu aktiivinen You can't disconnect while a call is active! popup text Et voi katkaista yhteyttäsi puhelun aikana! Save File Tallenna tiedosto Logs (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Tallenna asetukset työhakemistoon normaalin asetushakemiston sijaan Make Tox portable Tee ohjelmasta siirrettävä Reset to default settings Palauta oletusasetukset Portable Connection Settings Yhteysasetukset Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Ota käyttöön IPv6 (suositus) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP Ota käyttöön UDP (suositus) Proxy type: Välityspalvelimen tyyppi: Address: Text on proxy addr label Osoite: Port: Text on proxy port label Portti: None Ei mitään SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Yhdistä uudelleen Debug Export Debug Log Copy Debug Log Enable LAN discovery ChatForm Send a file Lähetä tiedosto qTox wasn't able to open %1 qTox ei pystynyt avaamaan tiedostoa %1 %1 calling %1 soittaa Unable to open qTox ei pysty avaamaan tiedostoa Bad idea Huono ajatus Calling %1 Soitetaan %1:lle Failed to open temporary file Temporary file for screenshot Väliaikaistiedoston avaaminen epäonnistui qTox wasn't able to save the screenshot Kuvakaappauksen tallennus epäonnistui Call with %1 ended. %2 Puhelu %1:n kanssa päättyi. %2 Call duration: Puhelun kesto: %1 is typing Copy Kopioi You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Start audio call Aloita äänipuhelu End audio call Lopeta äänipuhelu Cancel audio call Peruuta äänipuhelu Accept audio call Hyväksy äänipuhelu Can't start video call Start video call Aloita videopuhelu End video call Lopeta videopuhelu Cancel video call Peruuta videopuhelu Accept video call Hyväksy videopuhelu Sound can be disabled only during a call Unmute call Poista puhelun mykistys Mute call Mykistä puhelu Microphone can be muted only during a call Unmute microphone Poista mikrofonin mykistys Mute microphone Mykistä mikrofoni ChatLog Copy Kopioi Select all Valitse kaikki pending vireillä oleva ChatTextEdit Type your message here... Kirjoita viestisi tähän... CircleWidget Rename circle Menu for renaming a circle Uudelleennimeä piiri Remove circle Menu for removing a circle Poista piiri Open all in new window Avaa kaikki uudessa ikkunassa Core /me offers friendship, "%1" /me tarjoaa kaveruutta, "%1" Invalid Tox ID Error while sending friendship request You need to write a message with your request Error while sending friendship request Sinun täytyy kirjoittaa pyynnön mukaan viesti Your message is too long! Error while sending friendship request Viestisi on liian pitkä! Friend is already added Error while sending friendship request Kontakti on jo lisätty Groupchat %1 DesktopNotify New message Uusi viesti Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form 10Mb 10Mt 0kb/s ETA:10:10 Filename Tiedostonimi Waiting to send... file transfer widget Odotetaan lähettämistä... Accept to receive this file file transfer widget Hyväksy tiedoston vastaanotto Location not writable Title of permissions popup Kohteeseen ei voi kirjoittaa You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Käyttöoikeudet eivät riitä kohteeseen kirjoittamiseen. Valitse toinen kohde tai peru. Paused file transfer widget Keskeytetty Resuming... file transfer widget Jatketaan... Open file Avaa tiedosto Open file directory Avaa tiedoston hakemisto Pause transfer Keskeytä siirto Cancel transfer Peru siirto Resume transfer Jatka siirtoa Accept transfer Hyväksy siirto Save a file Title of the file saving dialog Tallenna tiedosto Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Siirretyt tiedostot Downloads Ladatut Uploads Lähetetyt FriendListWidget Today Tänään Yesterday Eilen Last 7 days Edelliset 7 päivää This month Tässä kuussa Older than 6 Months Yli 6 kuukautta vanhat Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Kontaktipyyntö Someone wants to make friends with you Sinulle on lähetetty kontaktipyyntö User ID: Käyttäjän ID: Friend request message: Kontaktipyynnön viesti: Accept Accept a friend request Hyväksy Reject Reject a friend request Hylkää FriendWidget Invite to group Menu to invite a friend to a groupchat Kutsu ryhmään Open chat in new window Avaa keskustelu uudessa ikkunassa Remove chat from this window Poista keskustelu tästä ikkunasta Move to circle... Menu to move a friend into a different circle Siirrä kontakti piiriin... To new circle Uuteen piiriin Remove from circle '%1' Poista piiristä '%1' Move to circle "%1" Siirrä piiriin "%1" Set alias... Auto accept files from this friend context menu entry Hyväksy tiedostot automaattisesti tältä kontaktilta Choose an auto accept directory popup title Valitse hakemisto automaattisesti hyväksyttäville tiedostoille New message Uusi viesti Online Paikalla Away Poissa Busy Kiireinen Offline Yhteydetön Remove friend Menu to remove the friend from our friendlist Poista kontakti Show details Näytä yksityiskohdat To new group Invite to group '%1' GeneralForm General Yleiset Choose an auto accept directory popup title Valitse hakemisto automaattisesti hyväksyttäville tiedostoille GeneralSettings General Settings Yleiset asetukset Start in tray Käynnistä järjestelmän ilmoitusalueelle Show contacts' status changes Näytä kontaktien tilamuutokset Auto away after (0 to disable): Automaattisesti poissatilaan (0=pois käytöstä): The translation may not load until qTox restarts. Käännökset eivät tule käyttöön ennen kuin qTox käynnistetään uudelleen. Language: Kieli: Show system tray icon Näytä kuvake järjestelmän ilmoitusalueella Enable light tray icon. toolTip for light icon setting Light icon qTox will start minimized in tray. toolTip for Start in tray setting qTox käynnistyy pienennettynä ilmoitusalueelle. After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Kun painaa sulje-nappia (X), qTox pienentyy järjestelmän ilmoitusalueelle eikä sulkeudu. Close to tray Sulje järjestelmän ilmoitusalueelle After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Kun painaa pienennä-nappia (_), qTox pienentyy järjestelmän ilmoitusalueelle eikä tehtäväpalkkiin. Minimize to tray Pienennä järjestelmän ilmoitusalueelle Autostart Käynnistä automaattisesti Set where files will be saved. Valitse minne tiedoostot tallennetaan. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Voit asettaa tämän kontakteillesi erikseen hiiren oikealla napilla. Autoaccept files Hyväksy tiedostot automaattisesti Set to 0 to disable Aseta pois päältä asettamalla arvoksi 0 Your status is changed to Away after set period of inactivity. Tilaksesi asetetaan Poissa kun olet ollut asetetun ajan toimettomana. Start qTox on operating system startup (current profile). Käynnistä qTox kun käyttöjärjestelmä käynnistyy (nykyinen profiili). Default directory to save files: Oletushakemisto tiedostoille: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Lähetä viesti Smileys Hymiöt Send file(s) Lähetä tiedosto(ja) Save chat log Tallenna keskustelu Send a screenshot Lähetä kuvakaappaus Clear displayed messages Tyhjennä näytetyt viestit Cleared Quote selected text Copy link address Confirmation Vahvistus You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Lataa keskusteluhistoria... Export to file GenericNetCamView Tox video Tox video Show Messages Näytä viestit Hide Messages Piilota viestit Full Screen Toggle video preview Mute audio Mute microphone Mykistä mikrofoni End video call Lopeta videopuhelu Exit full screen GroupChatForm %1 has set the title to %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Create new group Group invites Ryhmäkutsut GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Open chat in new window Avaa keskustelu uudessa ikkunassa Remove chat from this window Poista keskustelu tästä ikkunasta Set title... Aseta otsikko... Quit group Menu to quit a groupchat Sulje ryhmä %n user(s) in chat Number of users in chat New Message Online IdentitySettings Public Information Julkiset tiedot Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Tämän merkkijonon avulla toiset Tox käyttäjät voivat ottaa yhteyttä sinuun. Jaa merkkijono ystävillesi. Your Tox ID (click to copy) Sinun Tox ID (klikkaa kopioidaksesi) This QR code contains your Tox ID. You may share this with your friends as well. Tämä QR-koodi sisältää Tox ID:si. Tämänkin voit jakaa ystävillesi. Save image Tallenna kuva Copy image Kopioi kuva Profile Profiili Rename profile. tooltip for renaming profile button Uudelleennimeä profiili. Delete profile. delete profile button tooltip Poista profiili. Go back to the login screen tooltip for logout button Palaa sisäänkirjautumisruutuun Logout import profile button Kirjaudu ulos Remove password Poista salasana Change password Vaihda salasana Rename rename profile button Uudelleennimeä Export export profile button Vie Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Antaa sinun tallentaa Tox-profiilisi tiedostoon. Profiili ei sisällä historiaasi. Delete delete profile button Poista Server Hide my name from the public list Register Your password Update Päivitys Register on ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. Nimeä profiili uudelleen. Delete profile. Poista profiili. Export profile Vie profiili Remove password from profile Change profile password My name: My status: My username My biography My profile LoadHistoryDialog Load History Dialog Lataa historia -dialogi Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Käyttäjänimi: Password: Salasana: Confirm: Vahvista: Password strength: %p% Salasanan vahvuus: %p% Create Profile Luo profiili If the profile does not have a password, qTox can skip the login screen Jos profiililla ei ole salasanaa, qTox voi ohittaa sisäänkirjautumisruudun Load automatically Lataa automaattisesti Load Lataa New Profile Uusi profiili Load Profile Lataa profiili Couldn't create a new profile Uuden profiilin luonti epäonnistui The username must not be empty. Käyttäjänimi ei voi olla tyhjä. The password must be at least 6 characters long. Salasanan täytyy olla vähintään 6 merkkiä. The passwords you've entered are different. Please make sure to enter same password twice. Syöttämäsi salasanat eroavat toisistaan. Varmista, että salasanat täsmäävät. A profile with this name already exists. Tämän niminen profiili on jo olemassa. Couldn't load profile Ei voitu ladata profiilia There is no selected profile. You may want to create one. Mitään profiilia ei ole valittu. Haluat ehkä luoda sellaisen. Couldn't load this profile Ei voitu ladata tätä profiilia This profile is already in use. Tämä profiili on jo käytössä. Wrong password. Väärä salasana. Import Password protected profiles can't be automatically loaded. Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Sinun nimesi Your status Sinun tilasi ... ... Add friends Lisää kontakti Create a group chat Luo keskusteluryhmä View completed file transfers Näytä valmiit tiedostojensiirrot Change your settings Muuta asetuksiasi Close Sulje Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings Open Settings Nexus View OS X Menu bar Näytä Window OS X Menu bar Ikkuna Minimize OS X Menu bar Pienennä Bring All to Front OS X Menu bar Exit Fullscreen Sulje kokoruututila Enter Fullscreen Mene kokoruututilaan NotificationEdgeWidget Unread message(s) Lukematon viesti Lukemattomat viestit PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Yksityisyys Confirmation Vahvistus Do you want to permanently delete all chat history? Haluatko pysyvästi poistaa kaiken keskusteluhistorian? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Ystäväsi voivat nähdä milloin kirjoitat viestiäsi. Send typing notifications Lähetä tieto siitä että kirjoitat viestiä Keep chat history Säilytä keskusteluhistoria NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Luo satunnainen NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy Yksityisyys BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox Toxaa qToxilla ProfileForm Current profile: Nykyinen profiili: Remove Poista Choose a profile picture Valitse kuva profiilillesi Error Virhe Unable to open this file. Tiedostoa ei voi avata. Unable to read this image. Kuvaa ei voi lukea. The supplied image is too large. Please use another image. Valittu kuva on liian suuri. Valitse toinen kuva. Rename "%1" renaming a profile Uudelleennimeä "%1" Couldn't rename the profile to "%1" Profiilille ei voit asettaa uutta nimeä "%1" Location not writable Title of permissions popup Kohteeseen ei voi kirjoittaa You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Käyttöoikeudet eivät riitä kohteeseen kirjoittamiseen. Valitse toinen kohde tai peru. Failed to copy file Tiedoston kopioiminen epäonnistui The file you chose could not be written to. Valitsemaasi tiedostoon ei voitu kirjoittaa. Really delete profile? deletion confirmation title Haluatko varmasti poistaa profiilin? Are you sure you want to delete this profile? deletion confirmation text Haluatko varmasti poistaa tämän profiilin? Save save qr image Tallenna Save QrCode (*.png) save dialog filter Tallenna QR-koodi (*.png) Nothing to remove Ei mitään poistettavaa Your profile does not have a password! Profiilillasi ei ole salasanaa! Really delete password? deletion confirmation title Haluatko varmasti poistaa salasanan? Please enter a new password. Syötä uusi salasana. Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Päivitys Change password button text Vaihda salasanaa Set profile password button text Current profile location: %1 Nykyisen profiilin sijainti: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Uudelleennimeäminen epäonnistui Profile already exists Profiili on jo olemassa A profile named "%1" already exists. Profiili, jonka nimi on "%1" on jo olemassa. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Vie profiili Tox save file (*.tox) save dialog filter Tox-tiedosto (*.tox) The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Haluatko varmasti poistaa salasanasi? Images (%1) filetype filter Kuvat (%1) ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Tox-tiedosto (*.tox) Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title Profiili on jo olemassa A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Cancel Peru Yes Kyllä No Ei LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend Kontaktin lisääminen epäonnistui %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend Et voi lisätä itseäsi kaveriksesi! QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Red Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 täällä! Toxaa minun kanssa? None No camera device set Ei mitään Desktop Desktop as a camera input for screen sharing Työpöytä Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error Virhe qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status away contact status busy contact status offline contact status blocked contact status RemoveFriendDialog Remove friend Poista kontakti Also remove chat history Poista myös keskusteluhistoria Remove Poista Are you sure you want to remove %1 from your contacts list? Oletko varma, että haluat poistaa kontaktin %1 kontaktilistastasi? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Aseta salasanasi Confirm: Vahvista: Password: Salasana: Password strength: %p% Salasanan vahvuus: %p% The password is too short Salasana on liian lyhyt The password doesn't match. Salasanat eivät täsmää. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 Piiri #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Do you want to add %1 as a friend? User ID: Käyttäjän ID: Friend request message: Kontaktipyynnön viesti: Send Send a friend request Cancel Don't send a friend request Peru UserInterfaceForm None Ei mitään User Interface UserInterfaceSettings Chat Keskustelu Base font: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message Uusi viesti Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Avaa qTox-ikkuna kun saat uuden viestin eikä mikään ikkuna ole vielä auki. Open window Avaa ikkuna Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Näytä ryhmäkeskustelut kontaktilistan yläpäässä Your contact list will be shown in compact mode. toolTip for compact layout setting Kontaktilistasi näytetään pienenä. Compact contact list Pieni kontaktilista Multiple windows mode Useamman ikkunan käytäntö Open each chat in an individual window Avaa kukin keskustelu omaan ikkunaansa Emoticons Use emoticons Käytä hymiöitä Smiley Pack: Text on smiley pack label Hymiöpaketti: Emoticon size: Hymiöiden koko: px Theme Teema Style: Tyyli: Theme color: Teeman väri: Timestamp format: Aikaleiman muoto: Date format: Päivämäärän muoto: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Toista ääni Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Your name Nimesi Online Button to set your status to 'Online' Online Away Button to set your status to 'Away' Poissa Busy Button to set your status to 'Busy' Kiireinen toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Couldn't request friendship Status toxcore failed to start, the application will terminate after you close this message. Executable file popup title You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Message failed to send Add new circle... By Name By Activity All Online Online Offline Yhteydetön Friends Groups Search Contacts Logout Tray action menu to logout user Kirjaudu ulos Exit Tray action menu to exit tox Filter... File Edit Contacts Change Status Edit Profile Log out Add Contact... Next Conversation Previous Conversation Groupchat #%1 Create new group... %n New Friend Request(s) %n New Group Invite(s) Show Tray action menu to show qTox window Add friend title of the window Group invites title of the window Ryhmäkutsut File transfers title of the window Settings title of the window My profile title of the window Failed to send file "%1" Tiedoston %1 lähettäminen epäonnistui File sent sent you a friend request. invites you to join a group. qTox/translations/fr.ts000066400000000000000000003403521415623743500155320ustar00rootroot00000000000000 AVForm Audio/Video Audio / Vidéo Default resolution Résolution par défaut Disabled Désactivé Select region Sélectionner une région Screen %1 Écran %1 Audio Settings Paramètres sonores Gain Gain sonore Playback device Périphérique de lecture Use slider to set volume of your speakers. Utiliser le curseur afin de régler le volume de vos haut-parleurs. Capture device Périphérique d'enregistrement Volume Niveau de volume Video Settings Paramètres vidéo Video device Périphérique vidéo Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Définir la résolution de votre caméra. Plus la valeur sera élevée, plus la qualité de vidéo rendue à vos contacts sera bonne. Notez cependant qu'une meilleure qualité vidéo nécessitera une meilleure connexion internet. Il se peut parfois que votre connexion internet ne puisse pas supporter une qualité vidéo supérieure, ce qui peut entraîner des problèmes avec les appels vidéo. Resolution Résolution Rescan devices Actualiser la liste des périphériques Test Sound Son de test Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Active le serveur audio expérimental avec prise en charge de l'annulation d'écho, nécessite le redémarrage de qTox pour prendre effet. Enable experimental audio backend Activer le moteur audio expérimental Audio quality Qualité audio Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Qualité audio transmise. Baissez ce paramètre si votre bande passante n'est pas assez élevée ou si vous souhaitez réduire l'utilisation d'Internet. High (64 kbps) Haute (64Kbps) Medium (32 kbps) Moyenne (32 kbps) Low (16 kbps) Basse (16 Kbits/s) Very low (8 kbps) Très faible (8 kbps) Threshold Seuil AboutForm About À propos Original author: %1 Auteur d'origine : %1 You are using qTox version %1. Vous utilisez actuellement la version %1 de qTox. Commit hash: %1 Algorithme de sécurisation employé : %1 toxcore version: %1 Version de toxcore : %1 Qt version: %1 Version de Qt : %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Une liste de tous les problèmes connus peut être trouvée sur notre %1 sur le site internet Github. Si vous découvrez un bogue ou une faille de sécurité dans qTox, merci de bien vouloir nous la reporter en suivant les règles décrites dans notre article wiki %2. Click here to report a bug. Cliquer ici afin de rapporter un bogue. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Voir une liste complète de %1 sur Github bug-tracker Replaces `%1` in the `A list of all known…` Suivi de bogue Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Comment rédiger un rapport de bogue contributors Replaces `%1` in `See a full list of…` contributeurs AboutFriendForm Dialog Discussion username nom d'utilisateur status message statut Used aliases: Alias utilisés : HISTORY OF ALIASES HISTORIQUE DES ALIAS Automatically accept files from contact if set Si actif, accepte automatiquement les fichiers du contact Auto accept files Accepter automatiquement les fichiers Default directory to save files: Répertoire d'enregistrement par défaut des fichiers : Auto accept for this contact is disabled L'acceptation automatique est désactivée pour ce contact Auto accept call: Acceptation automatique des appels : Manual Manuel Audio Audio Audio + Video Audio + Vidéo Automatically accept group chat invitations from this contact if set. Si activé, accepte automatiquement les invitations de ce contact à des discussions de groupe. Auto accept group invites Accepter automatiquement les invitations de groupe Remove history (operation can not be undone!) Effacer l'historique (l'opération est irréversible !) Notes Notes Input field for notes about the contact Champ de saisie des notes du contact You can save comment about this contact here. Vous pouvez enregistrer ici des commentaires sur ce contact. History removed Historique effacé Choose an auto accept directory popup title Sélectionner un répertoire d'acceptation automatique <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Celle-ci est la clé publique de votre ami, utilisez-la pour vérifier son identité via un autre canal. Vous ne pouvez pas envoyer celle-ci à d'autres personnes pour qu'elles puissent ajouter ce contact.</p></body></html> Public key (not ToxID): Clé publique (non le ToxID) : Confirmation Confirmation Are you sure to remove %1 chat history? Etes-vous sûr de supprimer l'historique des conversations avec %1 ? Failed to remove chat history with %1! Échec de la suppression de l'historique de discussions avec %1 ! AboutSettings Version Version License Licence Authors Auteurs Known Issues Problèmes connus Open update download link Ouvrir le lien de téléchargement de la mise à jour Update available Mise à jour disponible qTox is up to date ✓ qTox est à jour ✓ AddFriendForm Add Friends Ajouter des contacts Invalid Tox ID format Le format de l'identifiant Tox est invalide Send friend request Envoyer une demande de contact Couldn't add friend Impossible d'ajouter le contact Add a friend Ajouter un contact Friend requests Demandes de contact Accept Accepter Reject Rejeter Tox ID, either 76 hexadecimal characters or name@example.com Identifiant Tox : soit 76 caractères hexadécimaux, soit nom@exemple.com Type in Tox ID of your friend Renseignez l'identifiant Tox de votre contact Friend request message Message de demande de contact Type message to send with the friend request or leave empty to send a default message Renseignez un message à joindre à la demande de contact ou bien laissez le champ vide afin d'envoyer le message type par défaut %1 Tox ID is invalid or does not exist Toxme error Tox ID n'est pas valable ou n'existe pas You can't add yourself as a friend! When trying to add your own Tox ID as friend Vous ne pouvez pas vous ajouter vous-même comme contact ! Open contact list Ouvrir la liste de contacts Couldn't open file Impossible d'ouvrir le fichier Couldn't open the contact file Error message when trying to open a contact list file to import Impossible d'ouvrir le fichier de contact Invalid file Fichier non valide We couldn't find any contacts to import in this file! On n'a trouvé aucun contact à importer dans ce fichier ! Tox ID Tox ID of the person you're sending a friend request to ID Tox either 76 hexadecimal characters or name@example.com Tox ID format description soit 76 caractères hexadécimaux, soit nom@exemple.com Message The message you send in friend requests Message Open Button to choose a file with a list of contacts to import Ouvrir Send friend requests Envoyer des demandes d'amitié %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Salut, c'est %1. Peut-on discuter ? Import a list of contacts, one Tox ID per line Importer une liste de contacts, un Tox ID par ligne Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Prêt à importer %n contact(s), cliquez sur envoyer pour confirmer Prêt à importer %n contacts, cliquez sur envoyer pour confirmer Import contacts Importer des contacts AdvancedForm Advanced Avancé Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. À moins que vous ne sachiez %1 ce que vous faites, merci de ne rien modifier %2 ici. Les changements effectués ici peuvent conduire à des problèmes lors de l'utilisation de qTox, même la perte de vos données, par exemple l'historique. really réellement not ne pas IMPORTANT NOTE NOTE IMPORTANTE Reset settings Réinitialiser les paramètres All settings will be reset to default. Are you sure? Attention : tous les paramètres vont être réinitialisés ! Êtes-vous certain(e) ? Yes Oui No Non Call active popup title Appel en cours You can't disconnect while a call is active! popup text Vous ne pouvez pas vous déconnecter durant un appel ! Save File Sauvegarder le fichier Logs (*.log) Journaux (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Sauvegardera les paramètres dans le répertoire courant au lieu du répertoire habituel de configuration Make Tox portable Rendre Tox portable Reset to default settings Réinitialiser vers les paramètres par défaut Portable Portable Connection Settings Paramètres de connexion Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Activer IPv6 (recommandé) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Désactiver ceci permettra par exemple d'utiliser Tox à travers Tor. Désactiver seulement si nécessaire, car cela ajoutera une charge supplémentaire au réseau Tox. Enable UDP (recommended) Text on checkbox to disable UDP Activer UDP (recommandé) Proxy type: Type de proxy : Address: Text on proxy addr label Adresse : Port: Text on proxy port label Port : None Aucun(e) SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Se reconnecter Debug Déboguer Export Debug Log Exporter la journalisation de débogage Copy Debug Log Copier le journal de débogage Enable LAN discovery Activer la découverte du réseau local ChatForm Send a file Envoyer un fichier qTox wasn't able to open %1 qTox n'a pas pu ouvrir %1 Calling %1 Appel de %1 en cours %1 calling %1 appelle Unable to open Impossible d'ouvrir Bad idea Mauvaise idée Failed to open temporary file Temporary file for screenshot Échec de l'ouverture du fichier temporaire qTox wasn't able to save the screenshot qTox n'a pas pu sauvegarder la capture d'écran Call with %1 ended. %2 Appel avec %1 terminé. %2 Call duration: Durée de l'appel : %1 is typing %1 est en train d'écrire Copy Copier You're trying to send a sequential file, which is not going to work! Vous essayez d'envoyer un fichier séquentiel, ce qui ne fonctionnera pas ! %1 is now %2 e.g. "Dubslow is now online" %1 est maintenant %2 Call with %1 ended unexpectedly. %2 L'appel avec %1 s'est terminé de façon inattendue. %2 Filename contained illegal characters Le nom de fichier contient des caractères non autorisés Illegal characters have been changed to _ so you can save the file on windows. Les caractères illégaux ont été changés en _ afin que vous puissiez enregistrer le fichier sur windows. ChatFormHeader Can't start audio call Impossible de démarrer l'appel audio Start audio call Démarrer un appel audio End audio call Terminer l'appel audio Cancel audio call Annuler l'appel audio Accept audio call Accepter l'appel audio Can't start video call Impossible de démarrer l'appel vidéo Start video call Démarrer un appel vidéo End video call Terminer l'appel vidéo Cancel video call Annuler l'appel vidéo Accept video call Accepter l'appel vidéo Sound can be disabled only during a call Le son ne peut être coupé que lors d'un appel Unmute call Réactiver le son de l'appel Mute call Couper le son de l'appel Microphone can be muted only during a call Le micro ne peut être coupé que lors d'un appel Unmute microphone Réactiver le micro Mute microphone Couper le micro ChatLog Copy Copier Select all Tout sélectionner pending en cours ChatTextEdit Type your message here... Entrez votre message ici... CircleWidget Rename circle Menu for renaming a circle Renommer le cercle Remove circle Menu for removing a circle Supprimer le cercle Open all in new window Tout ouvrir dans une nouvelle fenêtre Core /me offers friendship, "%1" /me souhaiterait vous ajouter à sa liste de contacts, "%1" Invalid Tox ID Error while sending friendship request Identifiant Tox invalide You need to write a message with your request Error while sending friendship request Vous devez écrire un message avec votre demande Your message is too long! Error while sending friendship request Votre message est trop long ! Friend is already added Error while sending friendship request Ce contact a déjà été ajouté Groupchat %1 Conversation de groupe %1 DesktopNotify New message Nouveau message Incoming file transfer Transfert de fichier entrant Friend request received Demande d'ami reçue New group message Nouveau message de groupe Group invite received Invitation de groupe reçue FileTransferWidget Form Formulaire 10Mb 10 Mb 0kb/s 0 Kb/s ETA:10:10 Temps restant estimé : 10:10 Filename Nom du fichier Waiting to send... file transfer widget En attente d'envoi... Accept to receive this file file transfer widget Accepter de recevoir ce fichier Location not writable Title of permissions popup L'emplacement n'est pas accessible en écriture You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Vous ne disposez pas des permissions nécessaires pour écrire à cet emplacement. Choisissez-en un autre ou annulez la boîte de dialogue de sauvegarde. Resuming... file transfer widget Reprise… Cancel transfer Annuler le transfert Pause transfer Mettre en pause le transfert Paused file transfer widget En pause Open file Ouvrir un fichier Open file directory Ouvrir le répertoire du fichier Resume transfer Reprendre le transfert Accept transfer Accepter le transfert Save a file Title of the file saving dialog Sauvegarder un fichier Remote Paused file transfer widget Commande à distance en pause FilesForm Transferred Files "Headline" of the window Fichiers transférés Downloads Téléchargements Uploads Envois FriendListWidget Today Aujourd'hui Yesterday Hier Last 7 days Ces 7 derniers jours This month Ce mois-ci Older than 6 Months Plus anciens que 6 mois Never Jamais FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Demandes de contact Someone wants to make friends with you Quelqu'un vient de vous ajouter à sa liste de contacts User ID: Identifiant d'utilisateur : Friend request message: Message de demande de contact : Accept Accept a friend request Accepter Reject Reject a friend request Rejeter FriendWidget Invite to group Menu to invite a friend to a groupchat Inviter au groupe Move to circle... Menu to move a friend into a different circle Déplacer vers le cercle… To new circle Vers un nouveau cercle Remove from circle '%1' Retirer du cercle '%1' Move to circle "%1" Déplacer vers le cercle "%1" Set alias... Définir un alias... Auto accept files from this friend context menu entry Acceptation automatique des fichiers de ce contact New message Nouveau message Online Connecté(e) Away Absent(e) Busy Occupé(e) Offline Hors ligne Choose an auto accept directory popup title Sélectionner un répertoire d'acceptation automatique Open chat in new window Ouvrir la discussion dans une nouvelle fenêtre Remove chat from this window Retirer la discussion de cette fenêtre Remove friend Menu to remove the friend from our friendlist Supprimer ce contact To new group Vers un nouveau groupe Invite to group '%1' Inviter au groupe '%1' Show details Afficher les détails GeneralForm General Général Choose an auto accept directory popup title Sélectionner un répertoire d'acceptation automatique GeneralSettings General Settings Paramètres généraux The translation may not load until qTox restarts. La traduction peut ne pas prendre effet tant que qTox n'aura pas redémarrer. Start in tray Démarrer dans la barre d'état Close to tray Fermer dans la barre d'état Minimize to tray Minimiser dans la barre d'état Start qTox on operating system startup (current profile). Démarrer le profil actuel de qTox lors du démarrage du système. Show contacts' status changes Afficher les changements de statut des contacts Auto away after (0 to disable): Se rendre automatiquement absent(e) après (entrez 0 pour désactiver) : Set to 0 to disable Mettre 0 pour désactiver Language: Langue : Enable light tray icon. toolTip for light icon setting Activer l’icône claire dans la barre d'état. qTox will start minimized in tray. toolTip for Start in tray setting qTox démarrera minimisé dans la barre d'état. After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Après avoir cliqué sur fermer (X), qTox se minimisera dans la barre d'état, au lieu de se fermer lui-même. After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Après avoir cliqué sur minimiser (_), qTox se minimisera lui-même dans la barre d'état, au lieu de se minimiser dans la barre des tâches du système. Autostart Démarrer avec l'ordinateur Set where files will be saved. Choisir où les fichiers seront sauvegardés. Your status is changed to Away after set period of inactivity. Votre statut sera modifié en "Absent(e)" après la période d'inactivité que vous avez définie. Default directory to save files: Répertoire par défaut d'enregistrement des fichiers : Show system tray icon Montrer l'icône système dans la barre d'état Light icon Icône claire You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Vous pouvez définir cela pour chaque contact en faisant un un clic-droit sur leur nom. Autoaccept files Acceptation automatique des fichiers Check for updates Vérifier des mises à jour Spell checking Vérification orthographique Max autoaccept file size (0 to disable): Taille maximale du fichier pour autoaccepter (0 pour désactiver): MB Mo GenericChatForm Send message Envoyer le message Smileys Émoticônes Send file(s) Envoyer un ou des fichier(s) Save chat log Sauvegarder le journal de discussion Send a screenshot Envoyer une capture d'écran Clear displayed messages Effacer les messages affichés Cleared Effacé Quote selected text Citer le texte sélectionné Copy link address Copier l'adresse du lien Confirmation Confirmation You are sure that you want to clear all displayed messages? Vous êtes sûr de vouloir effacer tous les messages affichés ? Search in text Chercher dans le texte Go to current date Aller à la date actuelle Load chat history... Charger l'historique de discussion... Export to file Exporter dans un fichier GenericNetCamView Tox video Vidéo Tox Show Messages Afficher les messages Hide Messages Cacher les messages Full Screen Plein Écran Toggle video preview Exhiber/occulter l'aperçu vidéo Mute audio Couper le son Mute microphone Couper le micro End video call Terminer l'appel vidéo Exit full screen Quitter le mode plein écran GroupChatForm %1 has set the title to %2 %1 a modifié le titre en %2 %1 has joined the group %1 a rejoint le groupe %1 is now known as %2 %1 maintenant s'appele %2 %1 has left the group %1 a quitté le groupe %n user(s) in chat Number of users in chat %n utilisateur dans le chat %n utilisateurs dans le chat mute muet unmute activer le son GroupInviteForm Groups Groupes Create new group Créer un nouveau groupe Group invites Invitations de groupe GroupInviteWidget Invited by %1 on %2 at %3. Invité(e) par %1 le %2 à %3. Join Rejoindre Decline Refuser GroupWidget Set title... Changer le titre... Open chat in new window Ouvrir la discussion dans une nouvelle fenêtre Remove chat from this window Retirer la discussion de cette fenêtre Quit group Menu to quit a groupchat Quitter le groupe %n user(s) in chat Number of users in chat %n utilisateur dans le chat %n utilisateurs dans le chat New Message Nouveau message Online Connecté(e) IdentitySettings Public Information Informations publiques Tox ID Identifiant Tox This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Cette suite de caractères renseigne aux autres clients Tox la façon de vous contacter. Partagez-la avec vos contacts afin de pouvoir communiquer avec eux. Your Tox ID (click to copy) Votre identifiant Tox (cliquer pour copier) This QR code contains your Tox ID. You may share this with your friends as well. Cette image est un QR code qui contient votre identifiant Tox. Vous pouvez aussi bien la partager avec vos contacts. Save image Enregistrer l'image Copy image Copier l'image Profile Profil Rename profile. tooltip for renaming profile button Renommer le profil. Delete profile. delete profile button tooltip Supprimer le profil. Go back to the login screen tooltip for logout button Retourner à l'écran de connexion Logout import profile button Déconnexion Remove password Supprimer le mot de passe Change password Changer de mot de passe Rename rename profile button Renommer Export export profile button Exporter Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Vous permet d'exporter votre profil Tox dans un fichier. Ce fichier ne contient pas votre historique de discussions. Delete delete profile button Supprimer Server Serveur Hide my name from the public list Cacher mon nom de la liste publique Register S'inscrire Your password Votre mot de passe Update Mise à jour Register on ToxMe S'inscrire sur ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Votre nom sur le service ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Facultatif. Écrivez quelque-chose à propos de vous. Ou à propos de votre chat. Optional. Something about you. Or your cat. Tooltip for the Biography field. Facultatif. Écrivez quelque-chose à propos de vous. Ou à propos de votre chat. ToxMe service to register on. Service ToxMe auquel s'inscrire. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Si non cochée, votre nom sur ToxMe sera visible publiquement. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Supprimer le mot de passe et le chiffrement de votre profil. Name input Saisie du nom Name visible to contacts Nom visible par les contacts Status message input Entrez un statut Status message visible to contacts Statut visible par les contacts Your Tox ID Votre identifiant Tox Save QR image as file Sauvegarder l'image QR code dans un fichier Copy QR image to clipboard Copier l'image QR Code dans le presse-papier ToxMe username to be shown on ToxMe Nom d'utilisateur ToxMe à montrer sur ToxMe Optional ToxMe biography to be shown on ToxMe Biographie facultative de ToxMe à montrer sur ToxMe ToxMe service address Adresse du service ToxMe Visibility on the ToxMe service Visibilité sur le service ToxMe Password Mot de passe Update ToxMe entry Mettre à jour l'entrée ToxMe Rename profile. Renommer le profil. Delete profile. Supprimer le profil. Export profile Exporter le profil Remove password from profile Supprimer le mot de passe du profil Change profile password Changer le mot de passe du profil My name: Mon prénom : My status: Mon statut : My username Mon nom d'utilisateur My biography Ma biographie My profile Mon profil LoadHistoryDialog Load History Dialog Charger l’historique de discussions Load history Charger l'historique from de to à (about 100 messages are loaded) (environ 100 messages ont été téléchargés) Select Date Dialog Boîte de dialogue Sélectionner une date Select a date Sélectionnez une date LoginScreen Username: Nom d'utilisateur : Password: Mot de passe : Confirm: Confirmation : Password strength: %p% Robustesse du mot de passe : %p% Create Profile Créer le profil If the profile does not have a password, qTox can skip the login screen Si le profil n'a pas de mot de passe, qTox peut passer l'écran de connexion Load automatically Charger automatiquement Load Charger Load Profile Charger un profil New Profile Nouveau profil Couldn't create a new profile Impossible de créer un nouveau profil The username must not be empty. Le nom d'utilisateur ne doit pas être vide. The password must be at least 6 characters long. Le mot de passe doit contenir un minimum de 6 caractères. The passwords you've entered are different. Please make sure to enter same password twice. Les mots de passe que vous avez saisis sont différents. Veuillez vous assurer d'entrer deux fois le même mot de passe. A profile with this name already exists. Un profil avec ce nom existe déjà. Couldn't load profile Impossible de charger le profil There is no selected profile. You may want to create one. Aucun profil n'est sélectionné. Vous voudrez peut-être en créer un. Couldn't load this profile Impossible de charger ce profil This profile is already in use. Ce profil est déjà utilisé. Wrong password. Mot de passe incorrect. Import Importer Password protected profiles can't be automatically loaded. Les profils protégés par un mot de passe ne peuvent pas être chargés automatiquement. Username input field Champ de saisie du nom d'utilisateur Password input field, you can leave it empty (no password), or type at least 6 characters Champ de saisie du mot de passe. Vous pouvez le laisser vide (pas de mot de passe) ou bien saisir un minimum de 6 caractères Password confirmation field Champ de confirmation du mot de passe Create a new profile button Créer un nouveau bouton de profil Profile list Liste des profils List of profiles Liste des profils Password input Champ de saisie du mot de passe Load automatically checkbox Case à cocher pour un chargement automatique Import profile Importer un profil Load selected profile button Bouton pour charger le profil sélectionné New profile creation page Page de création d'un nouveau profil Loading existing profile page Page de chargement d'un profil existant MainWindow Your name Votre nom Your status Votre statut ... Add friends Ajouter des contacts Create a group chat Créer un groupe de discussion instantanée View completed file transfers Voir les transferts de fichiers terminés Change your settings Changer vos paramètres Close Fermer Open profile Ouvrir le profil Open profile page when clicked Ouvrir la page du profil lorsque cliqué Status message input Renseigner un statut Set your status message that will be shown to others Renseigner votre message de statut qui sera affiché aux autres Status Statut Set availability status Changer votre statut Contact search Recherche de contacts Contact search input for known friends Rechercher parmi les noms de contacts Sorting and visibility Tri et visibilité Set friends sorting and visibility Régler le tri et la visibilité de vos contacts Open Add friends page Ouvrir la page ajout de contacts Groupchat Discussion de groupe Open groupchat management page Ouvrir la page de gestion du groupe de discussion File transfers history Historique de transferts de fichiers Open File transfers history Ouvrir l'historique de transferts de fichiers Settings Paramètres Open Settings Ouvrir les paramètres Nexus View OS X Menu bar Affichage Window OS X Menu bar Fenêtre Minimize OS X Menu bar Minimiser Bring All to Front OS X Menu bar Envoyer tout au premier plan Exit Fullscreen Quitter le plein écran Enter Fullscreen Entrer en plein écran NotificationEdgeWidget Unread message(s) %n message(s) non-lu(s) %n messages non-lus PasswordEdit CAPS-LOCK ENABLED MAJUSCULES ACTIVÉES PrivacyForm Privacy Vie privée Confirmation Confirmation Do you want to permanently delete all chat history? Êtes-vous certain(e) de vouloir supprimer définitivement l'intégralité de l'historique de discussions ? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Vos contacts pourront voir lorsque vous êtes en train d'écrire. Send typing notifications Envoyer les notifications de frappe Keep chat history Conserver l'historique des discussions NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam Le code anti-spam fait partie de votre identifiant Tox. Si vous êtes embêté(e) par des demandes de contacts non sollicitées, vous devriez changer votre code anti-spam. Les personnes ne seront plus en mesure de vous ajouter avec votre ancien identifiant, mais vous conserverez vos contacts actuels. NoSpam Code anti-spam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Le code anti-spam fait partie de votre identifiant et peut être remplacé à souhait. Si vous êtes embêté(e) par des demandes de contacts non sollicitées, changez votre code anti-spam. Generate random NoSpam Générer un code anti-spam aléatoire Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting La conservation de l'historique de discussions est toujours en phase développement informatique. Les changements de format de sauvegarde sont possibles, ce qui pourrait entrainer des pertes de données. Privacy Vie privée BlackList Liste noire Filter group message by group member's public key. Put public key here, one per line. Filtrer le message de groupe par clé publique du membre du groupe. Mettre la clé publique ici, une par ligne. Profile Failed to derive key from password, the profile won't use the new password. Impossible de dériver la clé du mot de passe, le profil n'utilisera pas le nouveau mot de passe. Couldn't change password on the database, it might be corrupted or use the old password. Impossible de modifier le mot de passe dans la base de données, il peut être endommagé ou utiliser l'ancien mot de passe. Toxing on qTox Je toxe sur qTox ProfileForm Current profile: Profil actuel : Remove Supprimer Choose a profile picture Choisissez une image de profil Error Erreur Unable to open this file. Impossible d'ouvrir ce fichier. Unable to read this image. Impossible de lire cette image. The supplied image is too large. Please use another image. L'image fournie est trop volumineuse. Veuillez utiliser une autre image. Rename "%1" renaming a profile Renommer "%1" Couldn't rename the profile to "%1" Impossible de renommer le profil en "%1" Location not writable Title of permissions popup L'emplacement n'est pas inscriptible You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Vous n'avez pas la permission d'écrire à cet emplacement. Choisissez-en un autre ou bien décochez la boîte de dialogue de sauvegarde. Failed to copy file Erreur lors de la copie du fichier The file you chose could not be written to. Le fichier que vous avez choisi ne peut pas être inscris. Really delete profile? deletion confirmation title Êtes-vous certain(e) de vouloir supprimer le profil ? Are you sure you want to delete this profile? deletion confirmation text Êtes-vous certain(e) de vouloir supprimer ce profil ? Save save qr image Enregistrer Save QrCode (*.png) save dialog filter Enregistrer le QR code (*.png) Nothing to remove Rien à supprimer Your profile does not have a password! Votre profil n'a pas de mot de passe ! Really delete password? deletion confirmation title Êtes-vous certain(e) de vouloir supprimer le mot de passe ? Please enter a new password. Veuillez entrer un nouveau mot de passe. Files could not be deleted! deletion failed title Des fichiers n'ont pas pu être supprimés ! Register (processing) Inscription (en cours) Update (processing) Mise à jour (en cours) Done! Terminé ! Account %1@%2 updated successfully Le compte %1@%2 a été mis à jour avec succès Successfully added %1@%2 to the database. Save your password %1@2 a été ajouté avec succès dans la base de données. Sauvegardez votre mot de passe Toxme error Erreur Toxme Register Inscription Update Mise à jour Change password button text Changer de mot de passe Set profile password button text Choisir un mot de passe de profil Current profile location: %1 Emplacement actuel de votre profil : %1 Couldn't change password Impossible de changer le mot de passe This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Cette chaîne de caractères renseigne aux autres clients Tox la manière de vous contacter. Partagez-la avec vos contacts afin de communiquer. Cet identifiant inclue le code anti-spam (en bleu) et la somme de contrôle (en gris). Empty path is unavaliable Un chemin vide n'est pas valide Failed to rename Renommage échoué Profile already exists Le profil existe déjà A profile named "%1" already exists. Un profil nommé "%1" existe déjà. Empty name Nom vide Empty name is unavaliable Le nom ne peut être vide Empty path Chemin vide Couldn't change password on the database, it might be corrupted or use the old password. Impossible de modifier le mot de passe dans la base de données, elle peut être endommagée, ou utilisez l'ancien mot de passe. Export profile Exporter le profil Tox save file (*.tox) save dialog filter Fichier de sauvegarde Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Les fichiers suivants ne peuvent pas être supprimés : Please manually remove them. deletion failed text part 2 Veuillez les supprimer manuellement. Are you sure you want to delete your password? deletion confirmation text Êtes-vous certain(e) de vouloir supprimer votre mot de passe ? Images (%1) filetype filter Images (%1) ProfileImporter Import profile import dialog title Importer un profil Tox save file (*.tox) import dialog filter Fichier de sauvegarde Tox (*.tox) Ignoring non-Tox file popup title Fichier non-Tox ignoré Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Attention : vous avez choisi un fichier de sauvegarde non-Tox; ignoré. Profile already exists import confirm title Le profil existe déjà A profile named "%1" already exists. Do you want to erase it? import confirm text Un profil nommé "%1" existe déjà. Voulez-vous le supprimer ? File doesn't exist Le fichier n'existe pas Profile doesn't exist Le profile n'existe pas Profile imported Profil importé %1.tox was successfully imported %1.tox à été importé avec succès QApplication Ok D'accord Cancel Annuler Yes Oui No Non LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Impossible d'ajouter le contact %1 is not a valid Toxme address. %1 n'est pas une adresse Toxme valide. You can't add yourself as a friend! When trying to add your own Tox ID as friend Vous ne pouvez pas vous ajouter vous-même comme contact ! QObject Tox URI to parse URI Tox à analyser Starts new instance and loads specified profile. Démarrer une nouvelle instance et charger le profil spécifié. profile profil Default Défaut Blue Bleu Olive Olive Red Rouge Violet Violet Incoming call... Appel entrant... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Salut, c'est %1. On se toxe ? None No camera device set Aucune Desktop Desktop as a camera input for screen sharing Bureau Server doesn't support Toxme Ce serveur ne supporte pas Toxme You're making too many requests. Wait an hour and try again Vous faites trop de requêtes. Attendez une heure et réessayez This name is already in use Ce nom est déjà utilisé This Tox ID is already registered under another name Cet identifiant Tox est déjà inscrit sous un autre nom Please don't use a space in your name Veuillez ne pas mettre d'espace dans votre nom Password incorrect Mot de passe incorrect You can't use this name Vous ne pouvez pas utiliser ce nom Name not found Nom introuvable Tox ID not sent Identifiant Tox non envoyé That user does not exist Cet utilisateur n'existe pas Error Erreur qTox couldn't open your chat logs, they will be disabled. qTox ne peux pas ouvrir vos journaux de discussions. Ils seront désactivés. Problem with HTTPS connection Problème avec la connexion HTTPS Internal ToxMe error Erreur interne de Toxme Reformatting text in progress.. Reformatage du texte en cours... Starts new instance and opens the login screen. Démarre une nouvelle instance et ouvre l'écran de connexion. Dark Foncé Dark blue Bleu foncé Dark olive Olive Dark red Rouge foncé Dark violet Violet foncé Failed to load profile automatically. Impossible de charger le profil automatiquement. online contact status connecté away contact status absent busy contact status occupé offline contact status déconnecté blocked contact status bloqué RemoveFriendDialog Remove friend Supprimer ce contact Also remove chat history Supprimer également l'historique de discussions Remove Supprimer Are you sure you want to remove %1 from your contacts list? Êtes-vous certain(e) de vouloir supprimer %1 de votre liste de contacts ? Remove all chat history with the friend if set Si activé, efface tout l'historique de discussions avec ce contact ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Cliquez et faites glisser pour sélectionner une région. Appuyez sur %1 pour masquer / afficher la fenêtre qTox ou bien %2 pour annuler. Space [Space] key on the keyboard Espace Escape [Escape] key on the keyboard Échap Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Appuyer sur %1 pour envoyer une capture d'écran de la sélection, %2 pour masquer / afficher la fenêtre qTox ou bien %3 pour annuler. Enter [Enter] key on the keyboard Entrée SearchForm The text could not be found. Le texte n'a pas pu être trouvé. Start Démarrer SearchSettingsForm Form Formulaire Start search: Lancer la recherche : from the end à partir de la fin from the beginning à partir du début after date après la date before date avant la date 00.00.0000 00/00/0000 Case sensitive Sensible aux majuscules et minuscules Whole words only Seulement mots entiers Use regular expressions Utiliser des expressions communes SetPasswordDialog Set your password Choisissez un mot de passe Confirm: Confirmation : Password: Mot de passe : Password strength: %p% Robustesse du mot de passe : %p% The password is too short Le mot de passe est trop court The password doesn't match. Le mot de passe ne correspond pas. Confirm password Confirmer le mot de passe Confirm password input Champ de saisie pour confirmer le mot de passe Password input Champ de saisie du mot de passe Password input field, minimum 6 characters long Champ de saisie du mot de passe, d'une longueur minimale de 6 caractères Settings Circle #%1 Cercle nᵒ%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Ajouter un contact Do you want to add %1 as a friend? Voulez-vous ajouter %1 à vos contacts ? User ID: Identifiant utilisateur : Friend request message: Joindre un message de demande : Send Send a friend request Envoyer Cancel Don't send a friend request Annuler UserInterfaceForm None Aucun(e) User Interface Interface utilisateur UserInterfaceSettings Chat Discussion instantanée Base font: Police de caractères de base : px pixels Size: Taille : New text styling preference may not load until qTox restarts. Le nouveau choix de style de texte peut ne pas être chargé avant un redémarrage de qTox. Text Style format: Format du style de texte : Select text styling preference. Choisir une préférence de style de texte. Plaintext Texte en clair Show formatting characters Afficher les caractères formatés Don't show formatting characters Ne pas afficher les caractères formatés New message Nouveau message Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Ouvrir la fenêtre qTox quand vous recevez un nouveau message et qu'aucune fenêtre n'est ouverte. Open window Ouvrir une fenêtre Contact list Liste de contacts If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Si coché, les groupes de discussions seront positionnés en haut de la liste de contacts. Sinon, ils seront positionnés en dessous des contacts connectés. Place groupchats at top of friend list Positionner les groupes de discussions en haut de la liste des contacts Your contact list will be shown in compact mode. toolTip for compact layout setting Votre liste de contacts sera affichée en mode compact. Compact contact list Liste de contacts compacte Multiple windows mode Mode multi-fenêtres Open each chat in an individual window Ouvrir chaque discussion instantanée dans une fenêtre individuelle Emoticons Émoticônes Use emoticons Utiliser les émoticônes Smiley Pack: Text on smiley pack label Pack d'émoticônes : Emoticon size: Taille de l'émoticône : px pixels Theme Thème Style: Style : Theme color: Couleur du thème : Timestamp format: Format de l'horodatage : Date format: Format de la date : If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Si elle est activée, chaque contact sans avatar défini aura un avatar généré basé sur son ID Tox au lieu d'une image par défaut. Requiert un redémarrage pour être appliquer. Use identicons instead of empty avatars Utiliser des identicons au lieu d'avatars vides Use colored nicknames in chats Utiliser des pseudonymes colorés dans les chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Afficher une notification lorsque vous recevez un nouveau message et que la fenêtre n'est pas sélectionnée. Notify Notifier Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Notifier des nouveaux messages dans les discussions de groupe uniquement lorsque vous êtes mentionné. Group chats only notify when mentioned Ne notifier des discussions de groupe que lorsque vous êtes mentionné Play sound Jouer un son Play sound while Busy Jouer un son lorsque vous êtes "Occupé(e)" Notify via desktop notifications Notifier via les notifications du bureau Hide message sender and contents Masquer l'expéditeur et le contenu du message Widget Online Connecté(e) Online Button to set your status to 'Online' Connecté(e) Away Button to set your status to 'Away' Absent(e) Busy Button to set your status to 'Busy' Occupé(e) toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text ToxCore n'a pas pu démarrer avec vos paramètres de proxy. qTox ne peut donc pas démarrer. Merci de modifier vos paramètres et de redémarrer l'application. File Fichier Edit Profile Éditer le profil Change Status Modifier le statut Log out Déconnexion Edit Éditer Filter... Filtrer… Contacts Contacts Add Contact... Ajouter un contact… Next Conversation Discussion suivante Previous Conversation Discussion précédente Executable file popup title Fichier exécutable You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Vous avez demandé à qTox d'ouvrir un fichier exécutable. Les fichiers exécutables peuvent potentiellement endommager votre ordinateur. Êtes-vous certain(e) de vouloir ouvrir ce fichier ? Couldn't request friendship Impossible de demander l'ajout du contact toxcore failed to start, the application will terminate after you close this message. toxcore n'a pas réussi à démarrer. L'application s'arrêtera quand vous fermerez ce message. Your name Votre nom Status Statut Message failed to send L'envoi du message a échoué Add new circle... Ajouter un nouveau cercle… By Name Par nom By Activity Par activité All Tous Offline Hors ligne Friends Contacts Groups Groupes Search Contacts Recherche de contacts Logout Tray action menu to logout user Déconnexion Exit Tray action menu to exit tox Quitter Groupchat #%1 Groupe de discussion #%1 Create new group... Créer un nouveau groupe... %n New Friend Request(s) %n nouvelle(s) demande(s) de contact %n nouvelles demandes de contact %n New Group Invite(s) %n nouvelle(s) invitation(s) de groupe %n nouvelles invitations de groupe Show Tray action menu to show qTox window Afficher Add friend title of the window Ajouter un contact Group invites title of the window Invitations de groupe File transfers title of the window Transferts de fichiers Settings title of the window Paramètres My profile title of the window Mon profil Failed to send file "%1" Impossible d'envoyer le fichier "%1" File sent Fichier envoyé sent you a friend request. vous a envoyé une demande d'amitié. invites you to join a group. vous invite à rejoindre un groupe. qTox/translations/he.ts000066400000000000000000003127401415623743500155170ustar00rootroot00000000000000 AVForm Audio/Video שמע/וידאו Default resolution רזולוציית בררת מחדל Disabled Select region Screen %1 Audio Settings הגדרות שמע Gain Playback device התקן נגינה Use slider to set volume of your speakers. שימוש במחוון גלילה להגדרת עצמת השמע ברמקולים שלך. Capture device התקן לכידה Volume Video Settings הגדרות וידאו Video device התקן וידאו Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Resolution רזולוציה Rescan devices סריקת ההתקנים מחדש Test Sound Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Medium (32 kbps) Low (16 kbps) Very low (8 kbps) Threshold AboutForm About Original author: %1 You are using qTox version %1. Commit hash: %1 toxcore version: %1 Qt version: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` bug-tracker Replaces `%1` in the `A list of all known…` Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` contributors Replaces `%1` in `See a full list of…` AboutFriendForm Dialog username status message Used aliases: HISTORY OF ALIASES Automatically accept files from contact if set Auto accept files Default directory to save files: Auto accept for this contact is disabled Auto accept call: Manual Audio Audio + Video Automatically accept group chat invitations from this contact if set. Auto accept group invites Remove history (operation can not be undone!) Notes Input field for notes about the contact You can save comment about this contact here. History removed Choose an auto accept directory popup title <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version License Authors Known Issues Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Invalid Tox ID format Send friend request Couldn't add friend Add a friend Friend requests Accept Reject Tox ID, either 76 hexadecimal characters or name@example.com Type in Tox ID of your friend Friend request message Type message to send with the friend request or leave empty to send a default message %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to either 76 hexadecimal characters or name@example.com Tox ID format description Message The message you send in friend requests Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not IMPORTANT NOTE Reset settings All settings will be reset to default. Are you sure? Yes No Call active popup title You can't disconnect while a call is active! popup text Save File Logs (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Make Tox portable Reset to default settings Portable Connection Settings Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP Proxy type: Address: Text on proxy addr label Port: Text on proxy port label None ללא SOCKS5 HTTP Reconnect reconnect button Debug Export Debug Log Copy Debug Log Enable LAN discovery ChatForm Send a file qTox wasn't able to open %1 Unable to open Bad idea %1 calling Calling %1 Failed to open temporary file Temporary file for screenshot qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch Call with %1 ended. %2 Call duration: %1 is typing Copy You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Start audio call End audio call Cancel audio call Accept audio call Can't start video call Start video call End video call Cancel video call Accept video call Sound can be disabled only during a call Unmute call Mute call Microphone can be muted only during a call Unmute microphone Mute microphone ChatLog Copy Select all pending ChatTextEdit Type your message here... CircleWidget Rename circle Menu for renaming a circle Remove circle Menu for removing a circle Open all in new window Core /me offers friendship, "%1" Invalid Tox ID Error while sending friendship request You need to write a message with your request Error while sending friendship request Your message is too long! Error while sending friendship request Friend is already added Error while sending friendship request Groupchat %1 DesktopNotify New message Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen 10Mb Ausgelassen 0kb/s Ausgelassen ETA:10:10 Ausgelassen Filename Ausgelassen Waiting to send... file transfer widget Accept to receive this file file transfer widget Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Resuming... file transfer widget Cancel transfer Pause transfer Paused file transfer widget Open file Open file directory Resume transfer Accept transfer Save a file Title of the file saving dialog Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Downloads Uploads FriendListWidget Today Yesterday Last 7 days This month Older than 6 Months Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Someone wants to make friends with you User ID: Friend request message: Accept Accept a friend request Reject Reject a friend request FriendWidget Invite to group Menu to invite a friend to a groupchat Move to circle... Menu to move a friend into a different circle To new circle Remove from circle '%1' Move to circle "%1" Open chat in new window Remove chat from this window Set alias... Auto accept files from this friend context menu entry Remove friend Menu to remove the friend from our friendlist Show details Choose an auto accept directory popup title New message Online Away Busy Offline Ausgelassen To new group Invite to group '%1' GeneralForm General Choose an auto accept directory popup title GeneralSettings General Settings The translation may not load until qTox restarts. Language: Show system tray icon Enable light tray icon. toolTip for light icon setting Light icon qTox will start minimized in tray. toolTip for Start in tray setting Start in tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Close to tray After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Minimize to tray Autostart Set where files will be saved. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Autoaccept files Set to 0 to disable Your status is changed to Away after set period of inactivity. Auto away after (0 to disable): Show contacts' status changes Start qTox on operating system startup (current profile). Default directory to save files: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Smileys Send file(s) Send a screenshot Save chat log Clear displayed messages Cleared Quote selected text Copy link address Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Export to file GenericNetCamView Tox video Show Messages Hide Messages Full Screen Toggle video preview Mute audio Mute microphone End video call Exit full screen GroupChatForm %1 has set the title to %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Create new group Group invites GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Set title... Open chat in new window Remove chat from this window Quit group Menu to quit a groupchat %n user(s) in chat Number of users in chat New Message Online IdentitySettings Public Information Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Your Tox ID (click to copy) Profile Rename profile. tooltip for renaming profile button Go back to the login screen tooltip for logout button Logout import profile button Remove password Change password This QR code contains your Tox ID. You may share this with your friends as well. Save image Copy image Rename rename profile button Delete profile. delete profile button tooltip Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Export export profile button Delete delete profile button Server Hide my name from the public list Register Your password Update Register on ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. Delete profile. Export profile Remove password from profile Change profile password My name: My status: My username My biography My profile LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Password: Confirm: Password strength: %p% Create Profile If the profile does not have a password, qTox can skip the login screen Load automatically Load Load Profile New Profile Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Import Password protected profiles can't be automatically loaded. Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Your status ... Ausgelassen Add friends Create a group chat View completed file transfers Change your settings Close Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings Open Settings Nexus View OS X Menu bar Window OS X Menu bar Minimize OS X Menu bar Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox ProfileForm Choose a profile picture Error Rename "%1" renaming a profile Unable to open this file. Current profile: Remove Unable to read this image. The supplied image is too large. Please use another image. Couldn't rename the profile to "%1" Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Nothing to remove Your profile does not have a password! Really delete password? deletion confirmation title Please enter a new password. Are you sure you want to delete this profile? deletion confirmation text Save save qr image Save QrCode (*.png) save dialog filter Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Change password button text Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout RTL Ok Cancel Yes No QMessageBox Couldn't add friend %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Red Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! None No camera device set ללא Desktop Desktop as a camera input for screen sharing Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status away contact status busy contact status offline contact status blocked contact status RemoveFriendDialog Remove friend Also remove chat history Remove Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Password: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Do you want to add %1 as a friend? User ID: Friend request message: Send Send a friend request Cancel Don't send a friend request UserInterfaceForm None ללא User Interface UserInterfaceSettings Chat Base font: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme Style: Theme color: Timestamp format: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Away Button to set your status to 'Away' Busy Button to set your status to 'Busy' toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File Edit Profile Change Status Log out Edit Logout Tray action menu to logout user Exit Tray action menu to exit tox Filter... Contacts Add Contact... Next Conversation Previous Conversation Executable file popup title You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Couldn't request friendship Status Your name Message failed to send Add new circle... By Name By Activity All Online Offline Ausgelassen Friends Groups Search Contacts Groupchat #%1 Create new group... %n New Friend Request(s) %n New Group Invite(s) Show Tray action menu to show qTox window Add friend title of the window Group invites title of the window File transfers title of the window Settings title of the window My profile title of the window Failed to send file "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/hr.ts000066400000000000000000003266271415623743500155450ustar00rootroot00000000000000 AVForm Audio/Video Audio/Video Default resolution Standardna rezolucija Disabled Deaktivirano Select region Odaberi područje Screen %1 Ekran %1 Audio Settings Postavke zvuka Gain Pojačavanje Playback device Uređaj za sviranje Use slider to set volume of your speakers. Koristi klizač za postavljanje glasnoću zvučnika. Capture device Uređaj za snimanje Volume Glasnoća Video Settings Postavke videa Video device Video uređaj Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Postavi rezoluciju kamere. Što su vrijednosti veće, tvoji će prijatelji primati bolju kvalitetu videa. Vodi računa o tome, da bolja kvaliteta zahtijeva i bržu internetsku vezu. Ponekad tvoja internetska veza nije dovoljno dobra, da bi podržala visoku video kvalitetu, što može dovesti do problema s video pozivima. Resolution Rezolucija Rescan devices Ponovo pretraži uređaje Test Sound Isprobaj zvuk Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Aktivira probni audio mehanizam s podrškom za ukidanje jeke; qTox se mora ponovo pokrenuti. Enable experimental audio backend Aktiviraj probni audio mehanizam Audio quality Kvaliteta zvuka Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Kvaliteta prenesenog zvuka. Smanji postavku, ako imaš sporu vezu ili ako želiš smanjiti količinu prometa podataka. High (64 kbps) Visoka (64 kbps) Medium (32 kbps) Srednja (32 kbps) Low (16 kbps) Niska (16 kbps) Very low (8 kbps) Vrlo niska (8 kbps) Threshold Prag AboutForm About O programu Original author: %1 Autor: %1 You are using qTox version %1. Koristiš qTox verziju %1. Commit hash: %1 Commit hash: %1 toxcore version: %1 toxcore verzija: %1 Qt version: %1 Qt verzija: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Popis poznatih problema nalazi se na stranici %1 na Githubu. Ako otkriješ pogrešku ili sigurnosni problem u qToxu, prijavi ih prema uputama u wiki članku %2. Click here to report a bug. Pritisni ovdje za prijavljivanje pogreške. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Pogledaj potpun popis „%1” na Githubu bug-tracker Replaces `%1` in the `A list of all known…` praćenje grešaka Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Kako pripremiti izvještaj o grešci contributors Replaces `%1` in `See a full list of…` suradnici AboutFriendForm Dialog Dijalog username korisničko ime status message poruka stanja Used aliases: Korišteni pseudonimi: HISTORY OF ALIASES POVIJEST PSEUDONIMA Automatically accept files from contact if set Automatski prihvati datoteke od ove osobe, ako je postavljeno Auto accept files Automatski prihvati datoteke Default directory to save files: Mapa za spremanje datoteka: Auto accept for this contact is disabled Automatsko prihvaćanje za ovaj kontakt je deaktivirano Auto accept call: Automatski prihvati poziv: Manual Ručno Audio Audio Audio + Video Audio i video Automatically accept group chat invitations from this contact if set. Automatski prihvati poziv za grupno čavrljanje od ovog kontakta, ako je postavljeno. Auto accept group invites Automatski prihvati pozive u grupe Remove history (operation can not be undone!) Ukloni povijest (radnja se ne može poništiti!) Notes Napomene Input field for notes about the contact Polje za upis napomena o kontaktu You can save comment about this contact here. Ovdje možeš spremiti komentar o ovom kontaktu. History removed Povijest je uklonjena Choose an auto accept directory popup title Odaberi direktorij za automatsko prihvaćanje <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Ovo je javni ključ tvog prijatelja, pomoću njega možeš provjeriti njegov identitet na drugom kanalu. Ne možeš ga poslati drugima, da bi ga oni mogli dodati.</p></body></html> Public key (not ToxID): Javni ključ (ne Tox ID): Confirmation Potvrda Are you sure to remove %1 chat history? Zaista želiš ukloniti povijest čavrljanja s %1? Failed to remove chat history with %1! Neuspjelo uklanjanje povijesti čavrljanja s %1! AboutSettings Version Verzija License Licenca Authors Autori Known Issues Poznati problemi Open update download link Otvori poveznicu za preuzimenje nadogradnje Update available Postoji nova verzija qTox is up to date ✓ Verzija qToxa je aktualna ✓ AddFriendForm Add Friends Dodaj prijatelje Send friend request Pošalji zahtjev za prijateljlstvo Couldn't add friend Nije bilo moguće dodati prijatelja Invalid Tox ID format Neispravan oblik Tox ID-a Add a friend Dodaj prijatelja Friend requests Zahtjevi za prijateljstvo Accept Prihvati Reject Odbaci Tox ID, either 76 hexadecimal characters or name@example.com Tox ID - ili 76 hex-znakova ili ime@primjer.hr Type in Tox ID of your friend Upiši Tox ID prijatelja Friend request message Poruka uz zahtjev za prijateljstvo Type message to send with the friend request or leave empty to send a default message Upiši poruku koju želiš poslati uz zahtjev za prijateljstvo ili ostavi prazno za standardnu poruku %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID je nevaljan ili ne postoji You can't add yourself as a friend! When trying to add your own Tox ID as friend Ne možeš sebe dodati kao prijatelja! Open contact list Otvori popis kontakata Couldn't open file Nije bilo moguće otvoriti datoteku Couldn't open the contact file Error message when trying to open a contact list file to import Nije bilo moguće otvoriti datoteku s kontaktima Invalid file Neispravna datoteka We couldn't find any contacts to import in this file! U ovoj datoteci nema kontakata, koji bi se mogli uvesti! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description ili 76 heksadecimalnih znakova ili ime@primjer.hr Message The message you send in friend requests Poruka Open Button to choose a file with a list of contacts to import Otvori Send friend requests Pošalji zahtjev za prijateljstvo %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 ovdje! Hoćemo toxati? Import a list of contacts, one Tox ID per line Uvezi popis kontakata, jedan Tox ID po retku Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Broj kontakata za uvoz: %n. Kliknite Pošalji za potvrdu Broj kontakata za uvoz: %n. Kliknite Pošalji za potvrdu Broj kontakata za uvoz: %n. Kliknite Pošalji za potvrdu Import contacts Uvezi kontakte AdvancedForm Advanced Napredno Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Ako %1 znaš što radiš, %1 mijenjaj ove postavke. Promjene ovih postavki mogu poremetiti rad qToxa i/ili prouzročiti gubitak podataka, npr. povijesti. really stvarno not ne IMPORTANT NOTE VAŽNO Reset settings Poništi postavke All settings will be reset to default. Are you sure? Sve postavke bit će postavljene na standardne vrijednosti. Zaista to želiš? Yes Da No Ne Call active popup title Poziv je aktivan You can't disconnect while a call is active! popup text Ne možeš se isključiti dok je poziv aktivan! Save File Spremi datoteku Logs (*.log) Dnevnici (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Spremi postavke u radnu mapu, umjesto u uobičajenu konfiguracijsku mapu Make Tox portable Pripremi Tox za prenosivost Reset to default settings Vrati na zadane postavke Portable Prenosivo Connection Settings Postavke veze Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Aktiviraj IPv6 (preporučeno) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Deaktiviranjem ovoga, dozvoljava se npr. toksiranje preko Toxa. Deaktiviraj samo ako je neophodno, jer dodatno opterećuje Tox mrežu. Enable UDP (recommended) Text on checkbox to disable UDP Omogući UDP (preporučeno) Proxy type: Vrsta proksija: Address: Text on proxy addr label Adresa: Port: Text on proxy port label Port: None Nema SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Ponovno spajanje Debug Traženje pogrešaka Export Debug Log Izvezi dnevnik traženja pogrešaka Copy Debug Log Kopiraj dnevnik traženja pogrešaka Enable LAN discovery Aktiviraj pronalaženje LAN mreže ChatForm Send a file Pošalji datoteku qTox wasn't able to open %1 qTox nije mogao otvoriti %1 %1 calling %1 zove Call with %1 ended. %2 Poziv s %1 je završen. %2 Call duration: Trajanje poziva: Unable to open Nije moguće otvoriti Bad idea Loša ideja Calling %1 Poziva se %1 Failed to open temporary file Temporary file for screenshot Neuspjelo otvaranje privremene datoteke qTox wasn't able to save the screenshot qTox nije mogao spremiti snimku ekrana %1 is typing %1 tipka Copy Kopiraj You're trying to send a sequential file, which is not going to work! Pokušavaš poslati sekvencijalnu datoteku, što neće uspjeti! %1 is now %2 e.g. "Dubslow is now online" %1 je sada %2 Call with %1 ended unexpectedly. %2 Poziv s %1 je neočekivano završen. %2 Filename contained illegal characters Ime datoteke je sadržalo nedozvoljene znakove Illegal characters have been changed to _ so you can save the file on windows. Nedozvoljeni znakovi su promijenjeni u znak _ tako da možeš spremiti datoteku u windowsima. ChatFormHeader Can't start audio call Nije moguće započeti poziv Start audio call Započni poziv End audio call Završi poziv Cancel audio call Prekini poziv Accept audio call Prihvati poziv Can't start video call Nije moguće započeti video poziv Start video call Započni video poziv End video call Završi video poziv Cancel video call Prekini video poziv Accept video call Prihvati video poziv Sound can be disabled only during a call Zvuk se može isključiti samo tijekom poziva Unmute call Uključi zvuk poziva Mute call Isključi zvuk poziva Microphone can be muted only during a call Mikrofon se može ugasiti samo tijekom poziva Unmute microphone Uključi mikrofon Mute microphone Isključi mikrofon ChatLog Copy Kopiraj Select all Odaberi sve pending u tijeku ChatTextEdit Type your message here... Ovdje upiši svoju poruku … CircleWidget Rename circle Menu for renaming a circle Preimenuj kružok Remove circle Menu for removing a circle Ukloni kružok Open all in new window Otvori sve u novom prozoru Core /me offers friendship, "%1" /me nudi prijateljstvo, „%1” Invalid Tox ID Error while sending friendship request Neispravan Tox ID You need to write a message with your request Error while sending friendship request Moraš napisati poruku uz zahtjev Your message is too long! Error while sending friendship request Poruka je predugačka! Friend is already added Error while sending friendship request Prijatelj je već dodan Groupchat %1 Grupno čavrljanje %1 DesktopNotify New message Nova poruka Incoming file transfer Prijenos dolazne datoteke Friend request received Primljen je zahtjev za prijateljstvo New group message Poruka nove grupe Group invite received Primljen je poziv u grupu FileTransferWidget Form Obrazac 10Mb 10 Mb 0kb/s 0 kb/s ETA:10:10 ETA:10:10 Filename Naziv datoteke Waiting to send... file transfer widget Čekanje na slanje … Accept to receive this file file transfer widget Prihvati primanje datoteke Location not writable Title of permissions popup Nije dozvoljeno pisanje na ovu lokaciju You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nemaš ovlasti za pisanje na tu lokaciju. Odaberi drugu ili prekini spremanje. Save a file Title of the file saving dialog Spremi datoteku Paused file transfer widget Zaustavljeno Resuming... file transfer widget Nastavlja se … Open file Otvori datoteku Open file directory Otvori mapu datoteke Pause transfer Zaustavi prijenos Cancel transfer Prekini prijenos Resume transfer Nastavi prijenos Accept transfer Prihvati prijenos Remote Paused file transfer widget Udaljeni pristup je zaustavljen FilesForm Downloads Preuzimanja Uploads Slanja Transferred Files "Headline" of the window Prenesene datoteke FriendListWidget Today Danas Yesterday Jučer Last 7 days Zadnjih 7 dana This month Ovaj mjesec Older than 6 Months Starije od 6 mjeseci Never Nikada FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Zahtjev za prijateljstvo Someone wants to make friends with you Netko se želi s tobom sprijateljiti User ID: Korisnički ID: Friend request message: Poruka uz zahtjev za prijateljstvo: Accept Accept a friend request Prihvati Reject Reject a friend request Odbaci FriendWidget Invite to group Menu to invite a friend to a groupchat Pozovi u grupu Set alias... Postavi pseudonim … Auto accept files from this friend context menu entry Automatski prihvati datoteke ovog prijatelja Remove friend Menu to remove the friend from our friendlist Ukloni prijatelja Choose an auto accept directory popup title Odaberi direktorij za automatsko prihvaćanje Open chat in new window Otvori čavrljanje u novom prozoru Remove chat from this window Ukloni čavrljanje iz ovog prozora To new group U novu grupu Invite to group '%1' Pozovi u grupu „%1” Move to circle... Menu to move a friend into a different circle Premjesti u kružok... To new circle U novi kružok Remove from circle '%1' Ukloni iz kružoka '%1' Move to circle "%1" Premjesti u kružok "%1" Show details Prikaži detalje New message Nova poruka Online Povezan(a) Away Odsutan(na) Busy Zauzet(a) Offline Odspojen(a) GeneralForm General Opće Choose an auto accept directory popup title Odaberi direktorij za automatsko prihvaćanje GeneralSettings General Settings Opće postavke The translation may not load until qTox restarts. Prijevod možda neće biti učitan, sve dok se qTox ponovo ne pokrene. Language: Jezik: Show system tray icon Prikaži ikonu u traci sustava Enable light tray icon. toolTip for light icon setting Aktiviraj svijetlu ikonu za programsku traku. Light icon Svijetla ikona qTox will start minimized in tray. toolTip for Start in tray setting qTox će se pokrenuti smanjen u programskoj traci. Start in tray Pokreni u programskoj traci After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Pritiskom na znak za zatvaranje (X), qTox će se smanjiti u programsku traku, a ne zatvoriti. Close to tray Zatvori u programsku traku After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Pritiskom na znak za smanjivanje (_), qTox će se smanjiti u programsku traku, umjesto u traku sustava. Minimize to tray Smanji u programsku traku Autostart Automatsko pokretanje Set where files will be saved. Postavi mjesto za spremanje datoteka. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Ovo možeš postaviti za svakog prijatelja, desnim pritiskom na prijatelja. Autoaccept files Automatsko prihvaćanje datoteka Set to 0 to disable Postavi na 0 za deaktiviranje Your status is changed to Away after set period of inactivity. Tvoje se stanje mijenja u „Odsutan(na)”, kad prođe postavljeno vrijeme neaktivnosti. Auto away after (0 to disable): Automatska odsutnost nakon (0 za deaktiviranje): Show contacts' status changes Prikaži promjene stanja kontakta Start qTox on operating system startup (current profile). Pokreni qTox nakon pokretanja operacijskog sustava (za ovaj profil). Default directory to save files: Mapa za spremanje datoteka: Check for updates Provjeri nadogradnje Spell checking Provjera pravopisa Max autoaccept file size (0 to disable): Maksimalna veličina automatski prihvaćenih datoteka (0 za deaktiviranje): MB MB GenericChatForm Send message Pošalji poruku Smileys Smješkići Send file(s) Pošalji datoteke Save chat log Spremi dnevnik čavrljanja Clear displayed messages Očisti prikazane poruke Cleared Očišćeno Send a screenshot Pošalji snimku ekrana Quote selected text Citiraj označeni tekst Copy link address Kopiraj poveznicu adrese Confirmation Potvrda You are sure that you want to clear all displayed messages? Zaista želiš izbrisati sve prikazane poruke? Search in text Traži u tekstu Go to current date Prijeđi na današnji datum Load chat history... Učitaj povijest čavrljanja … Export to file Izvezi u datoteku GenericNetCamView Tox video Tox video Show Messages Prikaži poruke Hide Messages Sakrij poruke Full Screen Cjeloekranski prikaz Toggle video preview Uključi/isključi pretprikaz videa Mute audio Isključi zvuk Mute microphone Isključi mikrofon End video call Završi video poziv Exit full screen Prekini cjeloekranski prikaz GroupChatForm %1 has set the title to %2 %1 je promijenio/la naslov u %2 %1 has joined the group %1 se pridružio(la) grupi %1 is now known as %2 %1 se sada vodi pod %2 %1 has left the group %1 je napustio/la grupu %n user(s) in chat Number of users in chat mute isključen zvuk unmute iključi zvuk GroupInviteForm Groups Grupe Create new group Stvori novu grupu Group invites Pozivi u grupu GroupInviteWidget Invited by %1 on %2 at %3. Pozivač(ica): %1 (%2 %3). Join Pridruži se Decline Odbaci GroupWidget Set title... Postavi naslov … Quit group Menu to quit a groupchat Zatvori grupu Open chat in new window Otvori čavrljanje u novom prozoru Remove chat from this window Ukloni čavrljanje iz ovog prozora %n user(s) in chat Number of users in chat New Message Nova poruka Online Povezan(a) IdentitySettings Public Information Javne informacije This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Ova gomila znakova govori drugim Tox klijentima kako te kontaktirati. Podijeli ih sa svojim prijateljima, kako biste komunicirali. Tox ID Tox ID Your Tox ID (click to copy) Tvoj Tox ID (pritisni za kopiranje) Rename rename profile button Preimenuj Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Dozvoljava izvoz Tox profila u datoteku. Profil ne sadrži tvoju povijest. Export export profile button Izvezi Delete delete profile button Izbriši This QR code contains your Tox ID. You may share this with your friends as well. Ovaj QR kod sadrži tvoj Tox ID. Možeš ga dijeliti s prijateljima. Save image Spremi sliku Copy image Kopiraj sliku Server Server Hide my name from the public list Sakrij moje ime iz javnog popisa Register Registracija Your password Lozinka Update Ažuriranje Profile Profil Rename profile. tooltip for renaming profile button Preimenuj profil. Delete profile. delete profile button tooltip Izbriši profil. Go back to the login screen tooltip for logout button Vrati se na ekran za prijavu Logout import profile button Odjavi se Remove password Ukloni lozinku Change password Promijeni lozinku Register on ToxMe Registriraj se na ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Naziv ToxMe usluge. Optional. Something about you. Or your cat. Tooltip for the Biography text. Neobavezno. Nešto o tebi. Ili o tvom psu. Optional. Something about you. Or your cat. Tooltip for the Biography field. Neobavezno. Nešto o tebi. Ili o tvom psu. ToxMe service to register on. ToxMe usluga za koju se želiš registrirati. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ako nije drugačije postavljeno, zapisi u ToxMe su vidljivi javnosti. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Uklonite lozinku i šifriranje iz svog profila. Name input Upis imena Name visible to contacts Ime, koje kontakti vide Status message input Upis poruke stanja Status message visible to contacts Poruka stanja koju kontakti vide Your Tox ID Tvoj Tox ID Save QR image as file Spremi QR sliku kao datoteku Copy QR image to clipboard Kopiraj QR sliku u međuspremnik ToxMe username to be shown on ToxMe Korisničko ime koje će se prikazati na ToxMe Optional ToxMe biography to be shown on ToxMe Neobavezna biografija koja će biti prikazana na ToxMe ToxMe service address Adresa ToxMe usluge Visibility on the ToxMe service Vidljivost na usluzi ToxMe Password Lozinka Update ToxMe entry Obnovi zapis u ToxMe Rename profile. Preimenuj profil. Delete profile. Izbriši profil. Export profile Izvoz profila Remove password from profile Ukloni lozinku iz profila Change profile password Promijeni lozinku profila My name: Moje ime: My status: Moje stanje: My username Moje korisnilko ime My biography Moja biografija My profile Moj profil LoadHistoryDialog Load History Dialog Dijalog za učitavanje povijesti Load history Učitaj povijest from od to do (about 100 messages are loaded) (učitano je ca. 100 poruka) Select Date Dialog Dijalog za biranje datuma Select a date Odaberi datum LoginScreen Username: Korisničko ime: Password: Lozinka: Confirm: Potvrdi: Password strength: %p% Jačina lozinke: %p% Create Profile Stvori profil If the profile does not have a password, qTox can skip the login screen Ako profil nema lozinku, qTox može preskočiti ekran za prijavu Load automatically Učitaj automatski Import Uvezi Load Učitaj New Profile Novi profil Load Profile Učitaj profil Couldn't create a new profile Nije bilo moguće stvoriti novi profil The username must not be empty. Korisničko ime ne smije biti prazno. The password must be at least 6 characters long. Lozinka mora sadržati barem 6 znakova. The passwords you've entered are different. Please make sure to enter same password twice. Upisane lozinke se razlikuju. Upiši istu lozinku dva puta. A profile with this name already exists. Profil s tim imenom već postoji. Password protected profiles can't be automatically loaded. Lozinkom zaštićeni profili ne mogu se automatski učitati. Couldn't load profile Nije bilo moguće učitati profil There is no selected profile. You may want to create one. Nijedan profil nije označen. Možda moraš stvoriti jedan profil. Couldn't load this profile Nije bilo moguće učitati ovaj profil This profile is already in use. Ovaj se profil već koristi. Wrong password. Pogrešna lozinka. Username input field Polje za upis korisničkog imena Password input field, you can leave it empty (no password), or type at least 6 characters Polje za upis lozinke, može biti prazno (bez lozinke) ili upiši barem 6 znakova Password confirmation field Polje za potvrdu lozinke Create a new profile button Gumb za stvaranje novog profila Profile list Popis profila List of profiles Popis profila Password input Upis lozinke Load automatically checkbox Potvrda automatskog učitavanja Import profile Uvoz profila Load selected profile button Gumb za učitavanje označenog profila New profile creation page Stranica za stvaranje novog profila Loading existing profile page Stranica za učitavanje postojećeg profila MainWindow Your name Tvoje ime Your status Tvoje stanje Add friends Dodaj prijatelje Create a group chat Stvori grupno čavrljanje View completed file transfers Pregledaj završene prijenose datoteka Change your settings Promjeni postavke Close Zatvori ... Open profile Otvori profil Open profile page when clicked Otvori stranicu profila klikom Status message input Upis poruke stanja Set your status message that will be shown to others Postavi poruku stanja, koja će se prikazati drugima Status Stanje Set availability status Postavi stanje dostupnosti Contact search Traženje kontakta Contact search input for known friends Traženje kontakta među prijateljima Sorting and visibility Poredak i vidljivost Set friends sorting and visibility Postavi poredak i vidljivost prijatelja Open Add friends page Otvori stranicu za dodavanje prijatelja Groupchat Grupno čavrljanje Open groupchat management page Otvori stranicu za upravljanje grupnim čavrljanjem File transfers history Povijest prijenosa datoteka Open File transfers history Otvori povijest prijenosa datoteka Settings Postavke Open Settings Otvori postavke Nexus View OS X Menu bar Prikaz Window OS X Menu bar Prozor Minimize OS X Menu bar Smanji Bring All to Front OS X Menu bar Prikaži sve naprijed Exit Fullscreen Prekini cjeloekranski prikaz Enter Fullscreen Otvori cjeloekranski prikaz NotificationEdgeWidget Unread message(s) Nepročitanih poruka Nepročitanih poruka Nepročitanih poruka PasswordEdit CAPS-LOCK ENABLED PISANJE VELIKIM SLOVIMA UKLJUČENO PrivacyForm Privacy Privatnost Confirmation Potvrda Do you want to permanently delete all chat history? Želiš li nepovratno izbrisati cijelu povijest čavrljanja? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Tvoji prijatelji će moći vidjeti kad tipkaš. Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Čuvanje povijesti čavrljanja nalazi se još u fazi razvoja. Moguće su promjene u formatu za spremanje, što može dovesti do gubitka podataka. Send typing notifications Slanje obavijesti tijekom tipkanja Keep chat history Čuvaj povijest čavrljanja NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam je dio tvog Tox ID-a. Ako te zatrpavaju zahtjevima za prijateljstvo, promijeni NoSpam. Drugi te neće moći dodati po tvom starom ID-u, ali ti ćeš zadržati svoje trenutačne prijatelje. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam je dio tvog Tox ID-a i možeš ga promijeniti kadgod želiš. Ako te zatrpavaju zahtjevima za prijateljstvo, promijeni NoSpam. Generate random NoSpam Generiraj slučajni NoSpam Privacy Privatnost BlackList Crna lista Filter group message by group member's public key. Put public key here, one per line. Filtriraj grupne poruke prema javnim ključevima članova. Stavi javne ključeve ovdje, u svaki redak po jedan. Profile Failed to derive key from password, the profile won't use the new password. Neuspjelo izvlačenje ključa iz lozinke, profil neće koristiti novu lozinku. Couldn't change password on the database, it might be corrupted or use the old password. Nije bilo moguće promijeniti lozinku baze podataka, možda je oštećena ili koristi staru lozinku. Toxing on qTox Toksiranje kroz qTox ProfileForm Choose a profile picture Odaberi sliku profila Error Pogreška Rename "%1" renaming a profile Preimenuj „%1” Failed to copy file Neuspjelo kopiranje datoteke The file you chose could not be written to. Nije moguće pisati u odabranu datoteku. Are you sure you want to delete this profile? deletion confirmation text Zaista želiš izbrisati ovaj profil? Current profile: Trenutačni profil: Remove Ukloni Unable to open this file. Nije moguće otvoriti ovu datoteku. Unable to read this image. Nije moguće učitati ovu sliku. The supplied image is too large. Please use another image. Slika je prevelika. Koristi jednu drugu sliku. Couldn't rename the profile to "%1" Nije bilo moguće preimenovati profil u "%1" Location not writable Title of permissions popup Nije dozvoljeno pisanje na ovu lokaciju You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nemaš ovlasti za pisanje na tu lokaciju. Odaberi drugu ili prekini spremanje. Really delete profile? deletion confirmation title Stvarno ukloniti profil? Files could not be deleted! deletion failed title Nije bilo moguće izbrisati datoteke! Save save qr image Spremi Save QrCode (*.png) save dialog filter Spremi QrCode (*.png) Nothing to remove Nema se što ukloniti Your profile does not have a password! Tvoj profil nema lozinku! Really delete password? deletion confirmation title Stvarno želiš ukloniti lozinku? Please enter a new password. Upiši novu lozinku. Register (processing) Registracija (u tijeku) Update (processing) Ažuriranje (u tijeku) Done! Gotovo! Account %1@%2 updated successfully Račun %1@%2 je uspješno ažuriran Successfully added %1@%2 to the database. Save your password Račun %1@%2 uspješno je dodan u bazu podataka. Spremi lozinku Toxme error Toxme pogreška Register Registracija Update Ažuriranje Change password button text Promijeni lozinku Set profile password button text Postavi lozinku profila Current profile location: %1 Trenutačna lokacija profila: %1 Couldn't change password Nije bilo moguće promijeniti lozinku This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Ova gomila znakova govori drugim Tox klijentima kako te kontaktirati. Podijeli ih sa svojim prijateljima, kako biste komunicirali. Ovaj ID sadrži NoSpam kȏd (plavo) i kontrolni zbroj (sivo). Empty path is unavaliable Prazna putanja nije dostupna Failed to rename Neuspjelo preimenovanje Profile already exists Profil već postoji A profile named "%1" already exists. Profil s imenom „%1” već postoji. Empty name Ime nema znakova Empty name is unavaliable Ime bez znakova nije dostupno Empty path Prazna putanja Couldn't change password on the database, it might be corrupted or use the old password. Nije bilo moguće promijeniti lozinku baze podataka. Možda je baza oštećena ili koristi staru lozinku. Export profile Izvoz profila Tox save file (*.tox) save dialog filter Tox datoteka (.tox) The following files could not be deleted: deletion failed text part 1 Nije moguće izbrisati sljedeće datoteke: Please manually remove them. deletion failed text part 2 Ukloni ih ručno. Are you sure you want to delete your password? deletion confirmation text Zaista želiš ukloniti svoju lozinku? Images (%1) filetype filter Slike (%1) ProfileImporter Import profile import dialog title Uvoz profila Tox save file (*.tox) import dialog filter Tox datoteka (*.tox) Ignoring non-Tox file popup title Zanemaruje se ne-Tox datoteka Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Upozorenje: odabrana je datoteka, koja nije .Tox; zanemaruje se. Profile already exists import confirm title Profil već postoji A profile named "%1" already exists. Do you want to erase it? import confirm text Profil s imenom „%1” već postoji. Želiš li ga izbrisati? File doesn't exist Datoteka ne postoji Profile doesn't exist Profil ne postoji Profile imported Profil je uvezen %1.tox was successfully imported %1.tox je uspješno uvezen QApplication Ok U redu Cancel Prekini Yes Da No Ne LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Lijevo‑na-desno QMessageBox Couldn't add friend Nije bilo moguće dodati prijatelja %1 is not a valid Toxme address. %1 nije valjana Toxme adresa. You can't add yourself as a friend! When trying to add your own Tox ID as friend Ne možeš sebe dodati kao prijatelja! QObject Tox URI to parse Tox URI za raščlanjivanje Starts new instance and loads specified profile. Pokreće novu instancu i učitava odabrani profil. profile profil Default Standardno Blue Plavo Olive Maslinasto Red Crveno Violet Ljubičasto Incoming call... Dolazni poziv … %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 ovdje! Hoćeš me toxati? Server doesn't support Toxme Poslužitelj ne podržava ToxMe You're making too many requests. Wait an hour and try again Stvaraš previše zahtjeva. Pričekaj sat vremena i pokušaj ponovo This name is already in use Ovo se ime već koristi This Tox ID is already registered under another name Ovaj je Tox ID već registriran pod drugim imenom Please don't use a space in your name Nemoj koristiti znak razmaka u imenu Password incorrect Pogrešna lozinka You can't use this name Ne možeš koristiti ovo ime Name not found Ime nije pronađeno Tox ID not sent Tox ID nije poslan That user does not exist Taj korisnik ne postoji Error Pogreška qTox couldn't open your chat logs, they will be disabled. qTox nije mogao otvoriti tvoje dnevnike čavrljanja, bit će deaktivirani. None No camera device set Nijedna Desktop Desktop as a camera input for screen sharing Radna površina Problem with HTTPS connection Problem s HTTPS vezom Internal ToxMe error Interna ToxMe pogreška Reformatting text in progress.. Preformatiranje teksta u tijeku … Starts new instance and opens the login screen. Pokreće novu instancu i otvara prozor za prijavu. Dark Tamno Dark blue Tamnoplavo Dark olive Tamnomaslinasto Dark red Tamnocrveno Dark violet Tamnoljubičasto Failed to load profile automatically. Neuspjelo automatsko učitavanje profila. online contact status povezan(a) away contact status odsutan(na) busy contact status zauzet(a) offline contact status odspojen(a) blocked contact status blokiran(a) RemoveFriendDialog Remove friend Ukloni prijatelja Also remove chat history Također ukloni i povijest čavrljanja Remove Ukloni Are you sure you want to remove %1 from your contacts list? Zaista želiš ukloniti „%1” iz kontakata? Remove all chat history with the friend if set Ukloni povijest čavrljanja s prijateljima, ako je postavljeno ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Pritisni i povuci za odabir regije. Pritisni %1 za skrivanje/prikaz qTox prozora ili %2 za prekid. Space [Space] key on the keyboard Razmaknica Escape [Escape] key on the keyboard ESC Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Pritisni %1 za slanje snimke ekrana odabira, %2 za prikaz qTox prozora ili %3 za prekid. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Tekst nije pronađen. Start Započni SearchSettingsForm Form Obrazac Start search: Započni pretragu: from the end od kraja from the beginning od početka after date nakon datuma before date prije datuma 00.00.0000 00.00.0000 Case sensitive Razlikovanje veličine slova Whole words only Samo cijele riječi Use regular expressions Koristi regularne izraze SetPasswordDialog Set your password Postavi lozinku Confirm: Potvrdi: Password: Lozinka: Password strength: %p% Jačina lozinke: %p% The password is too short Lozinka je prekratka The password doesn't match. Lozinke se ne podudaraju. Confirm password Potvrdi lozinku Confirm password input Upis potvrdne lozinke Password input Upis lozinke Password input field, minimum 6 characters long Polje za upis lozinke, barem 6 znakova Settings Circle #%1 Kružok #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Dodaj prijatelja Do you want to add %1 as a friend? Želiš li dodati %1 kao prijatelja? User ID: Korisnički ID: Friend request message: Poruka uz zahtjev za prijateljstvo: Send Send a friend request Pošalji Cancel Don't send a friend request Prekini UserInterfaceForm None Nijedan User Interface Korisničko sučelje UserInterfaceSettings Chat Čavrljanje Base font: Osnovni font: px px Size: Veličina: New text styling preference may not load until qTox restarts. Nove postavke stila će se možda učitati tek nakon ponovnog pokretanja qToxa. Text Style format: Izgled teksta: Select text styling preference. Odabir izgleda teksta. Plaintext Običan tekst Show formatting characters Prikaži specijalne znakove Don't show formatting characters Nemoj prikazati specijalne znakove New message Nova poruka Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Otvori prozor qToxa kad primiš novu poruku, a da nijedan prozor još nije otvoren. Open window Otvori prozor Contact list Kontakti If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Ako je uključeno, grupno čavrljanje će se postaviti na vrh popisa prijatelja, u suprotnom će se postaviti ispod povezanih prijatelja. Place groupchats at top of friend list Postavi grupno čavrljanje na vrh popisa prijatelja Your contact list will be shown in compact mode. toolTip for compact layout setting Tvoj popis kontakata bit će prikazan u sažetom obliku. Compact contact list Sažeta lista kontakata Multiple windows mode Rad s više prozora Open each chat in an individual window Otvori svako čavrljanje u novom prozoru Emoticons Emotikoni Use emoticons Koristi emotikone Smiley Pack: Text on smiley pack label Smješko-paket: Emoticon size: Veličina emotikona: px px Theme Tema Style: Stil: Theme color: Boja teme: Timestamp format: Format vremenske oznake: Date format: Oblik datuma: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Ako je aktivirano, svakom kontaktu koji ga nema, dodijelit će se avatar na temelju Tox ID-a umjesto standardne slike. Program se mora ponovo pokrenuti. Use identicons instead of empty avatars Upotrijebi identikonse umjesto praznih avatara Use colored nicknames in chats Koristi obojene nadimke u čavrljanjima Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Prikaži obavijest kad primiš novu poruku i kad prozor nije odabran. Notify Obavijesti Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Upozori o novim porukama u grupnim čavrljanjima samo kad te se spominje. Group chats only notify when mentioned Upozori o grupnim čavrljanjima samo kad te se spominje Play sound Sviraj zvuk Play sound while Busy Sviraj zvuk dok si zauzet Notify via desktop notifications Obavijesti putem obavještavanja na radnoj površini Hide message sender and contents Sakrij pošiljaoca poruke i sadržaj Widget Online Button to set your status to 'Online' Povezan(a) Away Button to set your status to 'Away' Odsutan(na) Busy Button to set your status to 'Busy' Zauzet(a) toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore se nije uspio pokrenuti s tvojim proxy postavkama. qTox se ne može pokrenuti; promjeni postavke proxyja i ponovo pokreni Tox. Executable file popup title Izvršna datoteka You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Tražiš od qToxa da otvori izvršnu datoteku. Zlonamjerne izvršne datoteke mogu oštetiti podatke. Zaista želiš pokrenuti ovu datoteku? Couldn't request friendship Nije bilo moguće zatražiti prijateljstvo Message failed to send Poruka nije uspješno poslana Status Stanje toxcore failed to start, the application will terminate after you close this message. toxcore se nije pokrenuo, program će prestati s radom nakon što zatvoriš ovu poruku. Your name Tvoje ime Groupchat #%1 Grupno čavrljanje br. %1 Create new group... Stvori novu grupu … Add new circle... Dodaj novi kružok … %n New Friend Request(s) Novih zahtjeva za prijateljstvo: %n Novih zahtjeva za prijateljstvo: %n Novih zahtjeva za prijateljstvo: %n %n New Group Invite(s) Novih poziva u grupu: %n Novih poziva u grupu: %n Novih poziva u grupu: %n By Name Po imenu By Activity Po aktivnosti All Sve Online Povezan(a) Offline Odspojen(a) Friends Prijatelji Groups Grupe Search Contacts Pretraži kontakte Logout Tray action menu to logout user Odjavi se Exit Tray action menu to exit tox Izađi Filter... Filtriranje … File Datoteka Edit Uredi Contacts Kontakti Change Status Promijeni stanje Edit Profile Uredi profil Log out Odjavi se Add Contact... Dodaj kontakt … Next Conversation Sljedeća konverzacija Previous Conversation Prethodna konverzacija Show Tray action menu to show qTox window Prikaži Add friend title of the window Dodaj prijatelja Group invites title of the window Pozivi u grupu File transfers title of the window Prijenosi datoteka Settings title of the window Postavke My profile title of the window Moj profil Failed to send file "%1" Neuspjelo slanje datoteke „%1” File sent Datoteka je poslana sent you a friend request. ti je poslao(la) zahtjev za prijateljstvo. invites you to join a group. te poziva, da se pridružiš grupi. qTox/translations/hu.ts000066400000000000000000003304641415623743500155420ustar00rootroot00000000000000 AVForm Audio/Video Hang/Videó Default resolution Alapértelmezett felbontás Disabled Tiltva Select region Régió kiválasztása Screen %1 %1. képernyő Audio Settings Hangbeállítások Gain Erősítés Playback device Hangeszköz Use slider to set volume of your speakers. Használd a csúszkát a hangerő beállításához. Capture device Felvevőeszköz Volume Hangerő Video Settings Videóbeállítások Video device Videó eszköz Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. A kamera képfelbontásának beállítása. A magasabb érték jobb minőségű képet eredményez. Ne felejtsd el, hogy a jobb minőségű képhez gyorsabb internet-kapcsolatra van szükség. Néha az internet-kapcsolat nem elég gyors ahhoz, hogy továbbítani tudja a jobb minőségű videót, ami a videóhívások problémáihoz vezethet. Resolution Képfelbontás Rescan devices Eszközök keresése Test Sound Teszt hang Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. A kísérleti, visszangelnyomást biztosító hang alrendszer bekapcsolása. Az új beállítás életbe lépéséhez a qTox újraindítása szükséges. Enable experimental audio backend Szakértői hang backend bekapcsolása Audio quality Hangminőség Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. A továbbított hang minősége. Csökkentsd a beállítást, ha a sávszélesség nem elég gyors, vagy, ha csökkenteni szeretnéd a sávszélesség használatot. High (64 kbps) Magas (64 kbps) Medium (32 kbps) Közepes (32 kbps) Low (16 kbps) Alacsony (16 kbps) Very low (8 kbps) Nagyon alacsony (8 kbps) Threshold Küszöb érték AboutForm About Névjegy Original author: %1 Eredeti szerző: %1 You are using qTox version %1. Ön a qTox %1 verzióját használja. Commit hash: %1 Commit hash: %1 toxcore version: %1 toxcore verzió: %1 Qt version: %1 Qt verzió: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Az összes ismert probléma listája megtalálható a Github %1. Ha észrevesz egy hibát vagy biztonsági sérülékenységet a qTox-ban, kérjük jelentse azt a %2 wiki cikkünkben leírtaknak megfelelően. Click here to report a bug. Kattintson ide egy hiba bejelentéséért. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` %1 teljes listája Github-on bug-tracker Replaces `%1` in the `A list of all known…` hibakövetőben Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Hasznos hibajelentések írása contributors Replaces `%1` in `See a full list of…` Hozzájárulók AboutFriendForm Dialog Párbeszédablak username felhasználónév status message állapotüzenet Used aliases: Használt nevek: HISTORY OF ALIASES NÉV ELŐZMÉNYEK Automatically accept files from contact if set Automatikusan elfogad fájlokat a partnertől ha engedélyezve van Auto accept files Fájlok automatikus fogadása Default directory to save files: Alapértelmezett könyvtár a fájlok mentéséhez: Auto accept for this contact is disabled Ettől az ismerőstől nem fogad automatikusan fájlokat Auto accept call: Automatikus hívásfogadás: Manual Kézi Audio Hang Audio + Video Hang + Videó Automatically accept group chat invitations from this contact if set. Csoportmeghívások automatikus fogadása ettől az ismerőstől. Auto accept group invites Csoport meghívások automatikus elfogadása Remove history (operation can not be undone!) Előzmények törlése (ez a művelet nem visszavonható!) Notes Privát jegyzetek Input field for notes about the contact Beviteli mező a partnerről való jegyzetek készítéséhez You can save comment about this contact here. Itt elmenthetsz egy privát (számára nem látható) megjegyzést erről az ismerősről. History removed Előzmények eltávolítva Choose an auto accept directory popup title Válassz egy könyvtárat az automatikusan fogadott fájloknak <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Megerősítés Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Verzió License Licenc Authors Szerzők Known Issues Ismert problémák Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Partner hozzáadása Send friend request Partnerkérelem küldése Couldn't add friend Partner hozzáadása sikertelen Invalid Tox ID format Érvénytelen Tox ID formátum Add a friend Partner hozzáadása Friend requests Partner kérelmek Accept Elfogadás Reject Elutasítás Tox ID, either 76 hexadecimal characters or name@example.com Tox azonosító, 76 hexadecimális karakter, vagy nev@pelda.com Type in Tox ID of your friend Írja be a partnere Tox azonosítóját Friend request message Partnerkérelem üzenete Type message to send with the friend request or leave empty to send a default message Írja be az üzenetét a partnerkérelemhez, vagy hagyja üresen az alapértelmezett üzenet küldéséhez %1 Tox ID is invalid or does not exist Toxme error A(z) %1 Tox ID érvénytelen, vagy nem létezik You can't add yourself as a friend! When trying to add your own Tox ID as friend Nem tudod hozzáadni önmagad partnerként! Open contact list Partnerlista megnyitása Couldn't open file Fájl megnyitása sikertelen Couldn't open the contact file Error message when trying to open a contact list file to import Az ismerőslista fájl megnyitása sikertelen Invalid file Érvénytelen fájl We couldn't find any contacts to import in this file! Nem található importálható ismerős ebben a fájlban! Tox ID Tox ID of the person you're sending a friend request to Tox azonosító (Tox ID) either 76 hexadecimal characters or name@example.com Tox ID format description 76 hexadecimális karakter, vagy nev@pelda.com Message The message you send in friend requests Meghívóüzenet Open Button to choose a file with a list of contacts to import Megnyitás Send friend requests Barátkérelmek küldése %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 vagyok! Beszélünk Toxon? Import a list of contacts, one Tox ID per line Ismerősök listájának importálása, minden egyes sorban egy Tox azonosító (Tox ID) Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Készen állok %n ismerős importálására, kattints a küldésre a megerősítéshez Import contacts Ismerőslista importálása AdvancedForm Advanced Haladó Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Ha nem %1 biztos abban, mit csinál, kérem %2 változtasson meg itt semmit. Az itt lévő változások problémákhoz vezethetnek a qTox-ban, és adatvesztést (pl. előzmények) is okozhat. really teljesen not ne IMPORTANT NOTE FONTOS MEGJEGYZÉS Reset settings Beállítások visszaállítása All settings will be reset to default. Are you sure? Minden beállítás alaphelyzetbe fog állni. Biztos ebben? Yes Igen No Nem Call active popup title Hívás aktív You can't disconnect while a call is active! popup text Hívás közben a megszakítás nem lehetséges! Save File Fájl mentése Logs (*.log) Naplók (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox A beállítások mentése a munkakönyvtárba a szokásos konfigurációs mappa helyett Make Tox portable Hordozható Tox létrehozása Reset to default settings Beállítások visszaállítása alapértelmezettre Portable Hordozható Connection Settings Csatlakozási beállítások Enable IPv6 (recommended) Text on a checkbox to enable IPv6 IPv6 engedélyezése (ajánlott) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Ennek letiltása lehetővé teszi, hogy például Tor-on keresztül használja a Tox-ot. Ez terhelést jelent a Tox hálózatra, ezért csak akkor kapcsolja ki ezt az opciót, ha szükséges. Enable UDP (recommended) Text on checkbox to disable UDP UDP engedélyezése (ajánlott) Proxy type: Proxy típusa: Address: Text on proxy addr label Cím: Port: Text on proxy port label Port: None Nincs SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Újracsatlakozás Debug Hibakeresés Export Debug Log Hibakereső napló exportálása Copy Debug Log Hibakereső napló másolása Enable LAN discovery ChatForm Send a file Fájlküldés qTox wasn't able to open %1 A qTox nem tudta ezt megnyitni: %1 %1 calling %1 hívja Önt Call with %1 ended. %2 %1 hívása befejeződött. %2 Call duration: Hívás időtartama: Unable to open Megnyitás sikertelen Bad idea Rossz ötlet Calling %1 %1 hívása Failed to open temporary file Temporary file for screenshot Ideiglenes fájl megnyitása sikertelen qTox wasn't able to save the screenshot A qTox nem tudta elmenteni a képernyőképet %1 is typing %1 gépel Copy Másolás You're trying to send a sequential file, which is not going to work! Egymást követő fájlt próbált meg küldeni, ami nem fog működni! %1 is now %2 e.g. "Dubslow is now online" %1 most %2 Call with %1 ended unexpectedly. %2 A hívás váratlanul ért véget %1 ismerőssel. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Nem kezdeményezhető audio hívás Start audio call Hanghívás kezdeményezése End audio call Hanghívás befejezése Cancel audio call Hívás megszakítása Accept audio call Hanghívás fogadása Can't start video call Nem kezdeményezhető videohívás Start video call Videohívás kezdeményezése End video call Videohívás befejezése Cancel video call Videohívás megszakítása Accept video call Videóhívás elfogadása Sound can be disabled only during a call A hangot csak hívás közben lehet kikapcsolni Unmute call Hang bekapcsolása Mute call Hívás némítása Microphone can be muted only during a call A mikrofon némítása csak hívás közben lehetséges Unmute microphone Mikrofon bekapcsolása Mute microphone Mikrofon kikapcsolása ChatLog Copy Másolás Select all Összes kijelölése pending várakozik ChatTextEdit Type your message here... Írd ide az üzenetet... CircleWidget Rename circle Menu for renaming a circle Kör átnevezése Remove circle Menu for removing a circle Kör eltávolítása Open all in new window Összes megnyitása új ablakban Core /me offers friendship, "%1" /me szeretne felvenni az ismerőslistájára, "%1" Invalid Tox ID Error while sending friendship request Érvénytelen Tox azonosító (Tox ID) You need to write a message with your request Error while sending friendship request Írj egy üzenetet a kérelemhez Your message is too long! Error while sending friendship request Az üzenet túl hosszú! Friend is already added Error while sending friendship request A partner már hozzá van adva Groupchat %1 DesktopNotify New message Új üzenet Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Űrlap 10Mb 10MB 0kb/s 0kB/s ETA:10:10 Hátralévő idő:10:10 Filename Fájlnév Waiting to send... file transfer widget Küldésre várakozás... Accept to receive this file file transfer widget Egyezzen bele a fájl fogadásához Location not writable Title of permissions popup A hely írásvédett You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nincs írási jogosultsága a megadott helyre! Válasszon másikat, vagy zárja be a dialógusablakot. Save a file Title of the file saving dialog Fájl mentése Paused file transfer widget Szüneteltetve Resuming... file transfer widget Folytatás... Open file Fájl megnyitása Open file directory Tárolómappa megnyitása Pause transfer Átvitel szüneteltetése Cancel transfer Átvitel megszakítása Resume transfer Átvitel folytatása Accept transfer Átvitel elfogadása Remote Paused file transfer widget FilesForm Downloads Letöltések Uploads Feltöltések Transferred Files "Headline" of the window Átvitt fájlok FriendListWidget Today Ma Yesterday Tegnap Last 7 days Az utolsó 7 nap This month Ebben a hónapban Older than 6 Months 6 hónapnál régebbi Never Soha FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Partnerkérelmek Someone wants to make friends with you Valaki szeretne az Ön partnere lenni User ID: Felhasználó ID: Friend request message: Partnerkérelem üzenete: Accept Accept a friend request Elfogadás Reject Reject a friend request Elutasítás FriendWidget Invite to group Menu to invite a friend to a groupchat Meghívás csoportba Set alias... Álnév beállítás... Auto accept files from this friend context menu entry Fájlok automatikus elfogadása e partnertől Remove friend Menu to remove the friend from our friendlist Partner eltávolítása Choose an auto accept directory popup title Válasszon egy mappát az automatikus fájlfogadáshoz Open chat in new window Chat megnyitása új ablakban Remove chat from this window Chat eltávolítása ebből az ablakból To new group Új csoportba Invite to group '%1' Meghívás a '%1' csoportba Move to circle... Menu to move a friend into a different circle Mozgatás másik körbe... To new circle Új körbe Remove from circle '%1' Eltávolítás '%1' körből Move to circle "%1" Mozgatás "%1" körbe Show details Részletek megjelenítése New message Új üzenet Online Elérhető Away Távol Busy Elfoglalt Offline Nem elérhető GeneralForm General Általános Choose an auto accept directory popup title Válasszon egy mappát az automatikus elfogadáshoz GeneralSettings General Settings Általános beállítások The translation may not load until qTox restarts. A fordítás csak a qTox újraindítása után lesz betöltve. Language: Nyelv: Show system tray icon Mutassa a rendszertálca ikont Enable light tray icon. toolTip for light icon setting Engedélyezi a világos tálcaikont. Light icon Világos tálcaikon qTox will start minimized in tray. toolTip for Start in tray setting A qTox a tálcán minimalizálva fog elindulni. Start in tray Indítás a tálcán After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting A Bezárásra (X) kattintva a qTox a tálcára lesz minimalizálva, ahelyett, hogy kilépne. Close to tray Bezárás a tálcára After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting A Minimalizálásra (_) kattintva a qTox a tálcára lesz minimalizálva a rendszertálca helyett. Minimize to tray Minimalizálás a tálcára Autostart Automatikus indítás Set where files will be saved. Állítsa be a fájlok mentésének helyét. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Ezt beállíthatja, ha az adott partner nevén jobb klikket nyom. Autoaccept files Fájlok automatikus elfogadása Set to 0 to disable Állítson be nullát a letiltáshoz Your status is changed to Away after set period of inactivity. Az állapota "Távol"-ra változik, miután beállítja a tétlenség időtartamát. Auto away after (0 to disable): Automatikus távollét (0 a letiltáshoz): Show contacts' status changes Mutassa a partnerek állapotváltozásait Start qTox on operating system startup (current profile). qTox indítása az aktuális profillal az operációs rendszer indításakor. Default directory to save files: Alapértelmezett mappa a fájlok mentéséhez: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Üzenet küldése Smileys Emotikonok Send file(s) Fájl(ok) küldése Save chat log Chat naplófájl mentése Clear displayed messages Megjelenített üzenetek törlése Cleared Törölve Send a screenshot Képernyőkép küldése Quote selected text Kiválasztott szöveg idézése Copy link address Link címének másolása Confirmation Megerősítés You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Chat előzmények betöltése... Export to file Exportálás fájlba GenericNetCamView Tox video Tox videó Show Messages Üzenetek Mutatása Hide Messages Üzenetek Elrejtése Full Screen Toggle video preview Mute audio Mute microphone Mikrofon kikapcsolása End video call Videohívás befejezése Exit full screen GroupChatForm %1 has set the title to %2 %1 megváltoztatta a címet erre: %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Csoportok Create new group Új csoport létrehozása Group invites Csoport meghívások GroupInviteWidget Invited by %1 on %2 at %3. Join Csatlakozás Decline Elutasítás GroupWidget Set title... Cím beállítása... Quit group Menu to quit a groupchat Kilépés a csoportból Open chat in new window Chat megnyitása új ablakban Remove chat from this window Chat eltávolítása ebből az ablakból %n user(s) in chat Number of users in chat New Message Online Elérhető IdentitySettings Public Information Publikus információ Tox ID Tox azonosító This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Ez a csomó karakter megmondja más Tox kliensnek, hogyan csatlakozzon. Ossza ezt meg a partnerével a kommunikációhoz. Your Tox ID (click to copy) Az Ön Tox azonosítója (klikk a másoláshoz) Rename rename profile button Átnevezés Export export profile button Exportálás Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Engedélyezi az Ön Tox profiljának exportálását egy fájlba. A profil nem tartalmazza az Ön előzményeit. Delete delete profile button Törlés This QR code contains your Tox ID. You may share this with your friends as well. Ez a QR-kód tartalmazza a Tox ID-jét. Ezt is megoszthatja a barátaival. Save image Kép mentése Copy image Kép másolása Server Szerver Hide my name from the public list Név elrejtése a nyilvános listáról Register Regisztráció Your password Jelszó Update Frissítés Profile Profil Rename profile. tooltip for renaming profile button Profil átnevezése. Delete profile. delete profile button tooltip Profil törlése. Go back to the login screen tooltip for logout button Vissza a bejelentkezéshez Logout import profile button Kijelentkezés Remove password Jelszó törlése Change password Jelszóváltoztatás Register on ToxMe ToxMe regisztráció Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Név a ToxMe szolgáltatáshoz. Optional. Something about you. Or your cat. Tooltip for the Biography text. Nem kötelező. Pár mondat Önről. Vagy a macskájáról. Optional. Something about you. Or your cat. Tooltip for the Biography field. Nem kötelező. Pár mondat Önről. Vagy a macskájáról. ToxMe service to register on. ToxMe szolgáltatáshoz regisztráció. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ha ez nincs beállítva, a ToxMe bejegyzései nyilvánosan láthatók lesznek. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Jelszava és a titkosítás eltávolítása az Ön profiljáról. Name input Név bevitel Name visible to contacts Név látható a partnereknek Status message input Állapotüzenet bevitel Status message visible to contacts Állapotüzenet látható a partnereknek Your Tox ID Az Ön Tox-azonosítója Save QR image as file QR-kép mentése fájlként Copy QR image to clipboard QR-kép másolása Vágólapra ToxMe username to be shown on ToxMe ToxMe felhasználónév megjelenítése ToxMe-n Optional ToxMe biography to be shown on ToxMe Nem kötelező ToxMe biográfia megjelenítése ToxMe-n ToxMe service address ToxMe szolgáltatás címe Visibility on the ToxMe service Láthatóság a ToxMe szolgáltatásban Password Jelszó Update ToxMe entry ToxMe bejegyzés frissítése Rename profile. Profil átnevezése. Delete profile. Profil törlése. Export profile Profil exportálása Remove password from profile Jelszó eltávolítása a profilból Change profile password Profil jelszó megváltoztatása My name: Nevem: My status: Állapotüzenet: My username Felhasználónevem My biography Rólam My profile Saját profil LoadHistoryDialog Load History Dialog Előzmény betöltése Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Felhasználónév: Password: Jelszó: Confirm: Megerősítés: Password strength: %p% Jelszó erőssége: %p% Create Profile Profil létrehozása If the profile does not have a password, qTox can skip the login screen Amennyiben a profil nincs jelszóval védve, a qTox automatikusan bejelentkezhet Load automatically Automatikus betöltés Import Importálás Load Betöltés New Profile Új profil Load Profile Profil betöltése Couldn't create a new profile Új profil létrehozása nem sikerült The username must not be empty. A felhasználónév nem lehet üres. The password must be at least 6 characters long. A jelszónak legalább 6 karakterből kell állnia. The passwords you've entered are different. Please make sure to enter same password twice. A beírt jelszavak nem egyeznek. Győződjön meg róla, hogy ugyanazt a jelszót írta be kétszer. A profile with this name already exists. Már létezik ilyen nevű profil. Password protected profiles can't be automatically loaded. A jelszóval védett profilok automatikusan nem tölthetőek be. Couldn't load profile A profil betöltése nem sikerült There is no selected profile. You may want to create one. Nincs kiválasztott profil. Lehet, hogy új profilt szükséges létrehozni. Couldn't load this profile A profil betöltése nem sikerült This profile is already in use. Ez a profil már használatban van. Wrong password. Helytelen jelszó. Username input field Felhasználónév beviteli mező Password input field, you can leave it empty (no password), or type at least 6 characters Jelszó beviteli mező, hagyhatja üresen (nincs jelszó), vagy írjon be legalább 6 karaktert Password confirmation field Jelszó megerősítő mező Create a new profile button Új profil létrehozása gomb Profile list Profil lista List of profiles Profilok listája Password input Jelszó bevitel Load automatically checkbox Automatikus betöltés jelölőnégyzet Import profile Profil importálása Load selected profile button Kiválasztott profil betöltése gomb New profile creation page Új profil létrehozó oldal Loading existing profile page Meglévő profil oldal betöltése MainWindow Your name Az Ön neve Your status Az Ön állapotüzenete Add friends Partnerek hozzáadása Create a group chat Csoportos chat létrehozása View completed file transfers Befejezett fájlátvitelek mutatása Change your settings Beállítások változtatása Close Bezárás ... ... Open profile Profil megnyitása Open profile page when clicked Profil oldal megnyitása klikkeléskor Status message input Állapotüzenet bevitel Set your status message that will be shown to others Állítsa be állapotüzenetét, mely másoknak megjelenik Status Leírás Set availability status Elérhetőségi állapot beállítása Contact search Partner keresése Contact search input for known friends Keresési mező bevitel ismert partnerekhez Sorting and visibility Rendezés és láthatóság Set friends sorting and visibility Partnerek rendezése és láthatóságuk beállítása Open Add friends page Partnerek hozzáadása oldal megnyitása Groupchat Csoportos chat Open groupchat management page Csoportos chat menedzselési oldal megnyitása File transfers history Fájlátviteli előzmények Open File transfers history Fájlátviteli előzmények megnyitása Settings Beállítások Open Settings Beállítások megnyitása Nexus View OS X Menu bar Nézet Window OS X Menu bar Ablak Minimize OS X Menu bar Minimalizálás Bring All to Front OS X Menu bar Mind előrehozása Exit Fullscreen Teljes képernyő bezárása Enter Fullscreen Teljes képernyő NotificationEdgeWidget Unread message(s) Olvasatlan üzenet PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK AKTÍV PrivacyForm Privacy Adatvédelem Confirmation Megerősítés Do you want to permanently delete all chat history? Összes chat előzmény végleges törlése? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting A partnere látni fogja, amikor Ön gépel. Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Chat előzmények megtartása még fejlesztés alatt áll. Mentési formátum változások lehetségesek, melyek adatvesztést eredményezhetnek. Send typing notifications Gépelési értesítések küldése Keep chat history Chat előzmények megtartása NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam A NoSpam része a Tox azonosítójának. Ha kéretlen partnerfelkérésekkel bombázzák, változtassa meg a NoSpam értékét. A felhasználók nem fogják tudni felvenni a régi azonosítójával, de a jelenlegi ismerősei megmaradnak. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. A NoSpam része az azonosítójának és bármikor megváltoztatható. Ha kéretlen partnerfelkérésekkel bombázzák, változtassa meg a NoSpam-ot. Generate random NoSpam Véletlenszerű NoSpam generálása Privacy Adatvédelem BlackList Tiltólista Filter group message by group member's public key. Put public key here, one per line. Csoport üzenet szűrése a tagok publikus kulcsa alapján. Illeszd be ide a publikus kulcsokat, minden sorba egyet. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Nem sikerült az adatbázis jelszavának megváltoztatása. Valószínűleg sérült, vagy a régi jelszót kell használni. Toxing on qTox A qTox klienst használom ProfileForm Current profile: Aktuális profil: Remove Törlés Choose a profile picture Válasszon egy profilképet Error Hiba Unable to open this file. A fájlt nem sikerült megnyitni. Unable to read this image. A kép nem olvasható. The supplied image is too large. Please use another image. A kép mérete túl nagy. Válasszon egy másik képet. Rename "%1" renaming a profile "%1" átnevezése Couldn't rename the profile to "%1" A profilt nem sikerült "%1"-ra/-re átnevezni Location not writable Title of permissions popup A hely írásvédett You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nincs írási jogosultsága a megadott helyre! Válasszon másikat, vagy zárja be a dialógusablakot. Failed to copy file Fájl másolása nem sikerült The file you chose could not be written to. A kiválasztott fájlba nem lehetett írni. Really delete profile? deletion confirmation title Tényleg törli a profilt? Are you sure you want to delete this profile? deletion confirmation text Valóban törölni szeretné ezt a profilt? Files could not be deleted! deletion failed title A fájlokat nem sikerült törölni! Save save qr image Mentés Save QrCode (*.png) save dialog filter QrCode mentése (*.png) Nothing to remove Nincs mit eltávolítani Your profile does not have a password! Az Ön profilja nem tartalmaz jelszót! Really delete password? deletion confirmation title Tényleg törli a jelszót? Please enter a new password. Adjon meg egy új jelszót. Register (processing) Regisztráció (feldolgozás) Update (processing) Frissítés (feldolgozás) Done! Kész! Account %1@%2 updated successfully %1@%2 fiók sikeresen frissítve Successfully added %1@%2 to the database. Save your password %1@%2 sikeresen hozzáadva az adatbázishoz. Mentse el a jelszavát Toxme error Toxme hiba Register Regisztráció Update Frissítés Change password button text Jelszó változtatás Set profile password button text Profil jelszó létrehozása Current profile location: %1 Jelenlegi profil helye: %1 Couldn't change password Jelszó módosítása sikertelen This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Ez a karaktersorozat mutatja meg a többi Tox kliensnek, hogyan léphetnek kapcsolatba veled. Küldd el az ismerőseidnek, hogy felvehessenek az ismerőslistájukra. Ez az azonosító tartalmazza a NoSpam kódot (kék), és az ellenőrzőösszeget (szürke). Empty path is unavaliable Az üres útvonal nem elérhető Failed to rename Nem sikerült átnevezni Profile already exists A profil már létezik A profile named "%1" already exists. A "%1" nevű profil már létezik. Empty name Üres név Empty name is unavaliable Az üres név érvénytelen Empty path Üres útvonal Couldn't change password on the database, it might be corrupted or use the old password. Nem sikerült az adatbázis jelszavának megváltoztatása. Valószínűleg sérült, vagy a régi jelszót kell használni. Export profile Profil exportálása Tox save file (*.tox) save dialog filter Tox mentési fájl (*.tox) The following files could not be deleted: deletion failed text part 1 Nem sikerült törölni a ezeket a fájlokat: Please manually remove them. deletion failed text part 2 Kérlek, kézileg távolitsd el őket. Are you sure you want to delete your password? deletion confirmation text Biztosan törlöd a jelszavad? Images (%1) filetype filter Képek (%1) ProfileImporter Import profile import dialog title Profil importálása Tox save file (*.tox) import dialog filter Tox mentésfájl (*.tox) Ignoring non-Tox file popup title Nem Tox-fájl mellőzése Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Figyelem: A kiválasztott fájl nem egy Tox mentés, ezért nem kerül feldolgozásra. Profile already exists import confirm title A profil már létezik A profile named "%1" already exists. Do you want to erase it? import confirm text A(z) "%1" nevű profil már létezik. Szeretné törölni? File doesn't exist A fájl nem létezik Profile doesn't exist A profil nem létezik Profile imported Profil importálva %1.tox was successfully imported %1.tox sikeresen importálva QApplication Ok Rendben Cancel Mégsem Yes Igen No Nem LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Balról jobbra QMessageBox Couldn't add friend Ismerős hozzáadása sikertelen %1 is not a valid Toxme address. Érvénytelen Toxme cím: %1 You can't add yourself as a friend! When trying to add your own Tox ID as friend Nem tudod hozzáadni önmagad partnerként! QObject Tox URI to parse Tox URI elemzés Starts new instance and loads specified profile. Új folyamatot indít, és betölt egy megadott profilt. profile profil Default Alapértelmezett Blue Kék Olive Olajzöld Red Piros Violet Lila Incoming call... Bejövő hívás... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 vagyok! Beszélünk Toxon? Server doesn't support Toxme A szerver nem támogatja a Toxme-t You're making too many requests. Wait an hour and try again Túl sok kérelem. Várjon egy órát, majd próbálkozzon újra This name is already in use Ez a név már használatban van This Tox ID is already registered under another name Ez a Tox ID már regisztrálva van más név alatt Please don't use a space in your name Kérjük ne használjon szóközt a nevében Password incorrect Helytelen jelszó You can't use this name Ez a név nem használható Name not found Név nem található Tox ID not sent Tox ID nem lett elküldve That user does not exist A felhasználó nem létezik Error Hiba qTox couldn't open your chat logs, they will be disabled. A qTox nem tudta megnyitni a csevegésnaplót, ezért kikapcsolásra került. None No camera device set Nincs Desktop Desktop as a camera input for screen sharing Asztal Problem with HTTPS connection HTTPS-kapcsolat hiba Internal ToxMe error Belső ToxMe hiba Reformatting text in progress.. Szöveg formázása folyamatban.. Starts new instance and opens the login screen. Új folyamat indítása, és a bejelentkezési képernyő megnyitása. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status elérhető away contact status távol busy contact status elfoglalt offline contact status nem elérhető blocked contact status RemoveFriendDialog Remove friend Partner eltávolítása Also remove chat history Az előzményeket is távolítsa el Remove Eltávolít Are you sure you want to remove %1 from your contacts list? Biztos eltávolítja %1 partnert a partnerlistájáról? Remove all chat history with the friend if set Eltávolít minden chat előzményt a partnerével, ha ez be van állítva ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Kattintson és húzzon egy régió kiválasztásához. Nyomja meg a %1-t a qTox ablakának elrejtéséhez/mutatásához, %2-t a megszakításhoz. Space [Space] key on the keyboard Szóköz Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected A kiválasztott képernyőkép elküldéséhez nyomja meg a %1-t, a qTox ablak elrejtéséhez a %2-t vagy %3-t a megszakításhoz. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form Űrlap Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Állítsa be jelszavát Confirm: Megerősítés: Password: Jelszó: Password strength: %p% Jelszó erőssége: %p% The password is too short A jelszó túl rövid The password doesn't match. A jelszó nem egyezik. Confirm password Jelszó megerősítése Confirm password input Jelszó megerősítés bevitel Password input Jelszó bevitel Password input field, minimum 6 characters long Jelszó beviteli mező, minimum 6 karakter hosszú Settings Circle #%1 #%1. kör ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Partner hozzáadása Do you want to add %1 as a friend? Szeretné hozzáadni %1 felhasználót partnerének? User ID: Felhasználó ID: Friend request message: Partnerkérelem üzenete: Send Send a friend request Küldés Cancel Don't send a friend request Mégsem UserInterfaceForm None Nincs User Interface Felhasználói felület UserInterfaceSettings Chat Chat Base font: Betűtípus: px px Size: Méret: New text styling preference may not load until qTox restarts. A szövegbeállítások változtatásához a qToxot újra kell indítani. Text Style format: Szövegstílus formátum: Select text styling preference. Válassza ki a szöveg stílusát. Plaintext Egyszerű szöveg Show formatting characters Formázási karakterek mutatása Don't show formatting characters Formázási karakterek elrejtése New message Új üzenet Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting qTox ablak megnyitása új üzenet érkezésekor, ha nincs megnyitott ablak. Open window Ablak megnyitása Contact list Partnerlista If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Bekapcsolva a csoportos beszélgetések a partnerlista tetejére kerülnek, kikapcsolva a bejelentkezett partnerek alá. Place groupchats at top of friend list Csoportos beszélgetések a partnerlista tetejére Your contact list will be shown in compact mode. toolTip for compact layout setting Az Ön partnerlistája kompakt módban lesz mutatva. Compact contact list Kompakt partnerlista Multiple windows mode Többablakos mód Open each chat in an individual window Minden beszélgetés külön ablakban nyíljon meg Emoticons Hangulatjelek Use emoticons Emotikonok használata Smiley Pack: Text on smiley pack label Emotikon csomag: Emoticon size: Emotikon méret: px px Theme Téma Style: Stílus: Theme color: Téma színe: Timestamp format: Időbélyeg formátum: Date format: Dátum formátuma: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Ha be van kapcsolva, a profilkép nélküli ismerősöknek az alapértelmezett kép helyett egy kép lesz generálva a Tox azonosítójuk (Tox ID) alapján. Az új beállítás a qTox legközelebbi indításakor lép életbe. Use identicons instead of empty avatars Identikonok használata üres profilképek helyett Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Hang lejátszása Play sound while Busy Hangjelzés míg Elfoglalt Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Elérhető Away Button to set your status to 'Away' Távol Busy Button to set your status to 'Busy' Elfoglalt toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text A Tox nem indult el ezekkel a proxy beállításokkal. A qTox nem fut; módosítsa a beállításait, és indítsa újra. Executable file popup title Futtatható fájl You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Meg akart nyitni egy futtatható fájlt. Ezek a fájlok potenciálisan veszélyeztethetik a számítógépét. Valóban meg szeretné nyitni ezt a fájlt? Couldn't request friendship Partnerkérelem nem lehetséges Message failed to send Üzenet küldése sikertelen Status Állapot toxcore failed to start, the application will terminate after you close this message. A toxcore indítása sikertelen, az alkalmazás le fog állni az üzenet bezárásakor. Your name Név Groupchat #%1 #%1. csoport Create new group... Új csoport létrehozása... Add new circle... Új kör hozzáadása... %n New Friend Request(s) %n új barátkérelem %n New Group Invite(s) %n új csoportmeghívás By Name Név szerint By Activity Aktivitás szerint All Összes Online Elérhető Offline Nem elérhető Friends Barátok Groups Csoportok Search Contacts Partnerek keresése Logout Tray action menu to logout user Kijelentkezés Exit Tray action menu to exit tox Kilépés Filter... Szűrő... File Fájl Edit Szerkesztés Contacts Partnerek Change Status Állapot módosítása Edit Profile Profil szerkesztése Log out Kijelentkezés Add Contact... Partner Hozzáadása... Next Conversation Következő Beszélgetés Previous Conversation Előző beszélgetés Show Tray action menu to show qTox window Mutat Add friend title of the window Ismerős hozzáadása Group invites title of the window Csoport meghívások File transfers title of the window Fájl átvitelek Settings title of the window Beállítások My profile title of the window Saját profil Failed to send file "%1" A(z) %1 fájl küldése nem sikerült File sent sent you a friend request. invites you to join a group. qTox/translations/i18n.pri000066400000000000000000000041111415623743500160340ustar00rootroot00000000000000# For autocompiling qm-files. TRANSLATIONS = \ translations/ar.ts \ translations/be.ts \ translations/bg.ts \ translations/cs.ts \ translations/da.ts \ translations/de.ts \ translations/el.ts \ translations/eo.ts \ translations/es.ts \ translations/et.ts \ translations/fa.ts \ translations/fi.ts \ translations/fr.ts \ translations/he.ts \ translations/hr.ts \ translations/hu.ts \ translations/it.ts \ translations/ja.ts \ translations/jbo.ts \ translations/ko.ts \ translations/mk.ts \ translations/nl.ts \ translations/no_nb.ts \ translations/lt.ts \ translations/pl.ts \ translations/pr.ts \ translations/pt.ts \ translations/pt_BR.ts \ translations/ro.ts \ translations/ru.ts \ translations/sk.ts \ translations/sl.ts \ translations/sr.ts \ translations/sr_Latn.ts \ translations/sv.ts \ translations/sw.ts \ translations/ta.ts \ translations/tr.ts \ translations/ug.ts \ translations/uk.ts \ translations/zh_CN.ts \ translations/zh_TW.ts #rules to generate ts isEmpty(QMAKE_LUPDATE) { win32: QMAKE_LUPDATE = $$[QT_INSTALL_BINS]/lupdate.exe else: QMAKE_LUPDATE = $$[QT_INSTALL_BINS]/lupdate } #limitation: only on ts can be generated updatets.name = Creating or updating ts-files... updatets.input = _PRO_FILE_ updatets.output = $$TRANSLATIONS updatets.commands = $$QMAKE_LUPDATE ${QMAKE_FILE_IN} updatets.CONFIG += no_link no_clean QMAKE_EXTRA_COMPILERS += updatets #rules for ts->qm isEmpty(QMAKE_LRELEASE) { win32: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease.exe else: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease } updateqm.name = Compiling qm-files... updateqm.input = TRANSLATIONS updateqm.output = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.qm updateqm.commands = $$QMAKE_LRELEASE ${QMAKE_FILE_IN} -qm ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.qm updateqm.CONFIG += no_link no_clean target_predeps QMAKE_EXTRA_COMPILERS += updateqm # Release all the .ts files at once updateallqm = $$QMAKE_LRELEASE -silent $$TRANSLATIONS qTox/translations/it.ts000066400000000000000000003311701415623743500155350ustar00rootroot00000000000000 AVForm Audio/Video Audio/Video Default resolution Risoluzione di default Disabled Disabilitato Select region Seleziona regione Screen %1 Schermo %1 Audio Settings Impostazioni Audio Gain Volume microfono Playback device Dispositivo di output Use slider to set volume of your speakers. Usa il cursore per impostare il volume degli altoparlanti. Capture device Dispositivo di input Volume Livello audio Video Settings Impostazioni Video Video device Dispositivo video Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Imposta la risoluzione della tua webcam. Più alto è il valore, migliore sarà la qualità del video che vedranno i tuoi amici. Nota tuttavia,che per una qualità video migliore è richiesta una connessione ad internet più veloce. Può capitare che la tua connessione ad internet non sia abbastanza veloce per gestire qualità video elevate, questo può causare problemi con le chiamate video. Resolution Risoluzione Rescan devices Controlla nuovamente i dispositivi Test Sound Suono di prova Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Abilita il backend sonoro sperimentale con cancellazione dell'eco, riavvio di qTox neccessario. Enable experimental audio backend Abilita il backend audio sperimentale Audio quality Qualità audio Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Qualità audio trasmessa. Abbassa questo parametro se non hai abbastanza banda o se vuoi limitare il tuo traffico di rete. High (64 kbps) Alta (64 kbps) Medium (32 kbps) Media (32 kbps) Low (16 kbps) Bassa (16 kbps) Very low (8 kbps) Molto bassa (8 kbps) Threshold Ingresso AboutForm About Informazioni su qTox Original author: %1 Autore originale: %1 You are using qTox version %1. Stai utilizzando la versione %1. Commit hash: %1 Commit hash: %1 toxcore version: %1 Versione toxcore: %1 Qt version: %1 Versione Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Una lista di tutti i problemi può essere trovata alla nostra %1 su Github.Se scopri un bug o una vulnerabilità di sicurezza in qTox, ti preghiamo di segnalarcelo in accordo con le nostre linee guida nel nostro %2 articolo del wiki. Click here to report a bug. Clicca qui per segnalare un bug. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Consulta l'elenco completo dei %1 su Github bug-tracker Replaces `%1` in the `A list of all known…` Log dei bug Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Segnalazione di bug contributors Replaces `%1` in `See a full list of…` contributori AboutFriendForm Dialog Finestra di dialogo username Username status message messaggio di stato Used aliases: Soprannomi usati: HISTORY OF ALIASES Cronologia dei soprannomi Automatically accept files from contact if set Accetta automaticamente files dal contatto se selezionato Auto accept files Accetta automaticamente i file ricevuti Default directory to save files: Cartella predefinita per salvare i files: Auto accept for this contact is disabled Non scaricare automaticamente i file per questo contatto Auto accept call: Accetta automaticamente le chiamate: Manual Manuale Audio Audio Audio + Video Audio + Video Automatically accept group chat invitations from this contact if set. Accetta automaticamente inviti per chat di gruppo da questo contatto. Auto accept group invites Accetta automaticamente gli inviti alle chat di gruppo Remove history (operation can not be undone!) Rimuovi la cronologia (questa operazione non può essere annullata!) Notes Appunti Input field for notes about the contact Campo per gli appunti sul contatto You can save comment about this contact here. Puoi salvare un commento per questo contatto qui. History removed Cronologia rimossa Choose an auto accept directory popup title Scegli dove salvare i file scaricati automaticamente <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Questa è la chiave pubblica del tuo amico, usatela per verificare la sua identità attraverso un altro canale. Non è possibile inviarla ad altre persone in modo che possano aggiungere questo contatto.</p></body></html> Public key (not ToxID): Chiave pubblica (non ToxID): Confirmation Conferma Are you sure to remove %1 chat history? Sei sicuro di rimuovere %1 cronologia chat? Failed to remove chat history with %1! Impossibile rimuovere la cronologia delle chat con %1! AboutSettings Version Versione License Licenza Authors Autori Known Issues Problemi Noti Open update download link Apri il link per il download dell'aggiornamento Update available Aggiornamento disponibile qTox is up to date ✓ qTox è aggiornato ✓ AddFriendForm Add Friends Aggiungi Contatto Send friend request Invia richiesta d'amicizia Couldn't add friend Impossibile aggiungere il contatto Invalid Tox ID format Tox ID non valido Add a friend Aggiungi un contatto Friend requests Richieste di amicizia Accept Accetta Reject Rifiuta Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, 76 caratteri esadecimali oppure nome@example.com Type in Tox ID of your friend Inserisci il Tox ID di un tuo amico Friend request message Messaggio di richiesta dell'amico Type message to send with the friend request or leave empty to send a default message Scrivi un messaggio da inviare con la richiesta di amicizia o lascia vuoto per inviare il messaggio predefinito %1 Tox ID is invalid or does not exist Toxme error %1 ID Tox non valido o inesistente You can't add yourself as a friend! When trying to add your own Tox ID as friend Non puoi aggiungerti come amico! Open contact list Apri la lista dei contatti Couldn't open file Impossibile aprire il file Couldn't open the contact file Error message when trying to open a contact list file to import Impossibile aprire il file contatto Invalid file File non valido We couldn't find any contacts to import in this file! Non abbiamo trovato nessun contatto da importare in questo file! Tox ID Tox ID of the person you're sending a friend request to ID Tox either 76 hexadecimal characters or name@example.com Tox ID format description 76 caratteri esadecimali oppure nome@esempio.com Message The message you send in friend requests Messaggio Open Button to choose a file with a list of contacts to import Apri Send friend requests Invia richieste di amicizia %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Ciao, sono %1! Toxiamo? Import a list of contacts, one Tox ID per line Importa una lista di contatti, un ID Tox per linea Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Pronto per importare %n contatto(i), premi invia per confermare Pronto per importare %n contatti, premi invia per confermare Import contacts Importa contatti AdvancedForm Advanced Avanzate Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. A meno che non sai %1 cosa stai facendo, si prega di %2 cambiare nulla qui. Le modifiche apportate qui potrebbero portare a problemi con qTox, e anche per la perdita di dati, ad esempio la cronologia. really realmente not non IMPORTANT NOTE NOTA IMPORTANTE Reset settings Resetta impostazioni All settings will be reset to default. Are you sure? Tutte le impostazioni saranno ripristinate ai valori predefiniti. Sei sicuro? Yes Si No No Call active popup title Chiamata in corso You can't disconnect while a call is active! popup text Non puoi disconnetterti mentre c'è una chiamata in corso! Save File Salva File Logs (*.log) I Log (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Slava le impostazioni nella directory di lavoro corrente, invece della directory di default Make Tox portable Rendi qTox portabile Reset to default settings Reimposta impostazioni di default Portable Potabile Connection Settings Impostazioni Connessione Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Abilita IPv6 (consigliato) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Disabilitando questo sarà possibile usare qTox con Tor. Tuttavia verrà aggiunto carico alla rete Tox, quindi disabilitare solo se necessario. Enable UDP (recommended) Text on checkbox to disable UDP Abilita UDP (consigliato) Proxy type: Proxy: Address: Text on proxy addr label Indirizzo: Port: Text on proxy port label Porta: None Nessuno SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Riconnetti Debug Debug Export Debug Log Esporta il Log di Debug Copy Debug Log Copia il Log di Debug Enable LAN discovery Attivare la scoperta della LAN ChatForm Send a file Invia un file qTox wasn't able to open %1 qTox non è riuscito ad aprire %1 %1 calling %1 ti sta chiamando Calling %1 Stai chiamando %1 Failed to open temporary file Temporary file for screenshot Impossibile aprire il file temporaneo qTox wasn't able to save the screenshot qTox non è stato in grado di salvare lo screenshot Call with %1 ended. %2 Chiamata con %1 terminata. %2 Call duration: Durata chiamata: Unable to open Impossibile da aprire Bad idea Cattiva idea %1 is typing %1 sta scrivendo Copy Copia You're trying to send a sequential file, which is not going to work! Stai cercando di mandare un file sequenziale,che non funzionerà! %1 is now %2 e.g. "Dubslow is now online" %1 è %2 adesso Call with %1 ended unexpectedly. %2 La chiamata con %1 è terminata inaspettatamente. %2 Filename contained illegal characters Il nome del file conteneva caratteri non autorizzati Illegal characters have been changed to _ so you can save the file on windows. I caratteri non autorizzati sono stati cambiati in _ in modo da poter salvare il file su windows. ChatFormHeader Can't start audio call Impossibile avviare chiamate audio Start audio call Avvia chiamata audio End audio call Termina chiamata Cancel audio call Annulla chiamata Accept audio call Accetta chiamata Can't start video call Impossibile avviare una videochiamata Start video call Avvia videochiamata End video call Termina videochiamata Cancel video call Annulla videochiamata Accept video call Accetta videochiamata Sound can be disabled only during a call Il suono può essere disabilitato solo durante una chiamata Unmute call Attiva audio Mute call Disattiva audio Microphone can be muted only during a call Il microfono può essere silenziato solo durante una chiamata Unmute microphone Attiva microfono Mute microphone Disattiva microfono ChatLog Copy Copia Select all Seleziona tutto pending in attesa ChatTextEdit Type your message here... Scrivi il tuo messaggio qui... CircleWidget Rename circle Menu for renaming a circle Rinomina circolo Remove circle Menu for removing a circle Elimina circolo Open all in new window Apri tutto in una nuova finestra Core /me offers friendship, "%1" /me ti ha aggiunto come contatto, "%1" Invalid Tox ID Error while sending friendship request ID di Tox non valido You need to write a message with your request Error while sending friendship request Scrivi un messaggio per la richiesta d'amicizia Your message is too long! Error while sending friendship request Il tuo messaggio è troppo lungo! Friend is already added Error while sending friendship request Questo contatto è già presente Groupchat %1 Chat di gruppo %1 DesktopNotify New message Nuovo messaggio Incoming file transfer Trasferimento file in arrivo Friend request received Richiesta di amicizia ricevuta New group message Nuovo messaggio di gruppo Group invite received Invito di gruppo ricevuto FileTransferWidget Form Modulo 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 ETA:10:10 Filename Nome file Waiting to send... file transfer widget In attesa di inviare... Accept to receive this file file transfer widget Accetta la ricezione di questo file Location not writable Title of permissions popup Posizione non scrivibile You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Non hai sufficienti permessi per scrivere in questa locazione. Scegli un'altra posizione, o annulla il salvataggio. Resuming... file transfer widget Riprendendo... Cancel transfer Annulla trasferimento Pause transfer Metti in pausa il trasferimento Resume transfer Riprendi trasferimento Accept transfer Accetta trasferimento Save a file Title of the file saving dialog Salva file Paused file transfer widget In pausa Open file Apri file Open file directory Apri cartella Remote Paused file transfer widget Remoto in pausa FilesForm Transferred Files "Headline" of the window File Trasferiti Downloads File Ricevuti Uploads File Inviati FriendListWidget Today Oggi Yesterday Ieri Last 7 days Ultimi 7 giorni This month Questo mese Older than 6 Months Più vecchi di 6 mesi Never Mai FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Richiesta d'amicizia Someone wants to make friends with you Qualcuno vuole chattare con te User ID: ID Utente: Friend request message: Messaggio della richiesta d'amicizia: Accept Accept a friend request Accetta Reject Reject a friend request Rifiuta FriendWidget Auto accept files from this friend context menu entry Accetta automaticamente i file inviati da questo contatto Invite to group Menu to invite a friend to a groupchat Invita nel gruppo Move to circle... Menu to move a friend into a different circle Sposta nel circolo... To new circle In un nuovo circolo Remove from circle '%1' Rimuovi dal circolo "%1" Move to circle "%1" Sposta nel circolo "%1" Set alias... Imposta soprannome... Remove friend Menu to remove the friend from our friendlist Rimuovi contatto Show details Mostra dettagli Choose an auto accept directory popup title Scegli dove salvare i file accettati automaticamente New message Nuovo messaggio Online Online Away Assente Busy Occupato Offline Offline Open chat in new window Apri la chat in una nuova finestra Remove chat from this window Rimuovi chat da questa finestra To new group In un nuovo gruppo Invite to group '%1' Invita nel gruppo '%1' GeneralForm General Generale Choose an auto accept directory popup title Scegli dove salvare i file accettati automaticamente GeneralSettings General Settings Impostazioni Generali The translation may not load until qTox restarts. La traduzione potrebbe non essere caricata fino al prossimo riavvio di qTox. Language: Lingua: Show system tray icon Mostra icona nella barra di sistema Light icon Usa icona brillante Enable light tray icon. toolTip for light icon setting Abilita icona brillante nella trybar. qTox will start minimized in tray. toolTip for Start in tray setting qTox sarà avviato minimizzato nella barra di sistema. After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Premendo l'icona "chiudi" (X) qTox sarà minimizzato nella barra di sistema invece che essere chiuso. After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Premendo l'icona "minimizza" (_) qTox sarà minimizzato nella barra di sistema invece che nella barra delle applicazioni. Set where files will be saved. Scegli dove salvare i file ricevuti. Set to 0 to disable Imposta 0 per disabilitare Auto away after (0 to disable): Mostra come assente dopo (0 per disabilitare): Autoaccept files Accetta automaticamente i file Default directory to save files: Cartella predefinita per salvare i file: Start qTox on operating system startup (current profile). Apri qTox all'avvio del sistema operativo (profilo corrente). Start in tray Avvia nella barra di sistema Close to tray Chiudi nella barra di sistema Minimize to tray Minimizza nella barra di sistema Show contacts' status changes Mostra quando i contatti cambiano stato Autostart Avvia automaticamente You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Puoi impostare questa preferenza per ogni singolo contatto usando il click destro sul suo nome. Your status is changed to Away after set period of inactivity. Il tuo stato sarà cambiato in "Assente" dopo il periodo di inattività indicato. Check for updates Controllare gli aggiornamenti Spell checking Controllo ortografico Max autoaccept file size (0 to disable): Dimensione massima del file per autoaccettare (0 per disabilitare): MB MB GenericChatForm Send message Invia messaggio Smileys Emoticons Send file(s) Invia file Send a screenshot Invia uno screenshot Save chat log Salva il log della chat Clear displayed messages Rimuovi messaggi visualizzati Cleared Pulito Quote selected text Quota testo selezionato Copy link address Copia il link dell'indirizzo Confirmation Conferma You are sure that you want to clear all displayed messages? Sei sicuro di voler cancellare tutti i messaggi visualizzati? Search in text Cerca nel testo Go to current date Vai alla data corrente Load chat history... Carica cronologia chat... Export to file Esporta su file GenericNetCamView Tox video Video Tox Show Messages Mostra messaggi Hide Messages Nascondi messaggi Full Screen Schermo intero Toggle video preview Attiva/Disattiva anteprima video Mute audio Disattiva audio Mute microphone Disattiva microfono End video call Terminare videochiamata Exit full screen Esci da schermo intero GroupChatForm %1 has set the title to %2 %1 ha impostato il titolo a %2 %1 has joined the group %1 ha aderito al gruppo %1 is now known as %2 % 1 è ora noto come %2 %1 has left the group %1 ha lasciato il gruppo %n user(s) in chat Number of users in chat %n utente in chat %n utenti in chat mute muto unmute riattivare l'audio GroupInviteForm Groups Gruppi Create new group Crea un nuovo gruppo Group invites Inviti di gruppo GroupInviteWidget Invited by %1 on %2 at %3. Invitato da %1 il %2 alle %3. Join Unisciti Decline Rifiuta GroupWidget Quit group Menu to quit a groupchat Esci dal gruppo Set title... Imposta nome gruppo... Open chat in new window Apri la chat in una nuova finestra Remove chat from this window Rimuovi la chat da questa finestra %n user(s) in chat Number of users in chat %n utente in chat %n utenti in chat New Message Nuovo messaggio Online Online IdentitySettings Public Information Informazioni Pubbliche Tox ID ID Tox This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Questo mucchio di caratteri serve agli altri client Tox per contattarti. Condividilo con chi vuoi comunicare. Your Tox ID (click to copy) Il tuo Tox ID (clicca per copiare) This QR code contains your Tox ID. You may share this with your friends as well. Questo codice QR contiene il tuo Tox ID. Puoi condividere questo codice QR al posto del tuo Tox ID. Save image Salva immagine Copy image Copia immagine Profile Profilo Rename profile. tooltip for renaming profile button Rinomina profilo. Delete profile. delete profile button tooltip Elimina profilo. Go back to the login screen tooltip for logout button Torna alla schermata di login Logout import profile button Esci Remove password Rimuovi password Change password Cambia password Rename rename profile button Rinomina Export export profile button Esporta Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Esporta il profilo corrente in un file. I profili non contengono la cronologia messaggi. Delete delete profile button Elimina Server Server Hide my name from the public list Nascondi il mio nome dalla lista pubblica Register Registrati Your password La tua password Update Aggiorna Register on ToxMe Registrati su ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Nome per il servizio ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Facoltativo. Qualcosa su di te o il tuo gatto. Optional. Something about you. Or your cat. Tooltip for the Biography field. Facoltativo. Qualcosa su di te o il tuo gatto. ToxMe service to register on. Servizio ToxMe a cui registrarsi. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Se non è impostata, le registrazioni ToxMe sono pubblicamente visibili. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Rimuovi la password e la crittografia dal tuo profilo. Name input Immissione Nome Name visible to contacts Nome visibile ai contatti Status message input Immissione stato messaggio Status message visible to contacts Stato del messaggio visibile ai contatti Your Tox ID Il tuo ID Tox Save QR image as file Salva immagine QR come file Copy QR image to clipboard Copia immagine QR negli appunti ToxMe username to be shown on ToxMe Nome utente ToxMe da mostrare su ToxMe Optional ToxMe biography to be shown on ToxMe Biografia opzionale ToxMe da mostrare su ToxMe ToxMe service address Servizio indirizzo ToxMe Visibility on the ToxMe service Visibilità sul servizio ToxMe Password Password Update ToxMe entry Aggiorna accesso ToxMe Rename profile. Cambio nome profilo. Delete profile. Cancella profilo. Export profile Salva profilo Remove password from profile Rimuovi password dal profilo Change profile password Cambia la password del profilo My name: Il mio nome: My status: Il mio stato: My username Il mio username My biography La mia biografia My profile Il mio profilo LoadHistoryDialog Load History Dialog Carica cronologia chat Load history Carica cronologia from da to a (about 100 messages are loaded) (circa 100 messaggi sono caricati) Select Date Dialog Finestra di dialogo Seleziona data Select a date Seleziona una data LoginScreen Username: Nome profilo: Password: Password: Confirm: Conferma: Password strength: %p% Robustezza password: %p% Load automatically Accedi automaticamente Load Accedi Load Profile Accedi al profilo If the profile does not have a password, qTox can skip the login screen Se il profilo non è protetto da una password, qTox può saltare questa schermata New Profile Nuovo Profilo Create Profile Crea Profilo Couldn't create a new profile Impossibile creare un nuovo profilo The username must not be empty. Il nome non può essere vuoto. The password must be at least 6 characters long. La password deve essere lunga almeno 6 caratteri. The passwords you've entered are different. Please make sure to enter same password twice. Le password che hai inserito sono diverse. Assicurati di inserire la stessa password due volte. A profile with this name already exists. Un profilo con questo nome esiste già. Couldn't load this profile Impossibile caricare il profilo This profile is already in use. Questo profilo è già in uso. Wrong password. Password errata. Import Importa Password protected profiles can't be automatically loaded. I profili protetti da una password non possono essere caricati automaticamente. Couldn't load profile Impossibile aprire il profilo There is no selected profile. You may want to create one. Non è stato selezionato nessuno profilo. È possibile crearne uno nuovo. Username input field Campo per l'inserimento dell'Username Password input field, you can leave it empty (no password), or type at least 6 characters Campo per l'inserimento della Password, si può lasciare vuota (nessuna password), o digitare almeno 6 caratteri Password confirmation field Campo per la conferma della password Create a new profile button Pulsante per la creazione di un nuovo profilo Profile list Lista profili List of profiles Lista dei profili Password input Inserimento password Load automatically checkbox Casella per caricare automaticamente Import profile Importa profilo Load selected profile button Pulsante per caricare il profilo selezionato New profile creation page Pagina per la creazione di un nuovo profilo Loading existing profile page Carica la pagina del profilo esistente MainWindow Your name qTox User Your status Toxing on qTox Add friends Aggiungi contatto Create a group chat Crea un gruppo View completed file transfers Visualizza i trasferimenti completati Change your settings Cambia le impostazioni Close Chiudi ... ... Open profile Apri profilo Open profile page when clicked Apri pagina profilo quando selezionata Status message input Immissione messaggio di stato Set your status message that will be shown to others Imposta il tuo messaggio di stato che sarà mostrato agli altri Status Stato Set availability status Imposta lo stato di disponibilità Contact search Ricerca Contatto Contact search input for known friends Immissione ricerca contatto per amici conosciuti Sorting and visibility Ordinamento e visibilità Set friends sorting and visibility Imposta ordinamento e visibilità degli amici Open Add friends page Apri pagina aggiungi amici Groupchat Chat di Gruppo Open groupchat management page Apri pagina di gestione chat di gruppo File transfers history Storia dei trasferimenti file Open File transfers history Apri la cronologia dei trasferimenti del file Settings Impostazioni Open Settings Apri Impostazioni Nexus View OS X Menu bar Vedi Window OS X Menu bar Finestra Minimize OS X Menu bar Minimizza Bring All to Front OS X Menu bar Porta tutto in primo piano Exit Fullscreen Esci dal Fullscreen Enter Fullscreen Metti in Fullscreen NotificationEdgeWidget Unread message(s) Messaggio non letto Messaggi non letti PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK ABILITATO PrivacyForm Privacy Privacy Confirmation Conferma Do you want to permanently delete all chat history? Vuoi cancellare permanentemente la cronologia della chat? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting I tuoi contatti potranno vedere se stai digitando un messaggio. Send typing notifications Mostra agli altri quando sto scrivendo Keep chat history Salva cronologia chat NoSpam Niente Spam NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam Il valore NoSpam è parte del tuo ID Tox. Se ricevi molte richieste di amicizia indesiderate, cambia questo valore. Le persone non saranno più in grado di aggiungerti con il tuo vecchio Tox ID, ma manterrai i contatti attuali. Generate random NoSpam Genera valore casuale Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Il salavataggio della cronologia chat è ancora in sviluppo. Il formato del file potrebbe cambiare (questo potrebbe causare perdita di dati). NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Il valore NoSpam è parte del tuo Tox ID che può essere cambiata a piacimento. Se ricevi molte richieste di amicizia indesiderate cambia questo valore. Privacy Privacy BlackList Lista Nera Filter group message by group member's public key. Put public key here, one per line. Filtra i messaggi di gruppo in base alla chiave publica di un membro. Inserisci le chiavi qui, una per linea. Profile Failed to derive key from password, the profile won't use the new password. Impossibile derivare alla chiave dalla password, questo profilo continuerà ad utilizzare la vecchia password. Couldn't change password on the database, it might be corrupted or use the old password. Impossiblie cambiare la password nel database, potrebbe essere corrotto o usare una vecchia password. Toxing on qTox Toxando su qTox ProfileForm Choose a profile picture Scegli un'immagine per il profilo Current profile: Profilo attuale: Error Errore The supplied image is too large. Please use another image. L'immagine selezionata è troppo grande. Per favore scegli un'immagine più piccola. Rename "%1" renaming a profile Rinomina "%1" Couldn't rename the profile to "%1" Impossibile rinominare il profilo in "%1" Location not writable Title of permissions popup Posizione non scrivibile You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Non hai sufficienti permessi per scrivere in questa locazione. Scegli un'altra posizione, o annulla il salvataggio. Really delete profile? deletion confirmation title Eliminare profilo? Save save qr image Salva Save QrCode (*.png) save dialog filter Salva Codice QR (*.png) Nothing to remove Nulla da rimuovere Your profile does not have a password! Il profilo non ha nessuna password! Really delete password? deletion confirmation title Rimuovere password? Please enter a new password. Inserisci una nuova password. Failed to copy file Impossibile copiare il file Unable to open this file. Impossibile aprire il file. Unable to read this image. Impossibile leggere l'immagine. The file you chose could not be written to. Il file che hai scelto non può essere copiato. Are you sure you want to delete this profile? deletion confirmation text Sei sicuro di voler eliminare questo profilo? Remove Rimuovi Files could not be deleted! deletion failed title I file non possono essere cancellati! Register (processing) Registrazione (In corso) Update (processing) Aggiornamento (In corso) Done! Finito! Account %1@%2 updated successfully L'Account %1@%2 è stato aggiornato con successo Successfully added %1@%2 to the database. Save your password Aggiunto con successo %1@% 2 al database. Salva la tua password Toxme error Errore Toxme Register Registra Update Aggiorna Change password button text Cambia password Set profile password button text Imposta una password per il profilo Current profile location: %1 Posizione del profilo attuale: %1 Couldn't change password Impossibile cambiare la password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Questa serie di cartteri informa i client Tox su come contattarti. Condividila con i tuoi amici per comunicare. Questo ID include il codice NoSpam (in blu), e il checksum (in grigio). Empty path is unavaliable Percorso vuoto non permesso Failed to rename Impossibile rinominare Profile already exists Profilo già esistente A profile named "%1" already exists. Il profilo "%1" esiste già. Empty name Nome vuoto Empty name is unavaliable Nome vuoto non disponibile Empty path Percorso vuoto Couldn't change password on the database, it might be corrupted or use the old password. Impossibile cambiare la password nel database, potrebbe essere corrotto o usare una password vecchia. Export profile Esporta profilo Tox save file (*.tox) save dialog filter File di salvataggio di Tox (*.tox) The following files could not be deleted: deletion failed text part 1 I seguenti file non possono essere cancellati: Please manually remove them. deletion failed text part 2 Rimuovili manualmente. Are you sure you want to delete your password? deletion confirmation text Sei sicuro di voler rimuovere la password? Images (%1) filetype filter Immagini (%1) ProfileImporter Import profile import dialog title Importa profilo Tox save file (*.tox) import dialog filter File di salvataggio di Tox (*.tox) Ignoring non-Tox file popup title File ignorato Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Attenzione: Hai scelto un file che non contiene un profilo Tox; Questo file verrà ignorato. Profile already exists import confirm title Profilo già esistente A profile named "%1" already exists. Do you want to erase it? import confirm text Un profilo chiamato "%1" esiste già. Vuoi sovrascriverlo? File doesn't exist File non esistente Profile doesn't exist Profilo non esistente Profile imported Profilo importato %1.tox was successfully imported %1.tox è stato importato con successo QApplication Ok Ok Cancel Annulla Yes Si No No LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Impossibile aggiungere l'amico %1 is not a valid Toxme address. %1 non è un indirizzo Toxme valido. You can't add yourself as a friend! When trying to add your own Tox ID as friend Non puoi aggiungere te stesso come amico! QObject Tox URI to parse URI Tox da interpretare Starts new instance and loads specified profile. Avvia una nuova istanza caricando il profilo selezionato. profile profilo Default Default Blue Blu Olive Oliva Red Rosso Violet Viola Incoming call... Chiamata in arrivo... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Ciao, sono %1! Posso aggiungerti alla mia lista contatti? None No camera device set Nessuno Server doesn't support Toxme Il server non supporta Toxme You're making too many requests. Wait an hour and try again Stai generando troppe richieste. Aspetta un'ora e prova di nuovo This name is already in use Questo nome è già in uso This Tox ID is already registered under another name Questo Tox ID è gia registrato con un altro nome Please don't use a space in your name Non usare spazi nel tuo nome Password incorrect Password non corretta You can't use this name Non puoi usare questo nome Name not found Nome non trovato Tox ID not sent Tox ID non inviato That user does not exist Questo utente non esiste Error Errore qTox couldn't open your chat logs, they will be disabled. Impossibile aprire la cronologia chat, verrà disibilitata. Desktop Desktop as a camera input for screen sharing Scrivania Problem with HTTPS connection Problema con la connessione HTTPS Internal ToxMe error Errore interno di ToxMe Reformatting text in progress.. Riformattazione del testo in corso.. Starts new instance and opens the login screen. Apre una nuova istanza con la finestra d'accesso. Dark Scuro Dark blue Blu scuro Dark olive Verde scuro Dark red Rosso scuro Dark violet Viola scuro Failed to load profile automatically. Impossibile caricare automaticamente il profilo. online contact status online away contact status assente busy contact status occupato offline contact status offline blocked contact status bloccato RemoveFriendDialog Remove friend Rimuovi contatto Also remove chat history Rimuovi anche la cronologia chat Remove Rimuovi Are you sure you want to remove %1 from your contacts list? Sei sicuro di voler rimuovere %1 dalla tua lista contatti? Remove all chat history with the friend if set Rimuove tutta la cronologia della chat con l'amico se impostata ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Fare clic e trascinare per selezionare una regione. Premere %1 per nascondere/mostrare la finestra qTox, o %2 per annullare. Space [Space] key on the keyboard Spazio Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Premi %1 per inviare uno screenshot della selezione, %2 per nascondere/mostrare la finestra qTox, o %3 per annullare. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Testo non trovato. Start Inizio SearchSettingsForm Form Formulario Start search: Iniziare ricerca: from the end dalla fine from the beginning dall'inizio after date dopo la data before date prima della data 00.00.0000 00/00/0000 Case sensitive Sensibile alle maiuscole e minuscole Whole words only Solo parole intere Use regular expressions Utilizzare espressioni comuni SetPasswordDialog Set your password Imposta password Confirm: Conferma: Password strength: %p% Robustezza password: %p% The password is too short La password è troppo corta The password doesn't match. Le password non corrispondono. Password: Password: Confirm password Conferma password Confirm password input Conferma immissione password Password input Immissione password Password input field, minimum 6 characters long Campo immissione password,minimo 6 caratteri lungo Settings Circle #%1 Circolo #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Aggiungi un contatto Do you want to add %1 as a friend? Vuoi aggiungere %1 come contatto? User ID: Tox ID del contatto: Friend request message: Messaggio da inviare assieme alla richiesta d'amicizia: Send Send a friend request Invia Cancel Don't send a friend request Annulla UserInterfaceForm None Nessuno User Interface Interfaccia Utente UserInterfaceSettings Chat Chat Base font: Carattere di base: px px Size: Grandezza: New text styling preference may not load until qTox restarts. La nuova preferenza dello stile del testo sarà caricata al riavvio qTox. Text Style format: Formato dello stile del testo: Select text styling preference. Selezionare lo stile del testo. Plaintext Testo in chiaro Show formatting characters Mostra caratteri di formattazione Don't show formatting characters Non mostrare i caratteri di formattazione New message Nuovo messaggio Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Apri la finestra di qTox quando si riceve un nuovo messaggio e nessuna finestra è ancora aperta. Open window Apri finestra Contact list Lista contatti If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Le chat di gruppo saranno posizionate all'inizio della lista contatti, altrimenti saranno posizionate sotto ai contatti online. Place groupchats at top of friend list Posiziona le chat di gruppo in cima alla lista contatti Your contact list will be shown in compact mode. toolTip for compact layout setting La lista contatti sarà visualizzata in modo compatto. Compact contact list Usa lista contatti compatta Multiple windows mode Modalità finestre multiple Open each chat in an individual window Apri ogni chat in una finestra singola Emoticons Faccine Use emoticons Usa emoticons Smiley Pack: Text on smiley pack label Emoticons: Emoticon size: Dimensione emoticon: px px Theme Impostazioni Tema Style: Stile: Theme color: Colore tema: Timestamp format: Formato data/ora: Date format: Formato data: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Se attivato, ad ogni contatto senza foto profilo verrà generata una foto profilo basata sull'ID Tox invece della foto predefinita. Richiede il riavvio di qTox. Use identicons instead of empty avatars Usa icone identificative al posto delle foto profilo vuote Use colored nicknames in chats Usare soprannomi colorati nelle chat Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Mostrare una notifica quando ricevi un nuovo messaggio e la finestra non è selezionata. Notify Notificare Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Solo notificare i nuovi messaggi nelle chat di gruppo quando veni menzionato. Group chats only notify when mentioned Solo notificare i chat di gruppo quando menzionato Play sound Riprodurre suono Play sound while Busy Riprodurre suono mentre sei occupato Notify via desktop notifications Notifica tramite notifiche sul desktop Hide message sender and contents Nascondere il mittente e il contenuto del messaggio Widget Online Button to set your status to 'Online' Online Away Button to set your status to 'Away' Assente Busy Button to set your status to 'Busy' Occupato toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy. qTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. Add new circle... Aggiungi un nuovo circolo... By Name Per Nome By Activity Per Attività All Tutti Online Online Offline Offline Friends Contatti Groups Gruppi Search Contacts Cerca tra i contatti Executable file popup title File eseguibile You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Hai chiesto a qTox di aprire un file eseguibile. I file eseguibili possono danneggiare il tuo computer. Sei sicuro di voler aprire questo file? Couldn't request friendship Impossibile inviare la richiesta d'amicizia Message failed to send Impossibile inviare il messaggio Status Stato toxcore failed to start, the application will terminate after you close this message. toxcore non è stato in grado di avviarsi, l'applicazione si chiuderà dopo aver chiuso questo messaggio. Your name qTox User Groupchat #%1 Gruppo #%1 Create new group... Crea un nuovo gruppo... %n New Friend Request(s) %n Nuova Richiesta di Amicizia %n Nuove Richieste di Amicizia %n New Group Invite(s) %n Nuovo Invito ad un Gruppo %n Nuovi Inviti ad un Gruppo Logout Tray action menu to logout user Esci Exit Tray action menu to exit tox Esci Filter... Filtro... File File Edit Modifica Contacts Contatti Change Status Cambia stato Edit Profile Modifica profilo Log out Esci Add Contact... Aggiungi contatto... Next Conversation Conversazione successiva Previous Conversation Conversazione precedente Show Tray action menu to show qTox window Mostra Add friend title of the window Aggiungi amico Group invites title of the window Inviti di gruppo File transfers title of the window Trasferimenti file Settings title of the window Impostazioni My profile title of the window Il mio profilo Failed to send file "%1" Impossibile inviare il file "%1" File sent File inviato sent you a friend request. ti ha inviato una richiesta di amicizia. invites you to join a group. ti invita a unirti a un gruppo. qTox/translations/ja.ts000066400000000000000000003405611415623743500155170ustar00rootroot00000000000000 AVForm Default resolution デフォルトの解像度 Audio/Video 音声/ビデオ Disabled 無効 Select region 範囲を選択 Screen %1 スクリーン %1 Audio Settings 音声の設定 Gain ゲイン Playback device 出力デバイス Use slider to set volume of your speakers. スライダーを使用して、スピーカーの音量を調節してください。 Capture device 入力デバイス Volume ボリューム Video Settings ビデオ設定 Video device ビデオデバイス Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. カメラの解像度を設定します。 大きい値だと、友人が見るビデオの品質がよくなります。 ただ、ビデオの品質をよくするには、高速なインターネット接続も必要です。 ご自身のインターネット接続が低速な場合、高品質のビデオ映像を送ることができずに ビデオ通話に影響が出る可能性があります。 Resolution 解像度 Rescan devices デバイスを再検索 Test Sound サウンドをテスト Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. エコー除去をサポートする、実験的な音声バックエンドを有効にします。変更を反映するには qTox の再起動が必要です。 Enable experimental audio backend 実験的な音声バックエンドを有効にする Audio quality 音質 Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. 転送される音声の音質です。帯域幅が十分に広くない場合や、インターネット使用料を抑えたい場合は、これを低く設定してください。 High (64 kbps) 高 (64 kbps) Medium (32 kbps) 中 (32 kbps) Low (16 kbps) 低 (16 kbps) Very low (8 kbps) 非常に低い (8 kbps) Threshold しきい値 AboutForm About qTox について Original author: %1 元の作成者: %1 You are using qTox version %1. qTox バージョン %1 を使用しています。 Commit hash: %1 コミットハッシュ値: %1 toxcore version: %1 toxcore バージョン: %1 Qt version: %1 Qt バージョン: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` 既知の問題の一覧が Github の %1 で見つかるかもしれません。qTox のバグやセキュリティの脆弱性を発見したら、ウィキの %2 の記事のガイドラインに沿って報告してください。 Click here to report a bug. バグを報告するにはここをクリックしてください。 See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Github で %1 の完全な一覧を参照 bug-tracker Replaces `%1` in the `A list of all known…` バグトラッカー Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` 分かりやすいバグ報告の書き方 contributors Replaces `%1` in `See a full list of…` 貢献者 AboutFriendForm Dialog ダイアログ username ユーザー名 status message ステータスメッセージ Used aliases: 使用したエイリアス: HISTORY OF ALIASES エイリアスの履歴 Automatically accept files from contact if set 設定した場合、連絡先からのファイルを自動的に承認します Auto accept files ファイルを自動承認 Default directory to save files: ファイルを保存するデフォルトの場所: Auto accept for this contact is disabled この連絡先からの自動承認は無効になっています Auto accept call: 自動承認通話: Manual 手動 Audio 音声 Audio + Video 音声とビデオ Automatically accept group chat invitations from this contact if set. 設定した場合、この連絡先からのグループチャットの招待を自動的に承認します。 Auto accept group invites グループ招待を自動承認 Remove history (operation can not be undone!) 履歴を削除 (元に戻せません!) Notes メモ Input field for notes about the contact この連絡先についてのメモの入力欄 You can save comment about this contact here. この連絡先についてのコメントを保存できます。 History removed 履歴を削除しました Choose an auto accept directory popup title 自動承認したファイルの保存場所を選択 <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation 確認 Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version バージョン License ライセンス Authors 作成者 Known Issues 既知の問題 Open update download link Update available qTox is up to date ✓ AddFriendForm Couldn't add friend 友達を登録できない Invalid Tox ID format Tox ID の形式が無効です Add Friends 友達を追加 Send friend request 友達リクエストを送信 Add a friend 友達を追加 Friend requests 友達の承認要求 Accept 承認 Reject 拒否 Tox ID, either 76 hexadecimal characters or name@example.com Tox ID (76 進数の文字列またはメールアドレス) Type in Tox ID of your friend 友達の Tox ID を入力してください Friend request message 友達リクエストメッセージ Type message to send with the friend request or leave empty to send a default message 友達リクエストの際に送信するメッセージを入力してください。デフォルトのメッセージを送信する場合は、空欄のままにしてください %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID は無効であるか、存在しません You can't add yourself as a friend! When trying to add your own Tox ID as friend 自分自身を友達として登録することはできません! Open contact list 連絡先リストを開く Couldn't open file ファイルを開けませんでした Couldn't open the contact file Error message when trying to open a contact list file to import 連絡先ファイルを開けませんでした Invalid file 無効なファイル We couldn't find any contacts to import in this file! このファイルからインポートできる連絡先が見つかりませんでした! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description 76 進数の文字列またはメールアドレス Message The message you send in friend requests メッセージ Open Button to choose a file with a list of contacts to import 開く Send friend requests 友達リクエストを送信 %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 だよ!Tox で会話しない? Import a list of contacts, one Tox ID per line 連絡先のリストをインポートします。1 行ごとに 1 つの Tox ID を記載してください Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) %n 件の連絡先をインポートする準備が整いました。確認するには送信を押してください Import contacts 連絡先をインポート AdvancedForm Advanced 高度な設定 Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not IMPORTANT NOTE 重要な注意事項 Reset settings 設定をリセット All settings will be reset to default. Are you sure? すべての設定がデフォルトに復元されます。よろしいですか? Yes はい No いいえ Call active popup title 通話中です You can't disconnect while a call is active! popup text 通話中は切断できません! Save File ファイルを保存 Logs (*.log) ログ (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox 通常の設定ディレクトリではなく、カレントディレクトリに設定を保存 Make Tox portable Tox をポータブルとして扱う Reset to default settings デフォルトの設定にリセット Portable ポータブル Connection Settings 接続設定 Enable IPv6 (recommended) Text on a checkbox to enable IPv6 IPv6 を有効にする (推奨) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip 無効にすると、Torを使うことができます。ただ、無効にするとToxネットワークに負荷がかかるので、必要なときのみ使ってください。 Enable UDP (recommended) Text on checkbox to disable UDP UDP を有効にする (推奨) Proxy type: プロキシの種類: Address: Text on proxy addr label アドレス: Port: Text on proxy port label ポート: None なし SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button 再接続 Debug デバッグ Export Debug Log デバッグログをエクスポート Copy Debug Log デバッグログをコピー Enable LAN discovery ChatForm Send a file ファイルを送信 Unable to open 開けません qTox wasn't able to open %1 qTox は %1 を開けませんでした Bad idea 不正な操作 %1 calling %1 呼び出し中 Calling %1 %1 を呼び出しています Failed to open temporary file Temporary file for screenshot 一時ファイルを開けませんでした qTox wasn't able to save the screenshot qTox はスクリーンショットを保存できませんでした Call with %1 ended. %2 %1 との通話が終了しました。%2 Call duration: 通話時間: %1 is typing %1 が入力しています Copy コピー You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" %1 は現在 %2 です Call with %1 ended unexpectedly. %2 %1 との通話は予期せず終了しました。%2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call 音声通話を開始できません Start audio call 音声通話を開始 End audio call 音声通話を終了 Cancel audio call 音声通話をキャンセル Accept audio call 音声通話を承認 Can't start video call ビデオ通話を開始できません Start video call ビデオ通話を開始 End video call ビデオ通話を終了 Cancel video call ビデオ通話をキャンセル Accept video call ビデオ通話を承認 Sound can be disabled only during a call 通話中のみ、音声を無効にできます Unmute call 通話をミュート解除 Mute call 通話をミュート Microphone can be muted only during a call 通話中のみ、マイクをミュートできます Unmute microphone マイクをミュート解除 Mute microphone マイクをミュート ChatLog pending 保留中 Copy コピー Select all すべて選択 ChatTextEdit Type your message here... ここにメッセージを入力してください… CircleWidget Rename circle Menu for renaming a circle サークル名を変更 Remove circle Menu for removing a circle サークルを削除 Open all in new window すべてを新しいウィンドウで開く Core /me offers friendship, "%1" /me 友達にならないか、"%1" Invalid Tox ID Error while sending friendship request 無効な Tox ID You need to write a message with your request Error while sending friendship request リクエストにメッセージを書く必要があります Your message is too long! Error while sending friendship request メッセージが長すぎます! Friend is already added Error while sending friendship request 友達はすでに追加されています Groupchat %1 DesktopNotify New message 新しいメッセージ Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form フォーム 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 完了までの時間: 10:10 Filename ファイル名 Waiting to send... file transfer widget 送信を待機しています… Accept to receive this file file transfer widget 承認してファイルを受け取る Location not writable Title of permissions popup 保存先に書き込めません You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup 書き込むのに必要な権限がありません!他の場所を選択するか、キャンセルしてください。 Paused file transfer widget 一時停止 Resuming... file transfer widget 再開しています… Open file ファイルを開く Open file directory ファイルの場所を開く Pause transfer 転送を一時停止 Cancel transfer 転送をキャンセル Resume transfer 転送を再開 Accept transfer 転送を承認 Save a file Title of the file saving dialog ファイルを保存 Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window 転送したファイル Downloads ダウンロード Uploads アップロード FriendListWidget Today 今日 Yesterday 昨日 Last 7 days 1 週間以内 This month 今月 Older than 6 Months 半年以上前 Never しない FriendRequestDialog Friend request Title of the window to aceept/deny a friend request 友達リクエスト Someone wants to make friends with you 誰かが友達になりたいようです User ID: ユーザー ID: Friend request message: 友達リクエストメッセージ: Accept Accept a friend request 承認 Reject Reject a friend request 拒否 FriendWidget Open chat in new window チャットを新しいウィンドウで開く Remove chat from this window このウィンドウからチャットを削除 Invite to group Menu to invite a friend to a groupchat グループに招待 To new group 新しいグループへ Invite to group '%1' グループ '%1' に招待 Move to circle... Menu to move a friend into a different circle サークルに移動… To new circle 新しいサークルへ Remove from circle '%1' サークル '%1' から削除 Move to circle "%1" サークル "%1" に移動 Set alias... エイリアスを設定… Auto accept files from this friend context menu entry この友達からのファイルを自動承認 Remove friend Menu to remove the friend from our friendlist 友達を削除 Show details 詳細を表示 Choose an auto accept directory popup title 自動承認したファイルの保存場所を選択 New message 新しいメッセージ Online オンライン Away 退席中 Busy 多忙 Offline オフライン GeneralForm Choose an auto accept directory popup title 自動承認したファイルの保存場所を選択 General 一般 GeneralSettings General Settings 一般設定 The translation may not load until qTox restarts. qTox が再起動するまで、翻訳は読み込まれない可能性があります。 Language: 言語: Start qTox on operating system startup (current profile). オペレーティングシステムの起動時に qTox を起動します (現在のプロファイルで)。 Autostart 自動起動 Enable light tray icon. toolTip for light icon setting 明るいトレイアイコンを有効にします。 Light icon 明るいアイコン Show system tray icon タスクトレイにアイコンを表示 qTox will start minimized in tray. toolTip for Start in tray setting qTox はトレイに最小化されて起動します。 Start in tray 起動時、最小化しておく After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting 最小化ボタンをクリックすると、タスクバーではなく、トレイに最小化します。 Minimize to tray トレイに最小化 After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting 閉じるボタンをクリックすると、終了せずにトレイに最小化します。 Close to tray 閉じた際、トレイに最小化 Your status is changed to Away after set period of inactivity. この時間退席すると、ステータスを退席中に変更します。 Auto away after (0 to disable): 退席中になるまでの時間 (無効にするには 0 を指定): Set to 0 to disable 0 を指定すると無効になります Set where files will be saved. ファイルを保存する場所を選択します。 Default directory to save files: ファイルを保存するデフォルトの場所: You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip 友達ごとに、右クリックすることで設定できます。 Autoaccept files ファイルを自動承認 Show contacts' status changes 連絡先のステータスの変化を表示 Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Save chat log チャットログを保存 Cleared 消去済み Send message メッセージを送信 Smileys スマイリー Send file(s) ファイルを送信 Send a screenshot スクリーンショットを送信 Clear displayed messages 表示されたメッセージを消去 Quote selected text 選択されたテキストを引用 Copy link address リンクのアドレスをコピー Confirmation 確認 You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... チャット履歴を読み込む… Export to file ファイルにエクスポート GenericNetCamView Tox video Tox ビデオ Show Messages メッセージを表示 Hide Messages メッセージを非表示 Full Screen Toggle video preview Mute audio Mute microphone マイクをミュート End video call ビデオ通話を終了 Exit full screen GroupChatForm %1 has set the title to %2 %1 さんがタイトルを %2 に設定しました %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups グループ Create new group 新しいグループを作成 Group invites グループ招待 GroupInviteWidget Invited by %1 on %2 at %3. %2 %3 に %1 さんに招待されました。 Join 参加 Decline 辞退 GroupWidget Open chat in new window 新しいウィンドウでチャットを開く Remove chat from this window このウィンドウからチャットを削除 Set title... タイトルを設定… Quit group Menu to quit a groupchat グループを終了 %n user(s) in chat Number of users in chat New Message Online オンライン IdentitySettings Public Information 公開情報 Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip この文字列には、他の Tox クライアントからあなたへ連絡する際の方法が記載されています。 友達と会話するには、これを共有してください。 Your Tox ID (click to copy) 自分の Tox ID (クリックするとコピーできます) This QR code contains your Tox ID. You may share this with your friends as well. この QR コードには、あなたの Tox ID が含まれています。これも友達と共有しても構いません。 Save image 画像を保存 Copy image 画像をコピー Server サーバー Hide my name from the public list ユーザー名を公開リストに表示しない Register 登録 Your password パスワード Update 更新 Profile プロファイル Rename profile. tooltip for renaming profile button プロファイルの名前を変更します。 Rename rename profile button 名前を変更 Delete profile. delete profile button tooltip プロファイルを削除します。 Delete delete profile button 削除 Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button 自分の Tox プロファイルをファイルにエクスポートできます。プロファイルに履歴は含まれません。 Export export profile button エクスポート Go back to the login screen tooltip for logout button ログイン画面に戻る Logout import profile button ログアウト Remove password パスワードを削除 Change password パスワードを変更 Register on ToxMe ToxMe で登録 Name for the ToxMe service. Tooltip for the `Username` ToxMe field. ToxMe サービスの名前です。 Optional. Something about you. Or your cat. Tooltip for the Biography text. 省略可能です。ご自身についての文章です。あるいは飼い猫について。 Optional. Something about you. Or your cat. Tooltip for the Biography field. 省略可能です。ご自身についての文章です。あるいは飼い猫について。 ToxMe service to register on. 登録用の ToxMe サービス。 If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. 設定していない場合、ToxMe エントリーを誰でも見ることができます。 Remove your password and encryption from your profile. Tooltip for the `Remove password` button. プロファイルのパスワードを削除し、暗号化を解除してください。 Name input 名前入力 Name visible to contacts 連絡先に表示される名前 Status message input ステータスメッセージ入力 Status message visible to contacts 連絡先に表示されるステータスメッセージ Your Tox ID 自分の Tox ID Save QR image as file ファイルとして QR コードを保存 Copy QR image to clipboard クリップボードに QR コードをコピー ToxMe username to be shown on ToxMe ToxMe で表示される ToxMe ユーザー名 Optional ToxMe biography to be shown on ToxMe ToxMe で表示される ToxMe の自己紹介 (省略可) ToxMe service address ToxMe サービスのアドレス Visibility on the ToxMe service ToxMe サービスの表示・非表示の状態 Password パスワード Update ToxMe entry ToxMe エントリーを更新 Rename profile. プロファイルの名前を変更します。 Delete profile. プロファイルを削除します。 Export profile プロファイルをエクスポート Remove password from profile プロファイルからパスワードを削除 Change profile password プロファイルのパスワードを変更 My name: 名前: My status: ステータス: My username ユーザー名 My biography 自己紹介 My profile プロファイル LoadHistoryDialog Load History Dialog 履歴の読み込みダイアログ Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: ユーザー名: Password: パスワード: Confirm: 確認: Password strength: %p% パスワード強度: %p% Create Profile プロファイルを作成 If the profile does not have a password, qTox can skip the login screen プロファイルにパスワードがなければ、qTox はログイン画面をスキップできます Load automatically 自動的に読み込む Import インポート Load 読み込む New Profile 新しいプロファイル Load Profile プロファイルを読み込む Couldn't create a new profile 新しいプロファイルを作成できませんでした The username must not be empty. ユーザー名を空にすることはできません。 The password must be at least 6 characters long. パスワードは最低 6 文字である必要があります。 The passwords you've entered are different. Please make sure to enter same password twice. 入力したパスワードが異なっています。 同じパスワードを二度確実に入力してください。 A profile with this name already exists. 同じ名前のプロファイルがすでに存在します。 Password protected profiles can't be automatically loaded. パスワードで保護されたプロファイルを自動的に読み込めません。 Couldn't load profile プロファイルを読み込めませんでした There is no selected profile. You may want to create one. プロファイルが選択されていません。 新しく作成しましょう。 Couldn't load this profile このプロファイルを読み込めませんでした This profile is already in use. このプロファイルは使用中です。 Wrong password. パスワードが違います。 Username input field ユーザー名入力欄 Password input field, you can leave it empty (no password), or type at least 6 characters パスワード入力欄です。空 (パスワードなし) にするか、最低 6 文字を入力してください Password confirmation field パスワード確認欄 Create a new profile button 新しいプロファイルを作成ボタン Profile list プロファイルリスト List of profiles プロファイルのリスト Password input パスワード入力 Load automatically checkbox 自動的に読み込むチェックボックス Import profile プロファイルをインポート Load selected profile button 選択されたプロファイルを読み込むボタン New profile creation page 新しいプロファイルの作成ページ Loading existing profile page 既存のプロファイルを読み込み中ページ MainWindow Your name 名前 Your status ステータス ... Add friends 友達を追加 Create a group chat グループチャットを作成 View completed file transfers 完了したファイル転送を表示 Change your settings 設定を変更 Close 閉じる Open profile プロファイルを開く Open profile page when clicked クリックされたらプロファイルページを開く Status message input ステータスメッセージ入力 Set your status message that will be shown to others 他の人に表示されるステータスメッセージを設定してください Status ステータス Set availability status 利用可能なステータスを設定 Contact search 連絡先の検索 Contact search input for known friends 既存の友達に対する連絡先検索入力 Sorting and visibility 並べ替えと表示 Set friends sorting and visibility 友達の並べ替えと表示を設定 Open Add friends page 友達を追加ページを開く Groupchat グループチャット Open groupchat management page グループチャットメッセージページを開く File transfers history ファイル転送履歴 Open File transfers history ファイル転送履歴を開く Settings 設定 Open Settings 設定を開く Nexus View OS X Menu bar 表示 Window OS X Menu bar ウィンドウ Minimize OS X Menu bar 最小化 Bring All to Front OS X Menu bar すべて最前面に表示 Exit Fullscreen 全画面表示を終了 Enter Fullscreen 全画面表示を開始 NotificationEdgeWidget Unread message(s) 未読メッセージ PasswordEdit CAPS-LOCK ENABLED Caps Lock が有効になっています PrivacyForm Confirmation 確認 Do you want to permanently delete all chat history? すべてのチャット履歴を完全に消去してもよろしいですか? Privacy プライバシー PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting 自分が入力している場合に、そのことを友達が分かるようになります。 Send typing notifications 入力通知を送信 Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting チャット履歴の保持は、まだ開発中です。 保存形式が変わる可能性があるため、データが失われるかもしれません。 Keep chat history チャット履歴を保持 NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam は、自分の Tox ID の一部です。 もし、あなたが不特定多数から友達リクエストを受け取るというスパム攻撃を受けたら、NoSpam を変更すべきです。 他の人が古い Tox ID であなたを友達として追加できなくなりますが、現在の友達には影響しません。 NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam は、自由に変更可能な、自分の Tox ID の一部です。 もし、あなたが不特定多数から友達リクエストを受け取るというスパム攻撃を受けたら、NoSpam を変更してください。 Generate random NoSpam ランダムな NoSpam を生成 Privacy プライバシー BlackList ブラックリスト Filter group message by group member's public key. Put public key here, one per line. グループメンバーの公開鍵でグループメッセージをフィルターします。公開鍵を一行ごとに 1 つづつ、ここに入力してください。 Profile Failed to derive key from password, the profile won't use the new password. パスワードからキーを作成できませんでした。プロファイルは新しいパスワードを使用しません。 Couldn't change password on the database, it might be corrupted or use the old password. データベースでパスワードを変更できませんでした。パスワードの形式が間違っているか、古いものを使用しています。 Toxing on qTox qTox で tox しています ProfileForm Current profile: 現在のプロファイル: Remove 削除 Choose a profile picture プロファイルの写真を選択 Error エラー Unable to open this file. このファイルを開けません。 Unable to read this image. この画像を読み込めません。 The supplied image is too large. Please use another image. 指定された画像は大きすぎます。 他の画像を使用してください。 Rename "%1" renaming a profile "%1" の名前を変更 Couldn't rename the profile to "%1" プロファイル名を "%1" に変更できませんでした Location not writable Title of permissions popup 場所が書き込み可能ではありません You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup この場所に書き込む権限がありません。他の場所を選ぶか、保存ダイアログをキャンセルしてください。 Failed to copy file ファイルのコピーに失敗 The file you chose could not be written to. 選択されたファイルに書き込めませんでした。 Really delete profile? deletion confirmation title 本当にプロファイルを削除しますか? Are you sure you want to delete this profile? deletion confirmation text このプロファイルを削除してもよろしいですか? Save save qr image 保存 Save QrCode (*.png) save dialog filter QR コードを保存 (*.png) Nothing to remove 削除するものがありません Your profile does not have a password! プロファイルにパスワードがありません! Really delete password? deletion confirmation title 本当にパスワードを削除しますか? Please enter a new password. 新しいパスワードを入力してください。 Register (processing) 登録 (処理しています) Update (processing) 更新 (処理しています) Done! 完了しました! Account %1@%2 updated successfully アカウント %1@%2 の更新に成功しました Successfully added %1@%2 to the database. Save your password %1@%2 をデータベースに正しく追加しました。パスワードを保存を保存してください Toxme error Toxme エラー Register 登録 Update 更新 Files could not be deleted! deletion failed title ファイルを削除できませんでした! Change password button text パスワードを変更 Set profile password button text プロファイルにパスワードを設定 Current profile location: %1 現在のプロファイル保存先: %1 Couldn't change password パスワードを変更できませんでした This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable 空のパスは利用できません Failed to rename 名前を変更できませんでした Profile already exists プロファイルがすでに存在しています A profile named "%1" already exists. "%1" という名前のプロファイルはすでに存在しています。 Empty name 空の名前 Empty name is unavaliable 空の名前は利用できません Empty path 空のパス Couldn't change password on the database, it might be corrupted or use the old password. データベースでパスワードを変更できませんでした。パスワードの形式が間違っているか、古いものを使用しています。 Export profile プロファイルをエクスポート Tox save file (*.tox) save dialog filter Tox 保存ファイル (*.tox) The following files could not be deleted: deletion failed text part 1 次のファイルを削除できませんでした: Please manually remove them. deletion failed text part 2 手動で削除してください。 Are you sure you want to delete your password? deletion confirmation text パスワードを削除してもよろしいですか? Images (%1) filetype filter 画像 (%1) ProfileImporter Import profile import dialog title プロファイルをインポート Tox save file (*.tox) import dialog filter Tox 保存ファイル (*.tox) Ignoring non-Tox file popup title Tox のファイルではないものを無視します Warning: You have chosen a file that is not a Tox save file; ignoring. popup text 警告: Tox の保存ファイルではないものが選択されました。無視します。 Profile already exists import confirm title プロファイルはすでに存在します A profile named "%1" already exists. Do you want to erase it? import confirm text "%1" という名前のプロファイルはすでに存在します。消去しますか? File doesn't exist ファイルが存在しません Profile doesn't exist プロファイルが存在しません Profile imported プロファイルがインポートされました %1.tox was successfully imported %1.tox を正しくインポートしました QApplication Ok OK Cancel キャンセル Yes はい No いいえ LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend 友達を追加できませんでした %1 is not a valid Toxme address. %1 有効な ToxMe アドレスではありません。 You can't add yourself as a friend! When trying to add your own Tox ID as friend 自分を友達として追加することはできません! QObject Tox URI to parse 解析用の Tox URI Starts new instance and loads specified profile. 新しいインスタンスを起動して、指定されたプロファイルを読み込みます。 profile プロファイル Server doesn't support Toxme サーバーが Toxme に対応していません You're making too many requests. Wait an hour and try again リクエストを送信しすぎています。1 時間待ってから、もう一度お試しください This name is already in use この名前はすでに使用されています This Tox ID is already registered under another name この Tox ID はすでに別の名前で登録されています Please don't use a space in your name 名前にスペースを入れないでください Password incorrect パスワードが違います You can't use this name この名前は使えません Name not found 名前が見つかりません Tox ID not sent Tox ID が送信されていません That user does not exist ユーザーが存在しません %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 だよ!Tox で会話しない? Error エラー qTox couldn't open your chat logs, they will be disabled. qTox はチャットログを開けませんでした。ログは無効になります。 None No camera device set なし Desktop Desktop as a camera input for screen sharing デスクトップ Default デフォルト Blue Olive オリーブ Red Violet すみれ Incoming call... お電話です… Problem with HTTPS connection HTTPS 接続に関する問題 Internal ToxMe error ToxMe 内部エラー Reformatting text in progress.. テキストの再書式設定が進行中です。 Starts new instance and opens the login screen. 新しいインスタンスを開始して、ログイン画面を開いてください。 Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status オンライン away contact status 退席中 busy contact status 多忙 offline contact status オフライン blocked contact status RemoveFriendDialog Remove friend 友達を削除 Also remove chat history チャット履歴も消去 Remove 削除 Are you sure you want to remove %1 from your contacts list? %1 を連絡先リストから削除してもよろしいですか? Remove all chat history with the friend if set 設定した場合、友達とのチャット履歴をすべて削除します ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet クリックとドラッグで範囲を選択します。%1 で qTox ウィンドウの表示・非表示を切り替えます。%2 でキャンセルします。 Space [Space] key on the keyboard スペース Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected %1 キーを押して、範囲のスクリーンショットを送信します。%2 で qTox ウィンドウの表示・非表示を切り替えます。%3 でキャンセルします。 Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form フォーム Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password パスワードを設定 Confirm: 確認: Password: パスワード: Password strength: %p% パスワードの強度: %p% The password is too short パスワードが短すぎます The password doesn't match. パスワードが一致しません。 Confirm password パスワードを確認 Confirm password input パスワードの確認入力 Password input パスワード入力 Password input field, minimum 6 characters long パスワードの入力欄、最低でも 6 文字 Settings Circle #%1 サークル #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI 友達を追加 Do you want to add %1 as a friend? %1 を友達に追加しますか? User ID: ユーザー ID: Friend request message: 友達リクエストメッセージ: Send Send a friend request 送信 Cancel Don't send a friend request キャンセル UserInterfaceForm None なし User Interface ユーザーインターフェース UserInterfaceSettings Chat チャット Base font: 基本のフォント: px px Size: サイズ: New text styling preference may not load until qTox restarts. qTox が再起動するまで、新しいテキストスタイル設定は読み込まれない可能性があります。 Text Style format: テキストスタイルの書式: Select text styling preference. テキストスタイル設定を選択します。 Plaintext プレーンテキスト Show formatting characters 書式付きの文字を表示 Don't show formatting characters 書式付きの文字を表示しない New message 新しいメッセージ Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting ウィンドウが開かれていない時に新しいメッセージを受信した場合に、qTox のウィンドウを開きます。 Open window ウィンドウを開く Contact list 連絡先リスト If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning チェックすると、グループチャットは友達リストの一番上に移動します。チェックを外すと、下のオンライン友達の位置に移動します。 Place groupchats at top of friend list 友達リストの一番上にグループチャットを表示 Your contact list will be shown in compact mode. toolTip for compact layout setting 連絡先リストがコンパクトに表示されます。 Compact contact list コンパクトな連絡先リスト Multiple windows mode 複数ウィンドウモード Open each chat in an individual window それぞれのチャットを別のウィンドウで開きます Emoticons 顔文字 Use emoticons 顔文字を使う Smiley Pack: Text on smiley pack label スマイリーパック: Emoticon size: 顔文字のサイズ: px px Theme テーマ Style: スタイル: Theme color: テーマの色: Timestamp format: タイムスタンプの書式: Date format: 日付の書式: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons 有効な場合、アバターが設定されていない連絡先には、デフォルトの画像の代わりに、Tox ID から生成されたアバターが設定されます。適用するには再起動が必要です。 Use identicons instead of empty avatars 空のアバターの代わりに自動的に生成されたアバターを使う Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound 音を再生 Play sound while Busy 多忙の際に音声を再生 Notify via desktop notifications Hide message sender and contents Widget Status ステータス toxcore failed to start, the application will terminate after you close this message. toxcore の開始に失敗しました。このウィンドウを閉じるとプログラムは強制終了します。 toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore は現在のプロキシ設定では開始できません。qTox を実行できません。設定を変更して再起動してください。 Executable file popup title 実行可能なファイル You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text qTox で実行可能なファイルを開こうとしています。実行可能なファイルはお使いのコンピューターに危害を加える可能性があります。このファイルを開いてもよろしいですか? Your name 名前 Couldn't request friendship 友達をリクエストできません Groupchat #%1 グループチャット #%1 Message failed to send メッセージの送信に失敗 Create new group... 新しいグループを作成… Add new circle... 新しいサークルを追加… %n New Friend Request(s) %n 件の新しい友達リクエスト %n New Group Invite(s) %n 件の新しいグループチャット招待 By Name 名前 By Activity アクティビティー All すべて Online オンライン Offline オフライン Friends 友達 Groups グループ Search Contacts 連絡先を検索 Online Button to set your status to 'Online' オンライン Away Button to set your status to 'Away' 退席中 Busy Button to set your status to 'Busy' 多忙 Logout Tray action menu to logout user ログアウト Exit Tray action menu to exit tox 終了 Filter... フィルター… File ファイル Edit 編集 Contacts 連絡先 Change Status ステータスを変更 Edit Profile プロフィールを編集 Log out ログアウト Add Contact... 連絡先を追加… Next Conversation 次の会話 Previous Conversation 前の会話 Show Tray action menu to show qTox window 表示 Add friend title of the window 友達を追加 Group invites title of the window グループ招待 File transfers title of the window ファイル転送 Settings title of the window 設定 My profile title of the window プロフィール Failed to send file "%1" ファイル "%1" の送信に失敗しました File sent sent you a friend request. invites you to join a group. qTox/translations/jbo.ts000066400000000000000000003117461415623743500157020ustar00rootroot00000000000000 AVForm Audio/Video sance ja jvinu Default resolution Disabled na tolpo'u Select region ko cuxna fi lo'i tutra Screen %1 Audio Settings lo sance jitro Gain Playback device lo selsnapra Use slider to set volume of your speakers. Capture device Volume kamcladu Video Settings jvinu jitro Video device Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Resolution Rescan devices Test Sound Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Medium (32 kbps) Low (16 kbps) Very low (8 kbps) Threshold AboutForm About Original author: %1 You are using qTox version %1. Commit hash: %1 toxcore version: %1 Qt version: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` bug-tracker Replaces `%1` in the `A list of all known…` Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` contributors Replaces `%1` in `See a full list of…` AboutFriendForm Dialog username status message Used aliases: HISTORY OF ALIASES Automatically accept files from contact if set Auto accept files Default directory to save files: Auto accept for this contact is disabled Auto accept call: Manual Audio Audio + Video Automatically accept group chat invitations from this contact if set. Auto accept group invites Remove history (operation can not be undone!) Notes Input field for notes about the contact You can save comment about this contact here. History removed Choose an auto accept directory popup title <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version License Authors Known Issues Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Invalid Tox ID format Send friend request Add a friend Friend requests Accept Reject Couldn't add friend Tox ID, either 76 hexadecimal characters or name@example.com Type in Tox ID of your friend Friend request message Type message to send with the friend request or leave empty to send a default message %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to either 76 hexadecimal characters or name@example.com Tox ID format description Message The message you send in friend requests Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not IMPORTANT NOTE Reset settings All settings will be reset to default. Are you sure? Yes No Call active popup title You can't disconnect while a call is active! popup text Save File Logs (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Make Tox portable Reset to default settings Portable Connection Settings Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP Proxy type: Address: Text on proxy addr label Port: Text on proxy port label None SOCKS5 HTTP Reconnect reconnect button Debug Export Debug Log Copy Debug Log Enable LAN discovery ChatForm Send a file qTox wasn't able to open %1 Unable to open Bad idea %1 calling Calling %1 Failed to open temporary file Temporary file for screenshot qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch Call with %1 ended. %2 Call duration: %1 is typing Copy You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Start audio call End audio call Cancel audio call Accept audio call Can't start video call Start video call End video call Cancel video call Accept video call Sound can be disabled only during a call Unmute call Mute call Microphone can be muted only during a call Unmute microphone Mute microphone ChatLog Copy Select all pending ChatTextEdit Type your message here... CircleWidget Rename circle Menu for renaming a circle Remove circle Menu for removing a circle Open all in new window Core /me offers friendship, "%1" Invalid Tox ID Error while sending friendship request You need to write a message with your request Error while sending friendship request Your message is too long! Error while sending friendship request Friend is already added Error while sending friendship request Groupchat %1 DesktopNotify New message Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen 10Mb Ausgelassen 0kb/s Ausgelassen ETA:10:10 Ausgelassen Filename Ausgelassen Waiting to send... file transfer widget Accept to receive this file file transfer widget Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Resuming... file transfer widget Cancel transfer Pause transfer Paused file transfer widget Open file Open file directory Resume transfer Accept transfer Save a file Title of the file saving dialog Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Downloads Uploads FriendListWidget Today Yesterday Last 7 days This month Older than 6 Months Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Someone wants to make friends with you User ID: Friend request message: Accept Accept a friend request Reject Reject a friend request FriendWidget Invite to group Menu to invite a friend to a groupchat Move to circle... Menu to move a friend into a different circle To new circle Remove from circle '%1' Move to circle "%1" Open chat in new window Remove chat from this window To new group Invite to group '%1' Set alias... Auto accept files from this friend context menu entry Remove friend Menu to remove the friend from our friendlist Show details Choose an auto accept directory popup title New message Online .tox. pilno ca Away Busy Offline Ausgelassen .tox. pilno na ca GeneralForm General Choose an auto accept directory popup title GeneralSettings General Settings The translation may not load until qTox restarts. Language: Show system tray icon Enable light tray icon. toolTip for light icon setting Light icon qTox will start minimized in tray. toolTip for Start in tray setting Start in tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Close to tray After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Minimize to tray Autostart Set where files will be saved. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Autoaccept files Set to 0 to disable Your status is changed to Away after set period of inactivity. Auto away after (0 to disable): Show contacts' status changes Start qTox on operating system startup (current profile). Default directory to save files: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Smileys Send file(s) Send a screenshot Save chat log Clear displayed messages Cleared Quote selected text Copy link address Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Export to file GenericNetCamView Tox video Show Messages Hide Messages Full Screen Toggle video preview Mute audio Mute microphone End video call Exit full screen GroupChatForm %1 has set the title to %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups girzu Create new group Group invites GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Set title... Open chat in new window Remove chat from this window Quit group Menu to quit a groupchat %n user(s) in chat Number of users in chat New Message Online .tox. pilno ca IdentitySettings Public Information Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Your Tox ID (click to copy) Profile Rename profile. tooltip for renaming profile button Go back to the login screen tooltip for logout button Logout import profile button Remove password Change password This QR code contains your Tox ID. You may share this with your friends as well. Save image Copy image Rename rename profile button Delete profile. delete profile button tooltip Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Export export profile button Delete delete profile button Server Hide my name from the public list Register Your password Update Register on ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. Delete profile. Export profile Remove password from profile Change profile password My name: My status: My username My biography My profile LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Password: Confirm: Password strength: %p% Create Profile If the profile does not have a password, qTox can skip the login screen Load automatically Load Load Profile New Profile Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Password protected profiles can't be automatically loaded. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Import Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Your status ... Ausgelassen Add friends Create a group chat View completed file transfers Change your settings Close Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings Open Settings Nexus View OS X Menu bar Window OS X Menu bar Minimize OS X Menu bar Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox ProfileForm Choose a profile picture Error Rename "%1" renaming a profile Unable to open this file. Current profile: Remove Unable to read this image. The supplied image is too large. Please use another image. Couldn't rename the profile to "%1" Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Nothing to remove Your profile does not have a password! Really delete password? deletion confirmation title Please enter a new password. Are you sure you want to delete this profile? deletion confirmation text Save save qr image Save QrCode (*.png) save dialog filter Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Change password button text Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Cancel Yes No LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Red Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! None No camera device set Desktop Desktop as a camera input for screen sharing Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status away contact status busy contact status offline contact status blocked contact status RemoveFriendDialog Remove friend Also remove chat history Remove Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Password: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Do you want to add %1 as a friend? User ID: Friend request message: Send Send a friend request Cancel Don't send a friend request UserInterfaceForm None User Interface UserInterfaceSettings Chat Base font: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme Style: Theme color: Timestamp format: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' .tox. pilno ca Away Button to set your status to 'Away' Busy Button to set your status to 'Busy' toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File Edit Profile Change Status Log out Edit Logout Tray action menu to logout user Exit Tray action menu to exit tox Filter... Contacts Add Contact... Next Conversation Previous Conversation Executable file popup title You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Couldn't request friendship Status Your name Message failed to send Create new group... Add new circle... %n New Friend Request(s) %n New Group Invite(s) By Name By Activity All ro Online .tox. pilno ca Offline Ausgelassen .tox. pilno na ca Friends pendo Groups girzu Search Contacts Groupchat #%1 Show Tray action menu to show qTox window Add friend title of the window Group invites title of the window File transfers title of the window Settings title of the window My profile title of the window Failed to send file "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/ko.ts000066400000000000000000003142421415623743500155330ustar00rootroot00000000000000 AVForm Audio/Video 오디오/비디오 Default resolution 표준 해상도 Disabled 사용안함 Select region 지역선택 Screen %1 화면 %1 Audio Settings 오디오설정 Gain Playback device 재생 기기 Use slider to set volume of your speakers. 슬라이더를 사용해 스피커의 볼륨을 조절할 수 있습니다. Capture device 캡쳐장치 Volume 볼륨 Video Settings 비디오설정 Video device 비디오장치 Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. 카메라 해상도를 설정하십시오. 해상도가 높으면 고품질 비디오를 전송합니다. 고품질 비디오는 인터넷 연결상태가 좋은곳에서 사용하실것을 추천합니다. Resolution 해상도 Rescan devices 장치 검색 Test Sound 사운드검사 Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality 오디오 음질 Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) 높음(64kbps) Medium (32 kbps) 중간(32kbps) Low (16 kbps) 낮음 (16kbps) Very low (8 kbps) 아주 낮음 (8kbps) Threshold 한계점 AboutForm About About Original author: %1 You are using qTox version %1. qTox 버젼 %1 사용중. Commit hash: %1 Commit hash: %1 toxcore version: %1 toxcore version: %1 Qt version: %1 Qt version: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` %1 에서 qTox의 알려진 문제들을 확인할 수 있습니다. 버그나 보안 취약점을 발견하면 %2 위키 가이드라인에 따라 %3. Click here to report a bug. 버그 제출하기. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` %1의 전체 리스트 보기 bug-tracker Replaces `%1` in the `A list of all known…` 버그추적 Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` 버그 리포트 작성 contributors Replaces `%1` in `See a full list of…` contributors AboutFriendForm Dialog 대화 username 사용자이름 status message 상태 메시지 Used aliases: 사용한 닉네임: HISTORY OF ALIASES 닉네임 기록 Automatically accept files from contact if set Auto accept files 파일전송 자동 수락 Default directory to save files: 파일 저장 폴더: Auto accept for this contact is disabled Auto accept call: 호출 자동 수락: Manual 설명서 Audio 오디오 Audio + Video 오디오 + 비디오 Automatically accept group chat invitations from this contact if set. Auto accept group invites 그룹 초대를 받아드립니다. Remove history (operation can not be undone!) 기록 삭제하기 (삭제된 기록은 복구할 수 없습니다!) Notes 메모 Input field for notes about the contact You can save comment about this contact here. 이 주소에 대한 설명을 보관할 수 있습니다. History removed 기록 삭제 Choose an auto accept directory popup title 자동받기 폴더 선택 <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version 버전 License 라이센스 Authors Known Issues 알려진 문제 Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends 친구 추가 Invalid Tox ID format Tox ID 형식이 정확하지 않습니다 Send friend request 친구 요청하기 Add a friend 친구 추가 Friend requests 친구 요청 Accept 수락 Reject 거부 Couldn't add friend 친구를 추가할 수 없습니다 Tox ID, either 76 hexadecimal characters or name@example.com Type in Tox ID of your friend 친구의 Tox 아이디를 입력 Friend request message 친구 요청 메시지 Type message to send with the friend request or leave empty to send a default message 친구요청 메시지를 입력하세요 %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend 자신을 친구로 추가할 수 없습니다! Open contact list Couldn't open file 파일을 열 수 없습니다 Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description Message The message you send in friend requests 메시지 Open Button to choose a file with a list of contacts to import 열기 Send friend requests 친구 요청을 보냄 %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not IMPORTANT NOTE Reset settings 설정초기화 All settings will be reset to default. Are you sure? 모든 설정을 초기화 하시겠습니까? Yes No 아니오 Call active popup title You can't disconnect while a call is active! popup text 통화중에는 연결을 종료할 수 없습니다! Save File 파일저장 Logs (*.log) Logs (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Make Tox portable Reset to default settings 설정초기화 Portable 이동가능 Connection Settings 연결설정 Enable IPv6 (recommended) Text on a checkbox to enable IPv6 IPv6사용 (권장) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP UDP사용 (권장) Proxy type: 프록시 형태: Address: Text on proxy addr label 주소: Port: Text on proxy port label 포트: None 없음 SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button 다시 연결 Debug 디버그 Export Debug Log 디버그 로그 내보내기 Copy Debug Log 디버그 로그 복사하기 Enable LAN discovery ChatForm Send a file 파일 보내기 qTox wasn't able to open %1 %1을 열 수 없습니다 Unable to open 열 수 없습니다 Bad idea 좋지않은 의견 %1 calling Calling %1 Failed to open temporary file Temporary file for screenshot 필요한 임시 파일을 만들 수 없습니다 qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch 스크린샷을 저장할 수 없었습니다. Call with %1 ended. %2 Call duration: %1 is typing Copy 복사 You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" %1님은 현재 %2 Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Start audio call 음성통화 시작 End audio call 음성 통화 종료 Cancel audio call 음성 통화 취소 Accept audio call 음성 통화 수락 Can't start video call Start video call 영상통화 시작 End video call 비디오 통화 종료 Cancel video call 비디오 통화 취소 Accept video call 비디오 통화 수락 Sound can be disabled only during a call Unmute call Mute call 음소거 전화 Microphone can be muted only during a call Unmute microphone Mute microphone ChatLog Copy 복사 Select all 전체 선택 pending ChatTextEdit Type your message here... 여기에 메시지를 입력하세요... CircleWidget Rename circle Menu for renaming a circle Remove circle Menu for removing a circle Open all in new window Core /me offers friendship, "%1" Invalid Tox ID Error while sending friendship request You need to write a message with your request Error while sending friendship request Your message is too long! Error while sending friendship request 제한된 메시지 길이를 초과했습니다! Friend is already added Error while sending friendship request 이미 추가된 친구입니다 Groupchat %1 DesktopNotify New message Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen ETA:10:10 Filename Ausgelassen 파일이름 Waiting to send... file transfer widget 전송 대기... Accept to receive this file file transfer widget 수신수락 Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Resuming... file transfer widget Cancel transfer 전송취소 Pause transfer Paused file transfer widget Open file 파일열기 Open file directory Resume transfer Accept transfer 전송수락 Save a file Title of the file saving dialog 파일보관 Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window 전송 된 파일 Downloads 다운로드 Uploads 업로드 FriendListWidget Today 오늘 Yesterday 어제 Last 7 days 지난 7일 This month 이 달 Older than 6 Months 6개월 이전 Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request 친구요청 Someone wants to make friends with you User ID: 사용자 아이디: Friend request message: 친구 요청 메시지: Accept Accept a friend request 수락 Reject Reject a friend request 거부 FriendWidget Invite to group Menu to invite a friend to a groupchat 그룹 초대 Move to circle... Menu to move a friend into a different circle To new circle Remove from circle '%1' Move to circle "%1" Open chat in new window Remove chat from this window To new group Invite to group '%1' Set alias... Auto accept files from this friend context menu entry Remove friend Menu to remove the friend from our friendlist Show details Choose an auto accept directory popup title 자동받기 폴더 선택 New message Online 온라인 Away Busy Offline Ausgelassen 오프라인 GeneralForm General 일반 Choose an auto accept directory popup title 자동받기 폴더 선택 GeneralSettings General Settings 일반설정 The translation may not load until qTox restarts. Language: 현시언어: Show system tray icon Enable light tray icon. toolTip for light icon setting Light icon qTox will start minimized in tray. toolTip for Start in tray setting Start in tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Close to tray After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Minimize to tray Autostart 자동시작 Set where files will be saved. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Autoaccept files Set to 0 to disable Your status is changed to Away after set period of inactivity. Auto away after (0 to disable): Show contacts' status changes Start qTox on operating system startup (current profile). Default directory to save files: 파일 저장 폴더: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Smileys Send file(s) Send a screenshot Save chat log 채팅 기록을 저장 Clear displayed messages Cleared Quote selected text Copy link address Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... 채팅 기록을 불러오는중... Export to file GenericNetCamView Tox video Show Messages Hide Messages Full Screen Toggle video preview Mute audio Mute microphone End video call 비디오 통화 종료 Exit full screen GroupChatForm %1 has set the title to %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups 그룹 Create new group Group invites 그룹 초대 GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Set title... Open chat in new window Remove chat from this window Quit group Menu to quit a groupchat %n user(s) in chat Number of users in chat New Message Online 온라인 IdentitySettings Public Information 공개 정보 Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Your Tox ID (click to copy) Tox ID (클릭해서 복사) Profile 프로필 Rename profile. tooltip for renaming profile button 프로필 이름 변경. Go back to the login screen tooltip for logout button Logout import profile button 로그아웃 Remove password 계정 비밀번호 삭제 Change password 계정 비밀번호 변경 This QR code contains your Tox ID. You may share this with your friends as well. Save image 이미지 저장 Copy image 이미지 복사 Rename rename profile button 이름변경 Delete profile. delete profile button tooltip 프로필 삭제. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Export export profile button 내보내기 Delete delete profile button 삭제 Server 서버 Hide my name from the public list Register Your password Update Register on ToxMe ToxMe 등록 Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. 프로필 이름 변경. Delete profile. 프로필 삭제. Export profile Remove password from profile Change profile password My name: My status: My username My biography My profile 내 프로필 LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Password: Confirm: Password strength: %p% Create Profile If the profile does not have a password, qTox can skip the login screen Load automatically Load Load Profile New Profile Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Password protected profiles can't be automatically loaded. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Import Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name 이름 Your status 상태 ... Ausgelassen Add friends Create a group chat View completed file transfers Change your settings Close Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status 상태 Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings 설정 Open Settings Nexus View OS X Menu bar Window OS X Menu bar Minimize OS X Menu bar Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Keep chat history 채팅기록 유지 NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox ProfileForm Choose a profile picture Error Rename "%1" renaming a profile Unable to open this file. Current profile: Remove Unable to read this image. The supplied image is too large. Please use another image. Couldn't rename the profile to "%1" Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Nothing to remove Your profile does not have a password! Really delete password? deletion confirmation title Please enter a new password. Are you sure you want to delete this profile? deletion confirmation text Save save qr image Save QrCode (*.png) save dialog filter Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Change password button text 계정 비밀번호 변경 Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Cancel Yes No 아니오 LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend 친구를 추가할 수 없습니다 %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend 자기 자신을 친구로 추가할 수 없습니다! QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Red Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! None No camera device set 없음 Desktop Desktop as a camera input for screen sharing Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status 온라인 away contact status 자리 비움 busy contact status offline contact status 오프라인 blocked contact status RemoveFriendDialog Remove friend Also remove chat history Remove Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Password: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI 친구 추가 Do you want to add %1 as a friend? User ID: 사용자 아이디: Friend request message: 친구 요청 메시지: Send Send a friend request Cancel Don't send a friend request UserInterfaceForm None 없음 User Interface 인터페이스 UserInterfaceSettings Chat Base font: 기본폰트: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme 테마 Style: Theme color: Timestamp format: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound 소리재생 Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' 온라인 Away Button to set your status to 'Away' Busy Button to set your status to 'Busy' toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File 파일 Edit Profile 프로필 편집 Change Status 상태 바꾸기 Log out 로그아웃 Edit 편집 Logout Tray action menu to logout user 로그아웃 Exit Tray action menu to exit tox Filter... Contacts Add Contact... Next Conversation Previous Conversation 이전의 대화 Executable file popup title 실행파일 You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text 실행파일을 열기하는것은 위험할 수 있습니다. 파일을 열기하시겠습니까? Couldn't request friendship 친구요청을 할 수 없습니다 Status 상태 Your name 이름 Message failed to send 메시지 전송에 실패하였습니다 Create new group... 새 그룹 만들기... Add new circle... %n New Friend Request(s) %n New Group Invite(s) By Name By Activity All 전체 Online 온라인 Offline Ausgelassen 오프라인 Friends 친구 Groups 그룹 Search Contacts Groupchat #%1 그룹채팅 #%1 Show Tray action menu to show qTox window 보기 Add friend title of the window 친구 추가 Group invites title of the window 그룹 초대 File transfers title of the window 파일 이동 Settings title of the window 설정 My profile title of the window 내 프로필 Failed to send file "%1" "%1" 파일을 전송하는데 실패하였습니다 File sent sent you a friend request. invites you to join a group. qTox/translations/lt.ts000066400000000000000000003330551415623743500155440ustar00rootroot00000000000000 AVForm Audio/Video Garsas ir vaizdas Default resolution Standartinė raiška Disabled Išjungta Select region Pasirinkti sritį Screen %1 Ekranas %1 Audio Settings Garso nustatymai Gain Stiprinimas Playback device Atkūrimo įrenginys Use slider to set volume of your speakers. Naudokite slinktuką, kad nustatytumėte savo garsiakalbių garsį. Capture device Įrašymo įrenginys Volume Garsis Video Settings Vaizdo nustatymai Video device Vaizdo įrenginys Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Nustatykite vaizdo kameros raišką. Kuo didesnė reikšmė, tuo geresnę vaizdo kokybę matys jūsų kontaktai. Geresnei vaizdo kokybei atitinkamai reikia geresnio interneto ryšio. Jeigu jūsų interneto ryšys yra per prastas, turėsite keblumų su aukštesnės kokybės vaizdo skambučiais. Resolution Raiška Rescan devices Aptikti įrenginius iš naujo Test Sound Išbandyti garsą Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Įjungia eksperimentinę garso vidinę pusę su aido malšinimo palaikymu. Norint, kad pakeitimai įsigaliotų, reikia iš naujo paleisti qTox. Enable experimental audio backend Įjungti eksperimentinę garso vidinę pusę Audio quality Garso kokybė Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Persiunčiamo garso kokybė. Sumažinkite šį nustatymą, jeigu siuntimo sparta nėra pakankamai aukšta, arba jei norite sumažinti interneto duomenų naudojimą. High (64 kbps) Aukšta (64 kbps) Medium (32 kbps) Vidutinė (32 kbps) Low (16 kbps) Žema (16 kbps) Very low (8 kbps) Labai žema (8 kbps) Threshold Slenkstis AboutForm About Apie Original author: %1 Pradinis autorius: %1 You are using qTox version %1. Naudojate qTox versiją: %1. Commit hash: %1 Atnaujinimo maiša: %1 toxcore version: %1 toxcore versija: %1 Qt version: %1 Qt versija: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Visų žinomų klaidų sąrašą galima rasti mūsų %1, Github puslapyje. Jeigu qTox programoje rasite klaidą ar saugumo spragą, prašome apie ją pranešti, kaip tai yra nurodyta mūsų gairėse, vikio straipsnyje, pavadinimu "%2". Click here to report a bug. Spustelėkite čia, kad praneštumėte apie klaidą. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Visą %1 rasite Github svetainėje bug-tracker Replaces `%1` in the `A list of all known…` klaidų seklyje Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Kaip parašyti naudingą pranešimą apie klaidą contributors Replaces `%1` in `See a full list of…` talkininkų sąrašą AboutFriendForm Dialog Dialogas username Naudotojo vardas status message Būsena Used aliases: Naudoti slapyvardžiai: HISTORY OF ALIASES SLAPYVARDŽIŲ ISTORIJA Automatically accept files from contact if set Jeigu nustatyta, automatiškai priimti failus iš šio kontakto Auto accept files Automatiškai priimti failus Default directory to save files: Numatytas katalogas failams įrašyti: Auto accept for this contact is disabled Nuo šio kontakto failai automatiškai nepriimami Auto accept call: Automatiškai atsiliepti į skambutį: Manual Rankiniu būdu Audio Garsas Audio + Video Garsas + Vaizdas Automatically accept group chat invitations from this contact if set. Jei nustatyta, automatiškai priimti grupės pokalbio pakvietimus nuo šio kontakto. Auto accept group invites Automatiškai priimti grupės pakvietimus Remove history (operation can not be undone!) Išvalyti pokalbių žurnalą (operacija neatšaukiama!) Notes Pastabos Input field for notes about the contact Įvesties laukas, skirtas pastaboms apie kontaktą You can save comment about this contact here. Čia galite įrašyti komentarus apie šį kontaktą. History removed Žurnalas išvalytas Choose an auto accept directory popup title Pasirinkite katalogą automatiniam priėmimui <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Tai yra jūsų draugo viešasis raktas, naudokite jį, norėdami patvirtinti draugo tapatybę kitu kanalu. Jūs negalite siųsti šio rakto kitiems asmenims, kad jie pridėtų šį kontaktą.</p></body></html> Public key (not ToxID): Viešasis raktas (ne ToxID): Confirmation Patvirtinimas Are you sure to remove %1 chat history? Ar tikrai norite pašalinti pokalbių su %1 žurnalą? Failed to remove chat history with %1! Nepavyko pašalinti pokalbių su %1 žurnalo! AboutSettings Version Versija License Licencija Authors Kūrėjai Known Issues Žinomos klaidos Open update download link Atverti atnaujinimo atsisiuntimo nuorodą Update available Yra prieinamas atnaujinimas qTox is up to date ✓ qTox yra naujausios versijos ✓ AddFriendForm Add Friends Pridėti kontaktą Invalid Tox ID format Tox ID neatitinka formato Send friend request Siųsti užklausą Couldn't add friend Nepavyko pridėti kontakto Add a friend Pridėti kontaktą Friend requests Kontaktų užklausos Accept Priimti Reject Atmesti Tox ID, either 76 hexadecimal characters or name@example.com Tox ID (76 šešioliktainės sistemos simboliai, arba vardas@example.com) Type in Tox ID of your friend Įrašykite kontakto Tox ID Friend request message Kontakto užklausos žinutė Type message to send with the friend request or leave empty to send a default message Parašykite žinutę, kurią siųsti kartu su kontakto užklausa arba palikite tuščią, kad būtų išsiųsta numatytoji žinutė %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID yra neteisingas arba jo nėra You can't add yourself as a friend! When trying to add your own Tox ID as friend Negalite pridėti savęs kaip kontakto! Open contact list Atverti kontaktų sąrašą Couldn't open file Nepavyko atverti failo Couldn't open the contact file Error message when trying to open a contact list file to import Nepavyko atverti kontakto failo Invalid file Neteisingas failas We couldn't find any contacts to import in this file! Nepavyko rasti jokių kontaktų, kuriuos importuoti į šį failą! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description 76 šešioliktainės sistemos skaitmenys arba vardas@example.com Message The message you send in friend requests Žinutė Open Button to choose a file with a list of contacts to import Atverti Send friend requests Siųsti kontaktų užklausas %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Čia %1! Gal susirašinėjam per Tox? Import a list of contacts, one Tox ID per line Importuoti kontaktų sąrašą, kiekvienoje eilutėje po vieną Tox ID Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Pasiruošę importuoti %n kontaktą, norėdami patvirtinti spustelėkite siųsti Pasiruošę importuoti %n kontaktus, norėdami patvirtinti spustelėkite siųsti Pasiruošę importuoti %n kontaktų, norėdami patvirtinti spustelėkite siųsti Import contacts Importuoti kontaktus AdvancedForm Advanced Kita Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. %2 čia nekeiskite, nebent %1 žinote ką darote. Čia atlikti pakeitimai gali sukelti problemų su qTox ir netgi duomenų (pvz., pokalbių žurnalo) praradimą. really tikrai not Nieko IMPORTANT NOTE SVARBUS PRANEŠIMAS Reset settings Atstatyti nustatymus All settings will be reset to default. Are you sure? Visi nustatymai bus atstatyti į numatytuosius. Ar tikrai to norite? Yes Taip No Ne Call active popup title Vyksta pokalbis You can't disconnect while a call is active! popup text Vykstant pokalbiui, negalite atsijungti! Save File Įrašyti failą Logs (*.log) Žurnalai (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Įrašyti nustatymus į darbinį katalogą, vietoj įprasto konfigūracijos katalogo Make Tox portable Leisti persinešti Tox programą Reset to default settings Atstatyti numatytuosius nustatymus Portable Perkeliama Connection Settings Ryšio nustatymai Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Įjungti IPv6 (rekomenduojama) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Išjungus, galima naudotis Tox protokolu per Tor. Tox tinklas dėl to yra papildomai apkraunamas, todėl nuimkite žymėjimą tik tada, kai reikia. Enable UDP (recommended) Text on checkbox to disable UDP Įjungti UDP (rekomenduojama) Proxy type: Įgaliotojo serverio tipas: Address: Text on proxy addr label Adresas: Port: Text on proxy port label Prievadas: None Joks SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Prisijungti iš naujo Debug Derinimas Export Debug Log Eksportuoti derinimo žurnalą Copy Debug Log Kopijuoti derinimo žurnalą Enable LAN discovery Įjungti LAN atradimą ChatForm Send a file Siųsti failą qTox wasn't able to open %1 qTox nepavyko atverti %1 Unable to open Nepavyko atverti Bad idea Bloga mintis %1 calling Skambina %1 Calling %1 Skambiname %1 Failed to open temporary file Temporary file for screenshot Nepavyko atverti laikinojo failo qTox wasn't able to save the screenshot Nepavyko įrašyti ekrano kopijos Call with %1 ended. %2 Pokalbis su %1 baigėsi. %2 Call duration: Pokalbio trukmė: %1 is typing %1 rašo Copy Kopijuoti You're trying to send a sequential file, which is not going to work! Jūs bandote išsiųsti nuoseklųjį failą, tai suveiks! %1 is now %2 e.g. "Dubslow is now online" %1 dabar %2 Call with %1 ended unexpectedly. %2 Skambutis su %1 netikėtai pasibaigė. %2 Filename contained illegal characters Failo pavadinime buvo neleidžiamų simbolių Illegal characters have been changed to _ so you can save the file on windows. Neleidžiami simboliai buvo pakeisti į _ tad dabar galite įrašyti failą Windows sistemoje. ChatFormHeader Can't start audio call Nepavyksta pradėti garso skambutį Start audio call Pradėti garso skambutį End audio call Užbaigti garso skambutį Cancel audio call Atsisakyti garso skambučio Accept audio call Atsiliepti į garso skambutį Can't start video call Nepavyksta pradėti vaizdo skambutį Start video call Pradėti vaizdo skambutį End video call Užbaigti vaizdo skambutį Cancel video call Atsisakyti vaizdo skambučio Accept video call Atsiliepti į vaizdo skambutį Sound can be disabled only during a call Garsas gali būti išjungtas tik skambučio metu Unmute call Įjungti garsą Mute call Išjungti garsą Microphone can be muted only during a call Mikrofonas gali būti nutildytas tik skambučio metu Unmute microphone Įjungti mikrofoną Mute microphone Nutildyti mikrofoną ChatLog Copy Kopijuoti Select all Pažymėti viską pending dar neišsiųsta ChatTextEdit Type your message here... Įrašykite čia savo žinutę... CircleWidget Rename circle Menu for renaming a circle Pervadinti draugų ratą Remove circle Menu for removing a circle Pašalinti draugų ratą Open all in new window Atverti visus atskirame lange Core /me offers friendship, "%1" /me siūlo bendrauti: „%1“ Invalid Tox ID Error while sending friendship request Neteisingas Tox ID You need to write a message with your request Error while sending friendship request Turite parašyti adresatui žinutę su savo užklausa Your message is too long! Error while sending friendship request Žinutė per ilga! Friend is already added Error while sending friendship request Toks kontaktas jau yra pridėtas Groupchat %1 Grupės pokalbis %1 DesktopNotify New message Nauja žinutė Incoming file transfer Gaunamo failo persiuntimas Friend request received Gauta kontakto užklausa New group message Nauja grupės žinutė Group invite received Gautas pakvietimas į grupę FileTransferWidget Form Forma 10Mb 10 Mb 0kb/s 0 KB/s ETA:10:10 Liko: 10:10 Filename Failo pavadinimas Waiting to send... file transfer widget Laukiama gavėjo... Accept to receive this file file transfer widget Priimkite, kad gautumėte šį failą Location not writable Title of permissions popup Vieta nėra skirta rašymui You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nėra teisių įrašyti failą šioje vietoje. Bandykite įrašyti kitur arba atsisakykite dialogo lango. Resuming... file transfer widget Siuntimas pratęsiamas... Cancel transfer Atsisakyti siuntimo Pause transfer Pristabdyti siuntimą Paused file transfer widget Pristabdyta Open file Atverti failą Open file directory Atverti katalogą Resume transfer Pratęsti siuntimą Accept transfer Priimti siuntimą Save a file Title of the file saving dialog Įrašyti failą Remote Paused file transfer widget Kita šalis pristabdė FilesForm Transferred Files "Headline" of the window Persiųsti failai Downloads Atsiųsti Uploads Išsiųsti FriendListWidget Today Šiandien Yesterday Vakar Last 7 days Per paskutinias 7 dienas This month Šį mėnesį Older than 6 Months Seniau nei prieš 6 mėnesius Never Niekada FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Kontakto užklausa Someone wants to make friends with you Kažkas nori su jumis bendrauti User ID: Naudotojo ID: Friend request message: Kontakto užklausos žinutė: Accept Accept a friend request Priimti kontaktą Reject Reject a friend request Atmesti kontaktą FriendWidget Invite to group Menu to invite a friend to a groupchat Pakviesti į grupės pokalbį Move to circle... Menu to move a friend into a different circle Perkelti į draugų ratą... To new circle Į naują ratą Remove from circle '%1' Pašalinti iš draugų rato „%1“ Move to circle "%1" Perkelti į draugų ratą „%1“ Open chat in new window Atverti pokalbį atskirame lange Remove chat from this window Pašalinti pokalbį iš šio lango Set alias... Nustatyti slapyvardį... Auto accept files from this friend context menu entry Automatiškai priimti failus iš šio kontakto Remove friend Menu to remove the friend from our friendlist Šalinti kontaktą Show details Rodyti profilį Choose an auto accept directory popup title Pasirinkite katalogą priimamiems failams New message Nauja žintutė Online Prisijungęs Away Pasišalinęs Busy Užsiėmęs Offline Neprisijungęs To new group Į naują grupės pokalbį Invite to group '%1' Pakviesti į grupės pokalbį „%1“ GeneralForm General Bendrosios Choose an auto accept directory popup title Pasirinkite priimamų failų katalogą GeneralSettings General Settings Bendrosios nuostatos The translation may not load until qTox restarts. Vertimas gali nepasirodyti, kol nepaleisite qTox iš naujo. Show system tray icon Rodyti sistemos juostelėje Enable light tray icon. toolTip for light icon setting Naudoti šviesią sistemos juostelės piktogramą. Start qTox on operating system startup (current profile). Paleisti qTox įjungus kompiuterį (prisijungus mano vardu). qTox will start minimized in tray. toolTip for Start in tray setting qTox pasileis pasislėpęs sistemos juostelėje. Start in tray Paslėpti paleidus After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Spustelėjus uždarymo mygtuką (X) qTox pasislėps sistemos juostelėje. Close to tray Paslėpti uždarius After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Spustelėjus sumažinimo mygtuką (_) qTox pasislėps sistemos juostelėje, o ne programų juostoje. Minimize to tray Paslėpti sumažinus Light icon Šviesi piktograma Language: Kalba: Autostart Paleisti įjungus kompiuterį Set where files will be saved. Nustatykite, kur išsaugoti gautus failus. Your status is changed to Away after set period of inactivity. Jūsų būsena po nustatyto laiko automatiškai bus pakeista į „Pasišalinęs“. Auto away after (0 to disable): Automatiškai „pasišalinęs“ po („0“ išjungia): Show contacts' status changes Rodyti kontaktų būsenos pokyčius Set to 0 to disable Išjungsite nustatydami „0“ You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Galite nustatyti individualiai ant bet kurio kontakto spustelėję dešiniuoju pelės klavišu. Autoaccept files Automatiškai priimti failus Default directory to save files: Numatytas katalogas failams išsaugoti: Check for updates Tikrinti, ar yra atnaujinimų Spell checking Rašybos tikrinimas Max autoaccept file size (0 to disable): Didžiausio automatiškai priimamo failo dydis ("0" išjungia): MB MB GenericChatForm Send message Siųsti žinutę Smileys Jaustukai Send file(s) Siųsti failą (-us) Send a screenshot Siųsti ekrano kopiją Save chat log Išsaugoti pokalbio žurnalą Clear displayed messages Išvalyti rodomas žinutes Cleared Išvalyta Quote selected text Cituoti pažymėtą tekstą Copy link address Kopijuoti nuorodos adresą Confirmation Patvirtinimas You are sure that you want to clear all displayed messages? Ar tikrai norite išvalyti visas rodomas žinutes? Search in text Ieškoti tekste Go to current date Pereiti į dabartinę datą Load chat history... Įkelti pokalbių žurnalą... Export to file Eksportuoti į failą GenericNetCamView Tox video Tox vaizdas Show Messages Rodyti žinutes Hide Messages Slėpti žinutes Full Screen Visas ekranas Toggle video preview Perjungti vaizdo peržiūrą Mute audio Nutildyti garsą Mute microphone Nutildyti mikrofoną End video call Užbaigti vaizdo skambutį Exit full screen Išeiti iš viso ekrano GroupChatForm %1 has set the title to %2 %1 nustatė pavadinimą „%2“ %1 has joined the group %1 prisijungė prie grupės %1 is now known as %2 %1 dabar yra žinoma(-s) kaip %2 %1 has left the group %1 išėjo iš grupės %n user(s) in chat Number of users in chat Pokalbyje yra %n naudotojas Pokalbyje yra %n naudotojai Pokalbyje yra %n naudotojų mute nutildyti unmute įjungti garsą GroupInviteForm Groups Grupės Create new group Sukurti naują grupės pokalbį Group invites Pakvietimai į grupes GroupInviteWidget Invited by %1 on %2 at %3. Pakvietė %1, %2, %3. Join Prisijungti Decline Atmesti GroupWidget Open chat in new window Atverti pokalbį atskirame lange Remove chat from this window Pašalinti pokalbį iš šio lango Set title... Nustatyti pavadinimą... Quit group Menu to quit a groupchat Palikti grupės pokalbį %n user(s) in chat Number of users in chat Pokalbyje yra %n naudotojas Pokalbyje yra %n naudotojai Pokalbyje yra %n naudotojų New Message Nauja žinutė Online Prisijungęs(-usi) IdentitySettings Public Information Vieša informacija Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Ši simbolių seka leidžia kitiems Tox naudotojams jus surasti. Nusiųskite ją tiems, su kuriais norite bendrauti. Your Tox ID (click to copy) Jūsų Tox ID (spustelėję nukopijuosite) Profile Profilis Rename profile. tooltip for renaming profile button Pervadinti profilį. Delete profile. delete profile button tooltip Ištrinti profilį. Go back to the login screen tooltip for logout button Grįžti į prisijungimo langą Logout import profile button Atsijungti Remove password Pašalinti slaptažodį Change password Pakeisti slaptažodį This QR code contains your Tox ID. You may share this with your friends as well. Šiame kode yra jūsų Tox ID. Pasidalykite juo su savo kontaktais. Save image Išsaugoti paveikslėlį Copy image Nukopijuoti paveikslėlį Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Galite eksportuoti Tox profilį į failą. Pokalbių žurnalas nebus išsaugotas. Rename rename profile button Pervadinti Export export profile button Eksportuoti Delete delete profile button Ištrinti Server Serveris Hide my name from the public list Paslėpti mano vardą viešajame sąraše Register Registruotis Your password Jūsų slaptažodis Update Atnaujinti Register on ToxMe Registruotis ToxMe paslaugoje Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Vardas, skirtas ToxMe paslaugai. Optional. Something about you. Or your cat. Tooltip for the Biography text. Nebūtina. Kas nors apie jus. Arba jūsų katiną. Optional. Something about you. Or your cat. Tooltip for the Biography field. Nebūtina. Kas nors apie jus. Arba jūsų katiną. ToxMe service to register on. ToxMe paslauga, kurioje registruotis. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Jeigu nenustatyta, ToxMe įrašai bus matomi viešai. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Šalinti jūsų slaptažodį ir šifravimą iš jūsų profilio. Name input Vardo įvestis Name visible to contacts Kontaktams matomas vardas Status message input Būsenos žinutės įvestis Status message visible to contacts Kontaktams matoma būsenos žinutė Your Tox ID Jūsų Tox ID Save QR image as file Įrašyti QR paveikslą kaip failą Copy QR image to clipboard Kopijuoti QR paveikslą į iškarpinę ToxMe username to be shown on ToxMe ToxMe naudotojo vardas, kuris bus rodomas ToxMe Optional ToxMe biography to be shown on ToxMe Nebūtina ToxMe biografija, kuri bus rodoma ToxMe ToxMe service address ToxMe paslaugos adresas Visibility on the ToxMe service Matomumas ToxMe paslaugoje Password Slaptažodis Update ToxMe entry Atnaujinti ToxMe įrašą Rename profile. Pervadinti profilį. Delete profile. Ištrinti profilį. Export profile Eksportuoti profilį Remove password from profile Šalinti slaptažodį iš profilio Change profile password Pakeisti profilio slaptažodį My name: Mano vardas: My status: Mano būsena: My username Mano naudotojo vardas My biography Mano biografija My profile Mano profilis LoadHistoryDialog Load History Dialog Įkelti žurnalą Load history Įkelti žurnalą from nuo to iki (about 100 messages are loaded) (yra įkeliama apie 100 žinučių) Select Date Dialog Datos pasirinkimo dialogas Select a date Pasirinkti datą LoginScreen Username: Slapyvardis: Password: Slaptažodis: Confirm: Patvirtinti slaptažodį: Password strength: %p% Slaptažodžio stiprumas: %p% Create Profile Sukurti profilį Load automatically Prisijungti automatiškai Load Prisijungti Load Profile Aktyvuoti profilį If the profile does not have a password, qTox can skip the login screen Jei profilis neturi slaptažodžio, qTox gali prisijungti automatiškai New Profile Naujas profilis Couldn't create a new profile Nepavyko sukurti naujo profilio The username must not be empty. Slapyvardis negali būti tuščias. The password must be at least 6 characters long. Slaptažodis negali būti trumpesnis nei 6 simboliai. The passwords you've entered are different. Please make sure to enter same password twice. Įvesti slaptažodžiai neatitinka. Įsitikinkite, kad tą patį slaptažodį įvedate du kartus. A profile with this name already exists. Toks profilis jau yra. Couldn't load profile Nepavyko prisijungti There is no selected profile. You may want to create one. Profilių nėra. Galite sukurti naują. Couldn't load this profile Nepavyko įkelti šio profilio This profile is already in use. Profilis jau naudojamas. Wrong password. Neteisingas slaptažodis. Import Importuoti Password protected profiles can't be automatically loaded. Slaptažodžiu apsaugotų profilių automatiškai užkrauti negalima. Username input field Naudotojo vardo įvesties laukas Password input field, you can leave it empty (no password), or type at least 6 characters Slaptažodžio įvesties laukas, jūs galite palikti jį tuščią (jokio slaptažodžio) arba parašyti bent 6 simbolius Password confirmation field Slaptažodžio patvirtinimo laukas Create a new profile button Mygtukas sukurti naują profilį Profile list Profilių sąrašas List of profiles Profilių sąrašas Password input Slaptažodžio įvestis Load automatically checkbox Automatiškai įkelti žymimasis langelis Import profile Importuoti profilį Load selected profile button Mygtukas įkelti pasirinktą profilį New profile creation page Naujo profilio kūrimo puslapis Loading existing profile page Esamo profilio įkėlimo puslapis MainWindow Your name Jūsų vardas Your status Jūsų būsena ... ... Add friends Pridėti kontaktą Create a group chat Sukurti grupės pokalbį View completed file transfers Rodyti baigtus siųsti failus Change your settings Keisti nuostatas Close Uždaryti Open profile Atverti profilį Open profile page when clicked Spustelėjus, atverti profilio puslapį Status message input Būsenos žinutės įvestis Set your status message that will be shown to others Nustatykite savo būsenos žinutę, kuri bus rodoma kitiems Status Būsena Set availability status Nustatyti prieinamumo būseną Contact search Kontaktų paieška Contact search input for known friends Kontaktų paieškos įvestis, skirta žinomiems kontaktams Sorting and visibility Rikiavimas ir matomumas Set friends sorting and visibility Nustatyti kontaktų rikiavimą ir matomumą Open Add friends page Atverti kontaktų pridėjimo puslapį Groupchat Grupės pokalbis Open groupchat management page Atverti grupės pokalbio tvarkymo puslapį File transfers history Failų persiuntimų istorija Open File transfers history Atverti failų persiuntimų istoriją Settings Nustatymai Open Settings Atverti nustatymus Nexus View OS X Menu bar Rodymas Window OS X Menu bar Langas Minimize OS X Menu bar Sumažinti Bring All to Front OS X Menu bar Fokusuoti viską Exit Fullscreen Išjungti pilno ekrano režimą Enter Fullscreen Aktyvuoti pilno ekrano režimą NotificationEdgeWidget Unread message(s) Paucal:2–9,22–29,32–39,... Plural:10–20,30,40,.. Neperskaityta žinutė Neperskaitytos žinutės Neperskaitytų žinučių PasswordEdit CAPS-LOCK ENABLED ĮJUNGTAS „CAPS LOCK“ PrivacyForm Privacy Privatumas Confirmation Patvirtinimas Do you want to permanently delete all chat history? Ar tikrai norite negrįžtamai ištrinti visą pokalbių žurnalą? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Jūsų kontaktai matys, kada rašote žinutę. Send typing notifications Rodyti įvesties pranešimus Keep chat history Vesti pokalbių žurnalą NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam „Nospam“ yra jūsų Tox ID dalis. Jį pakeitus nebegausite nepageidaujamų kontaktinių užklausų. Priimti kontaktai vis dar galės su jumis bendrauti, bet nauji kontaktai, nežinantys jūsų naujojo Tox ID, nebegalės atsiųsti jums užklausų. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. „NoSpam“ – jūsų Tox ID dalis, kurią galima bet kada pakeisti. Jei gaunate nepageidaujamų kontaktinių užklausų, pakeiskite „NoSpam“. Generate random NoSpam Sugeneruoti atsitiktinį „NoSpam“ Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Pokalbių žurnalo funkcija dar nestabili. Failo formatas dar gali pasikeisti, todėl galite prarasti sukauptus duomenis. Privacy Privatumas BlackList Juodasis sąrašas Filter group message by group member's public key. Put public key here, one per line. Filtruokite grupės žinutes pagal grupės dalyvių viešuosius raktus. Čia kiekvienoje eilutėje įdėkite po vieną viešąjį raktą. Profile Failed to derive key from password, the profile won't use the new password. Nepavyko įgauti rakto iš slaptažodžio, profilis nenaudos naujojo slaptažodžio. Couldn't change password on the database, it might be corrupted or use the old password. Nepavyko pakeisti slaptažodžio duomenų bazėje, ji gali būti sugadinta arba gali naudoti seną slaptažodį. Toxing on qTox Naudoju qTox ProfileForm Choose a profile picture Pasirinkite profilio paveikslėlį Error Klaida Rename "%1" renaming a profile Pervadinti „%1“ Location not writable Title of permissions popup Įrašyti failą čia neleidžiama You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nėra teisių įrašyti failą šioje vietoje. Bandykite įrašyti kitur arba atsisakykite dialogo lango. Really delete profile? deletion confirmation title Ar tikrai ištrinti profilį? Save save qr image Išsaugoti Save QrCode (*.png) save dialog filter Išsaugoti QR kodą (*.png) Failed to copy file Failo nukopijuoti nepavyko Current profile: Aktyvuotas profilis: Remove Pašalinti Unable to open this file. Nepavyko atidaryti failo. Unable to read this image. Nepavyko perskaityti paveikslėlio. The supplied image is too large. Please use another image. Pasirinktas paveikslėlis per didelis. Pasirinkite kitą. Couldn't rename the profile to "%1" Nepavyko pervadinti profilį į „%1“ The file you chose could not be written to. Nepavyko įrašyti į pasirinktą failą. Nothing to remove Nėra slaptažodžio Your profile does not have a password! Jūsų profilis neturi slaptažodžio! Really delete password? deletion confirmation title Ar tikrai panaikinti slaptažodį? Please enter a new password. Įveskite naują slaptažodį. Are you sure you want to delete this profile? deletion confirmation text Ar tikrai norite ištrinti šį profilį? Files could not be deleted! deletion failed title Failų ištrinti nepavyko! Register (processing) Registruotis (vykdoma) Update (processing) Atnaujinti (vykdoma) Done! Baigta! Account %1@%2 updated successfully Paskyra %1@%2 sėkmingai atnaujinta Successfully added %1@%2 to the database. Save your password %1@%2 užregistruotas duomenų bazėje. Nepamirškite slaptažodžio Toxme error Toxme klaida Register Registruotis Update Atnaujinti Change password button text Pakeisti slaptažodį Set profile password button text Nustatyti profilio slaptažodį Current profile location: %1 Aktyvaus profilio saugojimo vieta: %1 Couldn't change password Nepavyko pakeisti slaptažodžio This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Šis simbolių rinkinys nurodo kitiems Tox klientams kaip su jumis susisiekti. Norėdami bendrauti, pasidalinkite juo su savo draugais. Šiame ID yra NoSpam kodas (mėlynas) ir kontrolinė suma (pilka). Empty path is unavaliable Tuščias kelias yra neprieinamas Failed to rename Nepavyko pervadinti Profile already exists Toks profilis jau yra A profile named "%1" already exists. Profilis „%1“ jau yra. Empty name Tuščias vardas Empty name is unavaliable Tuščias vardas yra neprieinamas Empty path Tuščias kelias Couldn't change password on the database, it might be corrupted or use the old password. Nepavyko pakeisti slaptažodžio duomenų bazėje, ji gali būti sugadinta arba gali naudoti seną slaptažodį. Export profile Eksportuoti profilį Tox save file (*.tox) save dialog filter Tox failas (*.tox) The following files could not be deleted: deletion failed text part 1 Šių failų ištrinti nepavyko: Please manually remove them. deletion failed text part 2 Prašome juos pašalinti rankiniu būdu. Are you sure you want to delete your password? deletion confirmation text Ar tikrai norite panaikinti savo slaptažodį? Images (%1) filetype filter Paveikslai (%1) ProfileImporter Import profile import dialog title Importuoti profilį Tox save file (*.tox) import dialog filter Tox failas (*.tox) Ignoring non-Tox file popup title Praleidžiamas failas Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Įspėjimas: pasirinktas failas nėra Tox failas – praleista. Profile already exists import confirm title Toks profilis jau yra A profile named "%1" already exists. Do you want to erase it? import confirm text Profilis „%1“ jau yra. Ar norite jį ištrinti? File doesn't exist Failo nėra Profile doesn't exist Profilio nėra Profile imported Profilis importuotas %1.tox was successfully imported %1.tox sėkmingai importuotas QApplication Ok Gerai Cancel Atsisakyti Yes Taip No Ne LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Nepavyko pridėti kontakto %1 is not a valid Toxme address. %1 nėra taisyklingas Toxme adresas. You can't add yourself as a friend! When trying to add your own Tox ID as friend Negalite pridėti savęs kaip kontakto! QObject Tox URI to parse analizuoti Tox URI Starts new instance and loads specified profile. Atidaro naują langą ir aktyvuoja nurodytą profilį. profile profilis Default Numatyta Blue Mėlyna Olive Alyvinė Red Raudona Violet Violetinė Incoming call... Skambutis... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Čia %1! Gal susirašinėjam per Tox? None No camera device set Joks Desktop Desktop as a camera input for screen sharing Darbalaukio transliacija Error Klaida qTox couldn't open your chat logs, they will be disabled. qTox nepavyko atidaryti pokalbių žurnalo, todėl jis buvo išjungtas. Server doesn't support Toxme Serveris nepalaiko Toxme funkcijos You're making too many requests. Wait an hour and try again Per daug užklausų. Pabandykite dar sykį po valandos This name is already in use Vardas jau užimtas This Tox ID is already registered under another name Šis Tox ID jau užregistruotas kitu vardu Please don't use a space in your name Vardas turi būti be tarpų Password incorrect Neteisingas slaptažodis You can't use this name Šio vardo naudoti negalima Name not found Tokio vardo nėra Tox ID not sent Tox ID neišsiųstas That user does not exist Šio naudotojo nėra Problem with HTTPS connection Nepavyko sudaryti HTTPS ryšio Internal ToxMe error Vidinė „ToxMe“ klaida Reformatting text in progress.. Teksto reformatavimas eigoje.. Starts new instance and opens the login screen. Paleidžia naują egzempliorių ir atveria prisijungimo ekraną. Dark Tamsi Dark blue Tamsiai mėlyna Dark olive Tamsiai gelsva Dark red Tamsiai raudona Dark violet Tamsiai violetinė Failed to load profile automatically. Nepavyko automatiškai įkelti profilio. online contact status prisijungęs(-usi) away contact status pasišalinęs(-usi) busy contact status užsiėmęs(-usi) offline contact status neprisijungęs(-usi) blocked contact status užblokuota(-s) RemoveFriendDialog Remove friend Šalinti kontaktą Also remove chat history Išvalyti pokalbių žurnalą Remove Pašalinti Are you sure you want to remove %1 from your contacts list? Ar tikrai norite pašalinti %1 iš savo kontaktų sąrašo? Remove all chat history with the friend if set Jei nustatyta, šalinti visą pokalbių su kontaktu istoriją ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Spustelėkite pelės klavišą ir vilkdami pelę pasirinkite norimą ekrano sritį. Paspauskite %1 klavišą, kad būtų paslėptas/parodytas qTox langas, arba paspauskite %2 klavišą, kad atsisakytumėte. Space [Space] key on the keyboard tarpo Escape [Escape] key on the keyboard Escape (atšaukimo) Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Paspauskite %1 klavišą, kad nusiųstumėte pasirinktos ekrano srities kopiją, o %2, kad parodytumėte/paslėptumėte qTox langą. Veiksmo atsisakysite, paspaudę %3 klavišą. Enter [Enter] key on the keyboard Enter (įvedimo) SearchForm The text could not be found. Nepavyko rasti teksto. Start Pradėti SearchSettingsForm Form Forma Start search: Pradėti paiešką: from the end nuo galo from the beginning nuo pradžios after date po datos before date prieš datą 00.00.0000 00.00.0000 Case sensitive Skirti raidžių dydį Whole words only Tik visas žodis Use regular expressions Naudoti reguliariuosius reiškinius SetPasswordDialog Set your password Nustatykite slaptažodį Confirm: Patvirtinti slaptažodį: Password: Slaptažodis: Password strength: %p% Slaptažodžio stiprumas: %p% The password is too short Slaptažodis per trumpas The password doesn't match. Slaptažodis neatitinka. Confirm password Patvirtinkite slaptažodį Confirm password input Slaptažodžio patvirtinimo įvestis Password input Slaptažodžio įvestis Password input field, minimum 6 characters long Slaptažodžio įvesties laukas, mažiausiai 6 simbolių ilgio Settings Circle #%1 Draugų ratas Nr. %1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Pridėti kontaktą Do you want to add %1 as a friend? Ar norite pridėti %1 į kontaktus? User ID: Naudotojo ID: Friend request message: Prisistatymo žinutė: Send Send a friend request Siųsti Cancel Don't send a friend request Atšaukti UserInterfaceForm None Joks User Interface Naudotojo sąsaja UserInterfaceSettings Chat Susirašinėjimas Base font: Numatytasis šriftas: px piks. Size: Dydis: New text styling preference may not load until qTox restarts. Naujoji teksto stiliaus nuostata negali būti įkelta tol, kol qTox nebus paleista iš naujo. Text Style format: Teksto stiliaus formatas: Select text styling preference. Pasirinkite teksto stiliaus nuostatą. Plaintext Grynasis tekstas Show formatting characters Rodyti formatavimo simbolius Don't show formatting characters Nerodyti formatavimo simbolių New message Nauja žinutė Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Gavus naują žinutę, atverti qTox langą, jeigu jis dar nėra atvertas. Open window Atverti langą Contact list Kontaktų sąrašas If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Jei pažymėta, grupių pokalbiai bus rodomi kontaktų sąrašo viršuje, priešingu atveju – apačioje. Place groupchats at top of friend list Rodyti grupių pokalbius kontaktų sąrašo viršuje Your contact list will be shown in compact mode. toolTip for compact layout setting Jūsų kontaktų sąrašas bus rodomas glaustoje veiksenoje. Compact contact list Glaustas kontaktų sąrašas Multiple windows mode Atskirų langų veiksena Open each chat in an individual window Kiekvieną pokalbį atverti atskirame lange Emoticons Jaustukai Use emoticons Naudoti jaustukus Smiley Pack: Text on smiley pack label Šypsenėlių rinkinys: Emoticon size: Jaustukų dydis: px piks. Theme Apipavidalinimas Style: Stilius: Theme color: Apipavidalinimo spalva: Timestamp format: Laiko žymos formatas: Date format: Datos formatas: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Įjungus, kiekvienam kontaktui be avataro rinkinio, vietoj kontakto numatytojo paveikslo, bus sugeneruotas avataras pagal jo Tox ID. Use identicons instead of empty avatars Vietoj tuščių avatarų, naudoti tapatybės piktogramas Use colored nicknames in chats Naudoti pokalbiuose spalvotus slapyvardžius Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Rodyti pranešimą, kai gaunate naują žinutę, o langas nėra pasirinktas. Notify Rodyti pranešimus Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Praneša apie naujas žinutes grupės pokalbiuose tik tuomet, kai kas nors jus paminėjo. Group chats only notify when mentioned Rodyti grupės pokalbio pranešimus tik tuomet, kai kas nors paminėjo Play sound Groti garsą Play sound while Busy Groti garsą, kai įjungta būsena „Užsiėmęs“ Notify via desktop notifications Pranešti per darbalaukio pranešimus Hide message sender and contents Slėpti žinutės siuntėją ir turinį Widget Online Prisijungę Add new circle... Sukurti naują draugų ratą... By Name pagal vardą By Activity pagal aktyvumą All Visi Offline Atsijungę Friends Kontaktai Groups Grupės Search Contacts Ieškoti kontaktų Online Button to set your status to 'Online' Prisijungęs Away Button to set your status to 'Away' Pasišalinęs Busy Button to set your status to 'Busy' Užsiėmęs Logout Tray action menu to logout user Atsijungti Exit Tray action menu to exit tox Išjungti Filter... Filtruoti... File Failai Edit Taisyti Contacts Kontaktai Change Status Keisti būseną Edit Profile Redaguoti profilį Log out Atsijungti Add Contact... Pridėti kontaktą... Next Conversation Kitas pokalbis Previous Conversation Ankstesnis pokalbis toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Toxcore neprisijungia su Jūsų įgaliotojo serverio nustatymais. qTox negali dirbti – pakeiskite nustatymus ir prisijunkite iš naujo. Executable file popup title Vykdomasis failas You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Nurodėte qTox atidaryti vykdomąjį failą (programą). Vykdomieji failai gali pakenkti Jūsų kompiuteriui. Ar norite tęsti? Couldn't request friendship Nepavyko nusiųsti užklausos Status Būsena toxcore failed to start, the application will terminate after you close this message. toxcore paleisti nepavyko: programa išsijungs uždarius šį pranešimą. Your name Jūsų vardas Message failed to send Nepavyko nusiųsti žinutės Groupchat #%1 Grupės pokalbis Nr. %1 Create new group... Sukurti naują grupę... %n New Friend Request(s) %n nauja kontaktų užklausa %n naujos kontaktų užklausos %n naujų kontaktų užklausų %n New Group Invite(s) %n naujas grupės pakvietimas %n nauji grupės pakvietimai %n naujų grupės pakvietimų Show Tray action menu to show qTox window Fokusuoti langą Add friend title of the window Pridėti kontaktą Group invites title of the window Pakvietimai į grupes File transfers title of the window Failų persiuntimai Settings title of the window Nustatymai My profile title of the window Mano profilis Failed to send file "%1" Nepavyko išsiųsti failo „%1“ File sent Failas išsiųstas sent you a friend request. išsiuntė jums kontakto užklausą. invites you to join a group. kviečia jus prisijungti prie grupės. qTox/translations/lv.ts000066400000000000000000003320421415623743500155410ustar00rootroot00000000000000 AVForm Audio/Video Audio/Video Default resolution Noklusējuma izšķirtspēja Disabled Atspējots Select region Izvēlieties reģionu Screen %1 Ekrāns %1 Audio Settings Audio iestatījumi Gain Pastiprinājums Playback device Atskaņošanas ierīce Use slider to set volume of your speakers. Izmantojiet slīdni, lai iestatītu skaļruņu skaļumu. Capture device Ierakstīšanas ierīce Volume Skaļums Video Settings Video iestatījumi Video device Video ierīce Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Iestatiet kameras izšķirtspēju. Jo lielāka vērtība, jo augstākā kvalitātē video varēs redzēt Jūsu draugi. Ņemiet vērā, jo labāka ir video kvalitāte, jo ir nepieciešams labāks interneta pieslēgums. Nosūtot augstas kvalitātes video, dažreiz savienojums var būt nepietiekami labs, kas var radīt video zvanu problēmas. Resolution Izšķirtspēja Rescan devices Pārskenēt ierīces Test Sound Skaņas pārbaude Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Ietver eksperimentālu skaņas sistēmu, ar atbalss slāpēšanas atbalstu. Nepieciešams restartēt qTox, lai stātos spēkā. Enable experimental audio backend Iespējot eksperimentālo audio sistēmu Audio quality Audio kvalitāte Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Pārraidītās skaņas kvalitāte. Samaziniet iestatījuma vērtību, ja savienojums nav pietiekami ātrs vai vēlaties samazināt interneta izmantošanu. High (64 kbps) Augsts (64 kbps) Medium (32 kbps) Vidējs (32 kbps) Low (16 kbps) Zems (16 kbps) Very low (8 kbps) Ļoti zems (8 kbps) Threshold Slieksnis AboutForm About Par Original author: %1 Sākotnējais autors: %1 You are using qTox version %1. Jūs lietojat qTox versiju %1. Commit hash: %1 Saistības kešs: %1 toxcore version: %1 Tox kodola versija: %1 Qt version: %1 Qt versija: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Visu zināmo problēmu sarakstu Jūs varat atrast mūsu %1 vietnē Github. Ja Jūs atklājat kādu kļūdu vai neaizsargātību qTox drošības jomā, lūdzu ziņojiet par to saskaņā ar mūsu %2 wiki raksta vadlīnijām. Click here to report a bug. Noklikšķiniet šeit, lai ziņotu par kļūdu. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Pilnu sarakstu %1 skatiet Github vietnē bug-tracker Replaces `%1` in the `A list of all known…` kļūdu sekotājs Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Rakstīt lietderīgu kļūdu ziņojumu contributors Replaces `%1` in `See a full list of…` izstrādātāji AboutFriendForm Dialog Dialogs username lietotāja vārds status message statusa ziņojums Used aliases: Izmantotie pseidonīmi: HISTORY OF ALIASES PSEIDONĪMU VĒSTURE Automatically accept files from contact if set Automātiski pieņemt failus no izvēlētā kontakta Auto accept files Automātiski pieņemt failus Default directory to save files: Noklusējuma mape failu saglabāšanai: Auto accept for this contact is disabled Automātiska failu pieņemšana no šī kontakta ir aizliegta Auto accept call: Automātiska zvanu saņemšana: Manual Manuāli Audio Audio Audio + Video Audio + Video Automatically accept group chat invitations from this contact if set. Automātiski pieņemt grupas tērzēšanas ielūgumus no šī kontakta. Auto accept group invites Automātiski pieņemt grupas ielūgumus Remove history (operation can not be undone!) Dzēst vēsturi (darbību nevar atsaukt!) Notes Piezīmes Input field for notes about the contact Ievades piezīmju lauks par kontaktu You can save comment about this contact here. Šeit Jūs varat saglabāt komentāru par šo kontaktu. History removed Vēsture ir dzēsta Choose an auto accept directory popup title Izvēlieties automātiskās pieņemšanas mapi <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Šī ir Jūsu drauga publiskā atslēga, pielietojiet to, lai pārbaudītu savu identitāti, izmantojot citu kanālu. To nevar nosūtīt citiem cilvēkiem, lai viņi varētu pievienot šo kontaktu.</p></body></html> Public key (not ToxID): Publiskā atslēga (nevis ToxID): Confirmation Apstiprinājums Are you sure to remove %1 chat history? Vai tiešām vēlaties dzēst %1 tērzēšanas vēsturi? Failed to remove chat history with %1! Neizdevās dzēst tērzēšanas vēsturi ar %1! AboutSettings Version Versija License Licence Authors Autori Known Issues Zināmās problēmas Open update download link Atvērt atjaunināšanas lejupielādes saiti Update available Pieejams atjauninājums qTox is up to date ✓ qTox ir atjaunināts ✓ AddFriendForm Add Friends Pievienot draugus Invalid Tox ID format Nederīgs Tox ID formāts Send friend request Nosūtīt draudzības pieprasījumu Add a friend Pievienot draugu Friend requests Draudzības uzaicinājums Accept Pieņemt Reject Noraidīt Couldn't add friend Nevar pievienot draugu Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, vai 76 heksadecimālās rakstzīmes, vai name@example.com Type in Tox ID of your friend Ierakstiet sava drauga Tox ID Friend request message Draudzības pieprasījuma ziņojums Type message to send with the friend request or leave empty to send a default message Ierakstiet ziņojumu, lai nosūtītu kopā ar draudzības pieprasījumu, vai atstājiet tukšu, lai nosūtītu noklusējuma ziņojumu %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID ir nederīgs vai neeksistē You can't add yourself as a friend! When trying to add your own Tox ID as friend Jūs nevarat pievienot sevi kā draugu! Open contact list Atvērt kontaktu sarakstu Couldn't open file Nevar atvērt failu Couldn't open the contact file Error message when trying to open a contact list file to import Nevar atvērt kontakta failu Invalid file Nederīgs fails We couldn't find any contacts to import in this file! Neizdevās atrast nevienu kontaktu ko importēt Jūsu izvēlētajā failā! Tox ID Tox ID of the person you're sending a friend request to Tox-ID either 76 hexadecimal characters or name@example.com Tox ID format description 76 heksadecimālās rakstzīmes vai name@example.com Message The message you send in friend requests Ziņojums Open Button to choose a file with a list of contacts to import Atvērt Send friend requests Nosūtīt draudzības pieprasījumus %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Sveiki, šeit %1! Vai pievienosiet mani savos draugos? Import a list of contacts, one Tox ID per line Importēt kontaktu sarakstu, vienu Tox ID katrā rindā Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts Importēt kontaktus AdvancedForm Advanced Papildus Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Ja Jūs %1 nezināt ko darat, lūdzu, neveiciet %2 nekādas izmaiņas. Veiktās izmaiņas var izraisīt problēmas qTox darbībā, kā arī zaudēt datus (piemēram, vēsturi). really tiešām not nav IMPORTANT NOTE SVARĪGA PIEZĪME Reset settings Sākotnējie iestatījumi All settings will be reset to default. Are you sure? Visi iestatījumi tiks atiestatīti sākotnējā stāvoklī. Vai Jūs esat pārliecināts? Yes No Call active popup title Zvans ir aktīvs You can't disconnect while a call is active! popup text Jūs nevarat atvienoties, kad zvans ir aktīvs! Save File Saglabāt failu Logs (*.log) Žurnāls (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Saglabājiet iestatījumus darba direktorijā, nevis standarta iestatījumu mapē Make Tox portable Tox portatīvais režīms Reset to default settings Atjaunot sākotnējos iestatījumus Portable Portatīvs Connection Settings Savienojuma iestatījumi Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Iespējot IPv6 (ieteicams) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Piemēram, izslēgšana ļauj izmantot Tox virs Tor. Tomēr, tas palielinās Tox tīkla slodzi, tāpēc atvienojiet to tikai tad, kad tas ir nepieciešams. Enable UDP (recommended) Text on checkbox to disable UDP Iespējot UDP (ieteicams) Proxy type: Starpniekservera (Proxy) veids: Address: Text on proxy addr label Adrese: Port: Text on proxy port label Ports: None Nav SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button No jauna pievienojiet savienojumu Debug Atkļūdošana Export Debug Log Eksportēt atkļūdošanas žurnālu Copy Debug Log Kopēt atkļūdošanas žurnālu Enable LAN discovery Iespējot lokālā tīkla (LAN) noteikšanu ChatForm Send a file Sūtīt failu qTox wasn't able to open %1 qTox nevarēja atvērt %1 Unable to open Nevar atvērt Bad idea Slikta ideja %1 calling Ienākošais zvans no %1 Calling %1 Zvans %1 Failed to open temporary file Temporary file for screenshot Neizdevās atvērt pagaidu failu qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox nevarēja saglabāt ekrānuzņēmumu Call with %1 ended. %2 Saruna ar %1 beidzās. %2 Call duration: Sarunas ilgums: %1 is typing %1 raksta Copy Kopēt You're trying to send a sequential file, which is not going to work! Jūs mēģināt nosūtīt secīgu failu, kas nedarbosies! %1 is now %2 e.g. "Dubslow is now online" %1 tagad ir %2 Call with %1 ended unexpectedly. %2 Saruna ar %1 negaidīti beidzās. %2 Filename contained illegal characters Faila nosaukums satur nederīgas rakstzīmes Illegal characters have been changed to _ so you can save the file on windows. Nederīgās rakstzīmes ir nomainītas uz _ lai Jūs varētu saglabāt failu. ChatFormHeader Can't start audio call Nevar sākt audio zvanu Start audio call Sākt audio zvanu End audio call Beigt audio zvanu Cancel audio call Atcelt audio zvanu Accept audio call Pieņemt audio zvanu Can't start video call Nevar sākt videozvanu Start video call Sākt videozvanu End video call Beigt videozvanu Cancel video call Atcelt videozvanu Accept video call Atcelt videozvanu Sound can be disabled only during a call Skaņu var izslēgt tikai sarunas laikā Unmute call Ieslēgt skaņu Mute call Izslēgt skaņu Microphone can be muted only during a call Mikrofonu var izslēgt tikai sarunas laikā Unmute microphone Ieslēgt mikrofonu Mute microphone Izslēgt mikrofonu ChatLog Copy Kopēt Select all Izvēlēties visu pending gaida ChatTextEdit Type your message here... Ierakstiet savu ziņojumu šeit ... CircleWidget Rename circle Menu for renaming a circle Pārdēvēt sarakstu Remove circle Menu for removing a circle Dzēst sarakstu Open all in new window Atveriet visu jaunā logā Core /me offers friendship, "%1" /me piedāvā draudzību, ''%1'' Invalid Tox ID Error while sending friendship request Nederīgs Tox ID You need to write a message with your request Error while sending friendship request Jums ir jāsagatavo ziņojums ar pieprasījuma tekstu Your message is too long! Error while sending friendship request Jūsu ziņojums ir pārāk liels! Friend is already added Error while sending friendship request Draugs ir jau pievienots Groupchat %1 DesktopNotify New message Jauna ziņa Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen Forma 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen Atlicis: 10:10 Filename Ausgelassen Faila nosaukums Waiting to send... file transfer widget Gaida nosūtīšanu ... Accept to receive this file file transfer widget Ļaut saņemt šo failu Location not writable Title of permissions popup Nevar ierakstīt atrašanās vietu You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Jums nav atļaujas veikt ierakstus šajā mapē. Izvēlieties citu mapi vai atceliet saglabāšanas dialoglodziņu. Resuming... file transfer widget Atsākt ... Cancel transfer Atcelt pārsūtīšanu Pause transfer Apturēt pārsūtīšanu Paused file transfer widget Apturēts Open file Atvērt failu Open file directory Atvērt faila mapi Resume transfer Turpināt pārsūtīšanu Accept transfer Atļaut pārsūtīšanu Save a file Title of the file saving dialog Saglabāt failu Remote Paused file transfer widget Tālvadība apturēta FilesForm Transferred Files "Headline" of the window Pārsūtītie faili Downloads Lejupielādes Uploads Augšupielādes FriendListWidget Today Šodien Yesterday Vakar Last 7 days Pēdējās 7 dienas This month Šis mēnesis Older than 6 Months Vecāki par 6 mēnešiem Never Nekad FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Draudzības uzaicinājums Someone wants to make friends with you Kāds vēlas Jūs pievienot kā draugu User ID: Lietotāja ID: Friend request message: Draudzības pieprasījuma ziņojums: Accept Accept a friend request Pieņemt Reject Reject a friend request Noraidīt FriendWidget Invite to group Menu to invite a friend to a groupchat Ielūgt pievienoties grupai Move to circle... Menu to move a friend into a different circle Pārvietot uz sarakstu ... To new circle Jaunajā sarakstā Remove from circle '%1' Izņemt no saraksta '%1' Move to circle "%1" Pārvietot uz sarakstu ''%1'' Open chat in new window Atvērt tērzēšanu jaunā logā Remove chat from this window Izņemt tērzēšanu no šī loga To new group Uz jaunu grupu Invite to group '%1' Uzaicināt uz grupu '%1' Set alias... Iestatīt pseidonīmu ... Auto accept files from this friend context menu entry Automātiski pieņemt failus no šī drauga Remove friend Menu to remove the friend from our friendlist Noņemt draugu Show details Rādīt detalizētu informāciju Choose an auto accept directory popup title Izvēlieties automātiskās pieņemšanas mapi New message Jauna ziņa Online Tiešsaistē Away Nav šeit Busy Aizņemts Offline Ausgelassen Bezsaistē GeneralForm General Vispārīgi Choose an auto accept directory popup title Izvēlieties automātiskās pieņemšanas mapi GeneralSettings General Settings Vispārīgie iestatījumi The translation may not load until qTox restarts. Tulkojums neizmainīsies, kamēr no jauna neatvērsiet qTox. Language: Valoda: Show system tray icon Rādīt sistēmas paneļa ikonu Enable light tray icon. toolTip for light icon setting Iespējot gaišu paneļa ikonu. Light icon Gaiša ikona qTox will start minimized in tray. toolTip for Start in tray setting qTox palaidīsies minimizēts sistēmas panelī. Start in tray Palaisties minimizētam sistēmas panelī After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Nospiežot uz loga aizvēršanas ikonu (X), qTox netiks aizvērts pavisam, bet tiks minimizēts sistēmas panelī. Close to tray Aizverot, minimizēties sistēmas panelī After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Pēc minimizēšanas (_) nospiešanas, qTox minimizēsies sistēmas panelī, neviss sistēmas uzdevumjoslā. Minimize to tray Minimizēt sistēmas panelī Autostart Automātiskā palaišana Set where files will be saved. Norādiet, kur tiks saglabāti faili. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Jūs varat iestatīt to katram draugam, noklikšķinot ar peles labo taustiņu uz tā. Autoaccept files Automātiski pieņemt failus Set to 0 to disable Iestatiet 0, lai atspējotu Your status is changed to Away after set period of inactivity. Jūsu statuss tiek mainīts uz ''Nav šeit'' pēc noteiktā bezdarbības perioda. Auto away after (0 to disable): Automātiski iestatīt ''Nav šeit'' pēc (0, lai atspējotu): Show contacts' status changes Rādīt kontaktpersonu statusa izmaiņas Start qTox on operating system startup (current profile). Startēt qTox, kad ielādējas operētājsistēma (pašreizējo profilu). Default directory to save files: Standarta mape failu saglabāšanai: Check for updates Pārbaudīt, vai ir pieejami atjauninājumi Spell checking Pareizrakstības pārbaude Max autoaccept file size (0 to disable): Faila automātiskās saņemšanas maksimālais lielums (0, lai atspējotu): MB MB GenericChatForm Send message Nosūtīt ziņu Smileys Smaidiņi Send file(s) Nosūtīt failu(s) Send a screenshot Nosūtīt ekrānuzņēmumu Save chat log Saglabāt tērzēšanas žurnālu Clear displayed messages Notīrīt redzamos ziņojumus Cleared Notīrīts Quote selected text Citēt izvēlēto tekstu Copy link address Kopēt saites adresi Confirmation Apstiprinājums You are sure that you want to clear all displayed messages? Jūs esat pārliecināts, ka vēlaties dzēst visus redzamos ziņojumus? Search in text Meklēt tekstā Go to current date Load chat history... Ielādēt tērzēšanas vēsturi ... Export to file Eksportēt failā GenericNetCamView Tox video Tox video konference Show Messages Rādīt ziņojumus Hide Messages Slēpt ziņojumus Full Screen Pilnekrāna režīms Toggle video preview Pārslēgt video priekšskatījumu Mute audio Izslēgt skaņu Mute microphone Izslēgt mikrofonu End video call Beigt videozvanu Exit full screen Iziet no pilnekrāna režīma GroupChatForm %1 has set the title to %2 %1 mainīja nosaukumu uz %2 %1 has joined the group %1 pievienojās grupai %1 is now known as %2 %1 tagad ir zināms kā %2] %1 has left the group %1 pameta grupu %n user(s) in chat Number of users in chat mute izslēgt skaņu unmute ieslēgt skaņu GroupInviteForm Groups Grupas Create new group Izveidot jaunu grupu Group invites Grupas uzaicinājums GroupInviteWidget Invited by %1 on %2 at %3. Uzaicinājis %1 ap %2 vietnē %3. Join Pievienoties Decline Atteikties GroupWidget Set title... Uzstādīt nosaukumu ... Open chat in new window Atvērt tērzēšanu jaunā logā Remove chat from this window Noņemt tērzēšanu no šī loga Quit group Menu to quit a groupchat Iziet no grupas %n user(s) in chat Number of users in chat New Message Jauns ziņojums Online Tiešsaistē IdentitySettings Public Information Publiskā informācija Tox ID Tox-ID informācija This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Šis rakstzīmju kopums norāda citiem Tox klientiem, kā sazināties ar Jums. Izsūtiet to saviem draugiem, lai sazinātos. Your Tox ID (click to copy) Jūsu Tox ID (noklikšķiniet uz tā, lai to nokopētu) Profile Profils Rename profile. tooltip for renaming profile button Pārdēvēt profilu. Go back to the login screen tooltip for logout button Atgriezieties pieteikšanās logā Logout import profile button Iziet Remove password Dzēst paroli Change password Nomainīt paroli This QR code contains your Tox ID. You may share this with your friends as well. Šis QR kods satur Jūsu Tox ID. Ar to Jūs varat dalīties starp saviem draugiem. Save image Saglabāt attēlu Copy image Kopēt attēlu Rename rename profile button Pārdēvēt Delete profile. delete profile button tooltip Dzēst profilu. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Eksportēt Tox profilu failā. Šis profila fails nesatur tērzēšanas vēsturi. Export export profile button Eksportēt Delete delete profile button Dzēst Server Serveris Hide my name from the public list Slēpt manu vārdu no publiskā saraksta Register Reģistrēties Your password Jūsu parole Update Atjaunināt Register on ToxMe Piereģistrēties ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Nosaukums ToxMe pakalpojumam. Optional. Something about you. Or your cat. Tooltip for the Biography text. Nav obligāti. Kaut ko par Jums. :) ... vai par Jūsu kaķi. Optional. Something about you. Or your cat. Tooltip for the Biography field. Nav obligāti. Kaut ko par Jums. :) ... vai par Jūsu kaķi. ToxMe service to register on. ToxMe reģistrēšanās pakalpojums. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ja nav iestatīts, ToxMe ieraksti ir publiski redzami. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Dzēst savu paroli un šifrēšanu no sava profila. Name input Ierakstīt vārdu Name visible to contacts Vārds ir redzams kontaktiem Status message input Ierakstīt statusa ziņojumu Status message visible to contacts Statusa ziņojums redzams kontaktpersonām Your Tox ID Jūsu Tox ID Save QR image as file Saglabājiet QR attēlu kā failu Copy QR image to clipboard Kopējiet QR attēlu uz starpliktuvi ToxMe username to be shown on ToxMe ToxMe lietotājvārds tiks parādīts ToxMe Optional ToxMe biography to be shown on ToxMe Nav obligāti, ToxMe biogrāfija, kas tiks parādīta ToxMe ToxMe service address ToxMe pakalpojuma adrese Visibility on the ToxMe service ToxMe pakalpojuma redzamība Password Parole Update ToxMe entry Atjaunināt ToxMe ierakstu Rename profile. Pārdēvēt profilu. Delete profile. Dzēst profilu. Export profile Eksportēt profilu Remove password from profile Dzēsr paroli no profila Change profile password Mainīt profila paroli My name: Mans vārds: My status: Mans statuss: My username Mans lietotājvārds My biography Mana biogrāfija My profile Mans profils LoadHistoryDialog Load History Dialog Ielādēt vēstures dialogu Load history from to (about 100 messages are loaded) Select Date Dialog Izvēlieties datuma dialogu Select a date Izvēlieties datumu LoginScreen Username: Lietotājvārds: Password: Parole: Confirm: Apstiprināt: Password strength: %p% Paroles sarežģītība: %p% Create Profile Izveidot profilu If the profile does not have a password, qTox can skip the login screen Ja profilam nav paroles, qTox var izlaist pieteikšanās logu Load automatically Ielādēt automātiski Load Ielādēt Load Profile Ielādēt profilu New Profile Jauns profils Couldn't create a new profile Nevar izveidot jaunu profilu The username must not be empty. Lietotājvārda lauks nedrīkst būt tukšs. The password must be at least 6 characters long. Parolei jābūt ar vismaz 6 rakstzīmēm. The passwords you've entered are different. Please make sure to enter same password twice. Ievadītās paroles ir atšķirīgas. Lūdzu, divreiz ievadiet vienu un to pašu paroli. A profile with this name already exists. Profils ar šādu vārdu jau pastāv. Password protected profiles can't be automatically loaded. Ar paroli aizsargātus profilus nevar automātiski ielādēt. Couldn't load profile Nevar ielādēt profilu There is no selected profile. You may want to create one. Nav izvēlētā profila. Jūs varat izveidot jaunu. Couldn't load this profile Nevar ielādēt šo profilu This profile is already in use. Šis profils jau tiek lietots. Wrong password. Nepareiza parole. Import Importēt Username input field Lietotājvārda ievades lauks Password input field, you can leave it empty (no password), or type at least 6 characters Paroles ievades lauku var atstāt tukšu (bez paroles), vai ierakstiet vismaz 6 rakstzīmes Password confirmation field Paroles apstiprinājuma lauks Create a new profile button Izveidojiet jaunu profila pogu Profile list Profila saraksts List of profiles Profilu saraksts Password input Paroles ievade Load automatically checkbox Automātiski ielādēt izvēles rūtiņu Import profile Importēt profilu Load selected profile button Ielādēt izvēlēto profila pogu New profile creation page Jauna profila izveidošanas lapa Loading existing profile page Esošo profilu ielādes lapa MainWindow Your name Jūsu vārds Your status Jūsu statuss ... Ausgelassen ... Add friends Pievienot draugus Create a group chat Izveidot grupas tērzēšanu View completed file transfers Skatīt pilnībā pārsūtītos failus Change your settings Mainīt iestatījumus Close Aizvērt Open profile Atvērt profilu Open profile page when clicked Skatīt profilu uzklikšķinot Status message input Statusa ziņojumu ievadīšana Set your status message that will be shown to others Iestatiet statusa ziņojumu, kas tiks rādīts citiem Status Statuss Set availability status Iestatiet pieejamības statusu Contact search Kontaktu meklēšana Contact search input for known friends Zināmo draugu kontaktu meklēšanas lauks Sorting and visibility Šķirošana un redzamība Set friends sorting and visibility Iestatiet draugu šķirošanu un redzamību Open Add friends page Atvērt draugu pievienošanas lapu Groupchat Grupas tērzēšana Open groupchat management page Atvērt grupas tērzēšanas iestatījumu lapu File transfers history Failu pārsūtīšanas vēsture Open File transfers history Atvērt failu pārsūtīšanas vēsturi Settings Iestatījumi Open Settings Atvērt iestatījumus Nexus View OS X Menu bar Skatīt Window OS X Menu bar Logs Minimize OS X Menu bar Minimizēt Bring All to Front OS X Menu bar Novietot visu priekšā Exit Fullscreen Iziet no pilnekrāna režīma Enter Fullscreen Aktivizēt pilnekrāna režīmu NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED Ir ieslēgts ''Caps Lock'' PrivacyForm Privacy Konfidencialitāte Confirmation Apstiprinājums Do you want to permanently delete all chat history? Vai Jūs vēlaties neatgriezeniski dzēst visu tērzēšanas vēsturi? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Jūsu draugi varēs redzēt, kad rakstāt. Send typing notifications Sūtīt informāciju par ziņojuma sastādīšanu Keep chat history Saglabāt tērzēšanas vēsturi NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam Jūsu Tox ID kodā ietilpst AntiSpam daļa. Ja Jūs bieži saņemat kļūdainus draudzības pieprasījumus, Jūs varat mainīt AntiSpam daļu. Lietotāji vairs nespēs pievienot Jūs ar Jūsu veco ID, bet Jūsu pašreizējie draugi tiks saglabāti . NoSpam AntiSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. AntiSpam ir daļa no Jūsu ID, kuru varat mainīt pēc vēlēšanās. Ja saņemat surogātpastu ar draudzības pieprasījumiem, nomainiet AntiSpam daļu. Generate random NoSpam Ģenerēt nejaušu AntiSpam vērtību Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Čata vēstures saglabāšana atrodas izstrādes procesā. Iespējama saglabāšanas formāta izmaiņas, kas var novest pie datu zaudēšanas. Privacy Konfidencialitātes politika BlackList Melnais saraksts Filter group message by group member's public key. Put public key here, one per line. Filtrēt grupu ziņojumus, izmantojot publisko atslēgu. Norādiet publiskās atslēgas, pa vienai katrā rindā. Profile Failed to derive key from password, the profile won't use the new password. Neizdevās iegūt atslēgu no paroles, profils neizmantos jauno paroli. Couldn't change password on the database, it might be corrupted or use the old password. Nesanāca nomainīt paroli datu bāzē, tā varētu būt bojāta, vai tiek izmantota vecā parole. Toxing on qTox Tērzēt qTox ProfileForm Choose a profile picture Izvēlieties profila attēlu Error Kļūda Rename "%1" renaming a profile Pārdēvēt ''%1'' Unable to open this file. Nevar atvērt šo failu. Current profile: Pašreizējais profils: Remove Dzēst Unable to read this image. Nevar nolasīt attēlu. The supplied image is too large. Please use another image. Izvēlētais attēls pārāk liels. Lūdzu, izvēlieties citu attēlu. Couldn't rename the profile to "%1" Nevarēja pārdēvēt profilu uz "%1" Location not writable Title of permissions popup Šajā mapē nevar veikt ierakstu You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Jums nav atļaujas veikt ierakstu šajā mapē. Izvēlieties citu mapi, vai atceliet saglabāšanu. Failed to copy file Neizdevās nokopēt failu The file you chose could not be written to. Diemžēl nav iespējams ierakstīt izvēlētajā failā. Really delete profile? deletion confirmation title Vai tiešām dzēst profilu? Nothing to remove Nav ko dzēst Your profile does not have a password! Jūsu profilam nav paroles! Really delete password? deletion confirmation title Vai tiešām dzēst paroli? Please enter a new password. Lūdzu, ievadiet jauno paroli. Are you sure you want to delete this profile? deletion confirmation text Vai esat pārliecināts, ka vēlaties izdzēst izvēlēto profilu? Save save qr image Saglabāt Save QrCode (*.png) save dialog filter Saglabāt QR kodu (*.png) Files could not be deleted! deletion failed title Failus nevar izdzēst! Register (processing) Reģistrācija (procesā) Update (processing) Atjaunina (procesā) Done! Gatavs! Account %1@%2 updated successfully Konts %1@%2 veiksmīgi atjaunināts Successfully added %1@%2 to the database. Save your password %1@%2 veiksmīgi pievienots datu bāzei. Saglabājiet savu paroli Toxme error Pievienošanās kļūda Register Reģistrēties Update Atjaunināt Change password button text Nomainīt paroli Set profile password button text Iestatiet profila paroli Current profile location: %1 Pašreizējā profila atrašanās vieta: %1 Couldn't change password Nesanāca nomainīt paroli This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Šis rakstzīmju kopums norāda citiem Tox klientiem, kā sazināties ar Jums. Izsūtiet to saviem draugiem, lai sazinātos. ID kods ietver sevī AntiSpam koda daļu (zilā krāsā) un kontrolsummu (pelēkā krāsā). Empty path is unavaliable Norāde bez galamērķa nav pieļaujama Failed to rename Neizdevās pārdēvēt Profile already exists Profils jau pastāv A profile named "%1" already exists. Profils ar nosaukumu ''%1'' jau pastāv. Empty name Tukšs nosaukums Empty name is unavaliable Tukšs nosaukums nav pieļaujams Empty path Norāde bez galamērķa Couldn't change password on the database, it might be corrupted or use the old password. Nesanāca nomainīt paroli datu bāzē, tā varētu būt bojāta, vai tiek izmantota vecā parole. Export profile Eksportēt profilu Tox save file (*.tox) save dialog filter Saglabātais Tox fails (* .tox) The following files could not be deleted: deletion failed text part 1 Nevarēja izdzēst sekojošus failus: Please manually remove them. deletion failed text part 2 Lūdzu, izdzēsiet tos manuāli. Are you sure you want to delete your password? deletion confirmation text Vai tiešām vēlaties dzēst savu paroli? Images (%1) filetype filter Attēli (%1) ProfileImporter Import profile import dialog title Importēt profilu Tox save file (*.tox) import dialog filter Saglabātais Tox fails (* .tox) Ignoring non-Tox file popup title Ignorēt ne-Tox failus Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Brīdinājums: Jūs neesat izvēlējies Tox saglabāšanas failu; tas tiks ignorēts. Profile already exists import confirm title Profils jau pastāv A profile named "%1" already exists. Do you want to erase it? import confirm text Profils ar nosaukumu ''%1'' jau pastāv. Vai vēlaties to dzēst? File doesn't exist Fails nepastāv Profile doesn't exist Profils nepastāv Profile imported Profils importēts %1.tox was successfully imported %1.tox tika veiksmīgi importēts QApplication Ok Labi Cancel Atcelt Yes No LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout No labās puses uz kreiso QMessageBox Couldn't add friend Neizdevās pievienot draugu %1 is not a valid Toxme address. %1 nav derīga ToxMe adrese. You can't add yourself as a friend! When trying to add your own Tox ID as friend Jūs nevarat pievienot sevi kā draugu! QObject Tox URI to parse Tox URI apstrādei Starts new instance and loads specified profile. Uzsāk jaunu instanci un ielādē norādīto profilu. profile profils Default Noklusējuma Blue Zils Olive Olīvu Red Sarkans Violet Violeta Incoming call... Ienākošais zvans ... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Sveiki, šeit %1! Vai pievienosiet mani savos draugos? None No camera device set Nav Desktop Desktop as a camera input for screen sharing Darbvirsma Server doesn't support Toxme Serveris neatbalsta ToxMe You're making too many requests. Wait an hour and try again Jūs esat veikuši pārāk daudz pieprasījumu. Pagaidiet vienu stundu un mēģiniet vēlreiz This name is already in use Šis lietotāja vārds jau tiek lietots This Tox ID is already registered under another name Šis Tox ID jau ir reģistrēts zem cita lietotāja vārda Please don't use a space in your name Lūdzu neizmantojiet tukšzīmi Jūsu lietotāja vārdā Password incorrect Nepareiza parole You can't use this name Jūs nevarat izmantot šo lietotāja vārdu Name not found Lietotāja vārds nav atrasts Tox ID not sent Tox ID nav nosūtīts That user does not exist Šāds lietotājs neeksistē Error Kļūda qTox couldn't open your chat logs, they will be disabled. qTox nevar atvērt tērzēšanas vēsturi, tā tiks atslēgta. Problem with HTTPS connection Problēma ar HTTPS savienojumu Internal ToxMe error Iekšēja ToxMe kļūda Reformatting text in progress.. Notiek teksta atkārtota formatēšana ... Starts new instance and opens the login screen. Uzsāk jaunu instanci un atver pieteikšanās logu. Dark Tumšs Dark blue Tumši zils Dark olive Tumši olīvu Dark red Tumši sarkans Dark violet Tumši violets Failed to load profile automatically. online contact status tiešsaistē away contact status busy contact status aizņemts offline contact status blocked contact status bloķēts RemoveFriendDialog Remove friend Dzēst draugu Also remove chat history Arī noņemt tērzēšanas vēsturi Remove Dzēst Are you sure you want to remove %1 from your contacts list? Vai Jūs tiešām vēlaties dzēst %1 no kontaktpersonu saraksta? Remove all chat history with the friend if set Dzēš visu tērzēšanas vēsturi ar draugu, ja tiek iestatīts ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Noklikšķiniet un velciet, lai atlasītu reģionu. Nospiediet %1, lai paslēptu/rādītu qTox logu, vai %2, lai atceltu. Space [Space] key on the keyboard Tukšzīme Escape [Escape] key on the keyboard Atcelt Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Nospiediet %1, lai nosūtītu atlases ekrānuzņēmumu, %2, lai paslēptu/rādītu qTox logu, vai %3, lai atceltu. Enter [Enter] key on the keyboard Ievadīt SearchForm The text could not be found. Tekstu nevar atrast. Start Sākt SearchSettingsForm Form Forma Start search: Sākt meklēšanu: from the end no beigām from the beginning no sākuma after date pēc datuma before date līdz datumam 00.00.0000 00.00.0000 Case sensitive Ieskaitot reģistru Whole words only Tikai veseli vārdi Use regular expressions Lietot regulāras izteiksmes SetPasswordDialog Set your password Uzstādiet savu paroli Confirm: Apstiprināt: Password: Parole: Password strength: %p% Paroles sarežģītība: %p% The password is too short Parole ir pārāk īsa The password doesn't match. Paroles nesakrīt. Confirm password Apstipriniet paroli Confirm password input Apstipriniet paroles ievadi Password input Paroles ievade Password input field, minimum 6 characters long Paroles ievades lauks, ar vismaz 6 rakstzīmēm Settings Circle #%1 Uz sarakstu #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Pievienot draugu Do you want to add %1 as a friend? Vai vēlaties pievienot %1 kā draugu? User ID: Lietotāja ID: Friend request message: Draudzības pieprasījuma ziņojums: Send Send a friend request Sūtīt Cancel Don't send a friend request Atcelt UserInterfaceForm None Nav User Interface Lietotāja Interfeiss UserInterfaceSettings Chat Tērzēšana Base font: Pamatfonts: px px Size: Lielumu: New text styling preference may not load until qTox restarts. Teksta jaunais stils tiks pielietots pēc qTox atkārtotas palaišanas. Text Style format: Teksta stila formāts: Select text styling preference. Izvēlieties teksta vēlamo stilu. Plaintext Vienkāršs teksts Show formatting characters Rādīt formatēšanas zīmes Don't show formatting characters Nerādīt formatēšanas zīmes New message Jauna ziņa Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Atvērt qTox logu, kad Jūs esat saņēmis jaunu ziņu, ja līdz šim tā nav bijusi atvērta. Open window Atvērt logu Contact list Kontaktu saraksts If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme Style: Theme color: Timestamp format: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Paziņo tikai par jaunām grupas tērzēšanas ziņām, ja tas ir minēts. Group chats only notify when mentioned Play sound Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Tiešsaistē Away Button to set your status to 'Away' Nav šeit Busy Button to set your status to 'Busy' Aizņemts toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File Edit Profile Change Status Log out Edit Logout Tray action menu to logout user Iziet Exit Tray action menu to exit tox Filter... Contacts Add Contact... Next Conversation Previous Conversation Executable file popup title You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Couldn't request friendship Status Statuss Your name Jūsu vārds Message failed to send Create new group... Add new circle... %n New Friend Request(s) %n New Group Invite(s) By Name By Activity All Online Tiešsaistē Offline Ausgelassen Bezsaistē Friends Groups Grupas Search Contacts Groupchat #%1 Show Tray action menu to show qTox window Add friend title of the window Group invites title of the window Grupas uzaicinājums File transfers title of the window Settings title of the window Iestatījumi My profile title of the window Mans profils Failed to send file "%1" Neizdevās nosūtīt failu ''%1'' File sent sent you a friend request. invites you to join a group. qTox/translations/mk.ts000066400000000000000000003626001415623743500155320ustar00rootroot00000000000000 AVForm Audio/Video Аудио/Видео Default resolution Стандардна резолуција Disabled Оневозможено Select region Обележи регион Screen %1 Екран %1 Audio Settings Звучни поставки Gain Засилување Playback device Уред за репродукција Use slider to set volume of your speakers. Користи го лизгачот за поставување на гласноста на звучниците. Capture device Уред за превземање Volume Гласност Video Settings Видео Поставки Video device Видео уред Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Поставете ја резолуцијата на вашата камера. Колку е повисока вредноста, толку ќе биде поквалитетно видеото кое го добиваат вашите пријатели. Имајте в предвид, дека повисокиот видео квалитет бара и подобра интернет врска. Понекогаш врската може да не е доволно добра за праќање поквалитетно видео, што може да доведе до проблеми со видео повиците. Resolution Резолуција Rescan devices Повторно скенирај ги уредите Test Sound Пробен Звук Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Го овозможува експерименталниот аудио алгоритам со можности за отстранување на ехо, бара рестартирање на qTox. Enable experimental audio backend Вклучи екпериментална аудио поддршка Audio quality Аудио квалитет Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Емитуван аудио квалитет. Намалете ја оваа поставка доколку вашиот интернет е спор или ако сакате да ја намалите потрошувачката на интернет. High (64 kbps) Високо (64 kbps) Medium (32 kbps) Средно (32 kbps) Low (16 kbps) Ниско (16 kbps) Very low (8 kbps) Многу ниско (8 kbps) Threshold Праг AboutForm About За Original author: %1 Оригинален автор: %1 You are using qTox version %1. Вие користите qTox со верзија %1. Commit hash: %1 toxcore version: %1 toxcore верзија: %1 Qt version: %1 Qt верзија: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Листа со сите познати проблеми може да се најде на нашиот %1 на Github. Ако најдете грешка или безбедносна ранливост во qTox, ве молиме пријавете ја согласно со упатствата во нашата вики статија %2. Click here to report a bug. Стисни тука за да пријавиш грешка. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Види целосна листа на %1 на Github bug-tracker Replaces `%1` in the `A list of all known…` следач на грешки Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Пишување на корисни извештаи за грешки contributors Replaces `%1` in `See a full list of…` соработници AboutFriendForm Dialog Дијалог username корисничко име status message статусна порака Used aliases: Користени прекари: HISTORY OF ALIASES ИСТОРИЈА НА ПРЕКАРИ Automatically accept files from contact if set Ако е поставено, автоматски прифаќај датотеки од контакт Auto accept files Автоматски прифати датотеки Default directory to save files: Стандардна папка за зачувување датотеки: Auto accept for this contact is disabled Автоматски прифати за овој контакт ако е оневозможено Auto accept call: Автоматски прифати повик: Manual Рачно Audio Аудио Audio + Video Аудио + Видео Automatically accept group chat invitations from this contact if set. Автоматски прифати покани за групен разговор од овој контакт ако е поставено. Auto accept group invites Автоматски прифати покани за групи Remove history (operation can not be undone!) Отстрани историја (операцијата не може да се врати назад!) Notes Белешки Input field for notes about the contact Поле за внесување на белешки за контактот You can save comment about this contact here. Тука може да зачувате коментар за овој контакт. History removed Историјата е отстранета Choose an auto accept directory popup title Избери папка за автоматско прифаќање <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Јавен клуч (не ToxID): Confirmation Потврда Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Верзија License Лиценца Authors Автори Known Issues Познати Проблеми Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Додади Пријатели Invalid Tox ID format Невалиден Tox ID формат Send friend request Испрати покана за пријателство Add a friend Додади пријател Friend requests Покани за пријателство Accept Прифати Reject Отфрли Couldn't add friend Не може да се додаде пријател Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, или 76 хексадецимални знаци или име@пример.com Type in Tox ID of your friend Напиши го Tox ID-то на твојот пријател Friend request message Порака за покана за пријателство Type message to send with the friend request or leave empty to send a default message Напиши порака, пратена со барањето за пријателство, или остави празно за стандардната порака %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID е невалиден или не постои You can't add yourself as a friend! When trying to add your own Tox ID as friend Не можеш да се додадеш себе си за пријател! Open contact list Отвори листа на контакти Couldn't open file Не може да се отвори датотеката Couldn't open the contact file Error message when trying to open a contact list file to import Не може да се отвори датотеката за контакти Invalid file Невалидна датотека We couldn't find any contacts to import in this file! Не можевме да најдеме контакти за увезување во оваа датотека! Tox ID Tox ID of the person you're sending a friend request to either 76 hexadecimal characters or name@example.com Tox ID format description или 76 хексадецимални знаци или име@пример.com Message The message you send in friend requests Порака Open Button to choose a file with a list of contacts to import Отвори Send friend requests Прати покани за пријателство %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 тука! Пиши ми на Tox? Import a list of contacts, one Tox ID per line Увези листа на контакти, едно Tox ID по линија Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) %n контакт(и) подготвен(и) за увезување , стисни прати за потврда %n контакти подготвени за увезување, стисни прати за потврда Import contacts Увези контакти AdvancedForm Advanced Напредно Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Освен ако %1 знаете што правите, ве молиме да %2 менувате ништо тука. Промените направени тука може да водат до проблеми со qTox, па дури и загуба на вашите податоци, на пр. историјата. really навистина not не IMPORTANT NOTE ВАЖНА ЗАБЕЛЕШКА Reset settings Ресетирај поставки All settings will be reset to default. Are you sure? Сите поставки ќе бидат ресетирани на стандардните. Дали сте сигурни? Yes Да No Не Call active popup title Активен повик You can't disconnect while a call is active! popup text Не можете да се исклучите додека повикот е активен! Save File Зачувај Датотека Logs (*.log) Дневници (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Зачувај поставки во работната папка наместо во обичната конфигурациска папка Make Tox portable Направи го Tox пренослив Reset to default settings Ресетирај на стандардни поставки Portable Преносно Connection Settings Поставки за конектирање Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Овозможи IPv6 (препорачано) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Оневозможување на ова дозволува, на пример, токсирање преку Tor. Сепак, додава товар на Tox мрежата, па отштиклирајте го само по потреба. Enable UDP (recommended) Text on checkbox to disable UDP Овозможи UDP (препорачано) Proxy type: Тип на прокси: Address: Text on proxy addr label Адреса: Port: Text on proxy port label Порта: None Празно SOCKS5 HTTP Reconnect reconnect button Повторно поврзување Debug Дебагирање Export Debug Log Извези дебагирачки дневник Copy Debug Log Копирај дневник за дебагирање Enable LAN discovery ChatForm Send a file Прати датотека qTox wasn't able to open %1 qTox не можеше да се отвори %1 Unable to open Не може да се отвори Bad idea Лоша идеја %1 calling %1 ѕвони Calling %1 Ѕвонам на %1 Failed to open temporary file Temporary file for screenshot Не успеа да се отвори привремената датотека qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox не можеше да го зачува кадарот Call with %1 ended. %2 Повикот со %1 заврши. %2 Call duration: Времетраење на повикот: %1 is typing %1 пишува Copy Копирај You're trying to send a sequential file, which is not going to work! Пробувате да испратите секвентна датотека, што нема да работи! %1 is now %2 e.g. "Dubslow is now online" %1 е сега %2 Call with %1 ended unexpectedly. %2 Повикот со %1 прекина неочекувано. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Не може да се започне аудио повик Start audio call Започни аудио повик End audio call Заврши аудио повик Cancel audio call Откажи аудио повик Accept audio call Прифати аудио повик Can't start video call Не може да се започне видео повик Start video call Започни видео повик End video call Заврши видео повик Cancel video call Откажи видео повик Accept video call Прифати видео повик Sound can be disabled only during a call Звукот може да се оневозможи само за време на повик Unmute call Вклучи го звукот на повикот Mute call Исклучи го звукот на повикот Microphone can be muted only during a call Микрофонот може да биде занемен само за време на повик Unmute microphone Вклучи микрофон Mute microphone Исклучи микрофон ChatLog Copy Копирај Select all Одбери се pending во тек ChatTextEdit Type your message here... Напиши ја твојата порака тука... CircleWidget Rename circle Menu for renaming a circle Преименувај круг Remove circle Menu for removing a circle Отстрани круг Open all in new window Отвори ги сите во нов прозорец Core /me offers friendship, "%1" Invalid Tox ID Error while sending friendship request Невалидна Tox ID You need to write a message with your request Error while sending friendship request Треба да напишете порака со вашето барање Your message is too long! Error while sending friendship request Вашата порака е предолга! Friend is already added Error while sending friendship request Пријателот е веќе додаден Groupchat %1 DesktopNotify New message Нова порака Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen Форма 10Mb Ausgelassen 0kb/s Ausgelassen ETA:10:10 Ausgelassen Filename Ausgelassen Име на датотека Waiting to send... file transfer widget Чека да се прати... Accept to receive this file file transfer widget Прифати за да ја примиш оваа датотека Location not writable Title of permissions popup Не може да се пишува на таа локација You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Немате права за пишување на таа локација. Избере друга, или откажете го дијалогот за зачувување. Resuming... file transfer widget Се продолжува… Cancel transfer Откажи трансфер Pause transfer Paused file transfer widget Паузирано Open file Open file directory Отвори ја папката на датотеката Resume transfer Продолжи трансфер Accept transfer Прифати трансфер Save a file Title of the file saving dialog Зачувај ја датотеката Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Префрлени датотеки Downloads Uploads Прикачувања FriendListWidget Today Денес Yesterday Вчера Last 7 days Последните 7 дена This month Овој месец Older than 6 Months Постаро од 6 месеци Never Никогаш FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Барање за пријателство Someone wants to make friends with you Некој сака да ве додаде како пријател User ID: Кориснички ID: Friend request message: Порака за побарување пријателство: Accept Accept a friend request Прифати Reject Reject a friend request Одбиј FriendWidget Invite to group Menu to invite a friend to a groupchat Покани во група Move to circle... Menu to move a friend into a different circle Премести во круг... To new circle Во нов круг Remove from circle '%1' Отстрани од круг '%1' Move to circle "%1" Премести во круг "%1" Open chat in new window Отвори го разговорот во нов прозорец Remove chat from this window Отстрани го разговорот од овој прозорец To new group Во нова група Invite to group '%1' Покани во група '%1' Set alias... Auto accept files from this friend context menu entry Автоматски прифаќај ги датотеките од овој пријател Remove friend Menu to remove the friend from our friendlist Отстрани пријател Show details Покажи ги деталите Choose an auto accept directory popup title Избери папка за автоматско прифаќање New message Нова порака Online Away Отсутен Busy Зафатен/а Offline Ausgelassen GeneralForm General Choose an auto accept directory popup title Изберете папка за автоматско прифаќање GeneralSettings General Settings Општи поставки The translation may not load until qTox restarts. Преводот може да не биде вчитан се до рестартирањето на qTox. Language: Јазик: Show system tray icon Прикажи икона во системскиот панел Enable light tray icon. toolTip for light icon setting Прикажи светла икона во панелот. Light icon Светла икона qTox will start minimized in tray. toolTip for Start in tray setting qTox ќе се стартува минимизаран во панелот. Start in tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting При притискањето на копчето за затворање прозорец (X) qTox ќе се минимизира во панелот, наместо навистина да се затвори. Close to tray Затвори во панелот After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting При притискањето на копчето за минимизација на прозорец (_) qTox ќе се минимизира во панелот, наместо да остане во системската линија на активни апликации. Minimize to tray Минимизирај во панел Autostart Автостарт Set where files will be saved. Поставете каде да се зачувуваат датотеките. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Ова може да се постави одделно за секој пријател со кликнување со десниот тастер врз нив. Autoaccept files Автоматско прифаќање датотеки Set to 0 to disable Поставете на 0 да оневозможите Your status is changed to Away after set period of inactivity. Вашиот статус се менува во Отсустен по поставениот период на неактивност. Auto away after (0 to disable): Автоматска отсутност по (0 за оневозможување): Show contacts' status changes Прикажи ги промените на статусот на контактите Start qTox on operating system startup (current profile). Стартување на qTox при стартување на оперативниот систем (тековниот профил). Default directory to save files: Подразбирана папка во која треба да се зачуваат датотеки: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Прати порака Smileys Емотикони Send file(s) Испрати датотека(и) Send a screenshot Испрати снимка од екранот Save chat log Зачувај го дневникот на разговори Clear displayed messages Избриши ги прикажаните пораки Cleared Избришано Quote selected text Цитирај го селектираниот текст Copy link address Копирај ја адресата на врската Confirmation Потврда You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Вчитај историја на разговор... Export to file Извези во датотека GenericNetCamView Tox video Tox видео Show Messages Прикажи Пораки Hide Messages Скриј Пораки Full Screen Toggle video preview Mute audio Mute microphone Исклучи микрофон End video call Заврши видео повик Exit full screen GroupChatForm %1 has set the title to %2 %1 го постави насловот на %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Групи Create new group Создади нова група Group invites Покани за групи GroupInviteWidget Invited by %1 on %2 at %3. Поканет(а) од %1 на %2 на %3. Join Придружи се Decline Одбиј GroupWidget Set title... Постави наслов… Open chat in new window Отвори разговор во нов прозорец Remove chat from this window Отстрани разговор од овој прозорец Quit group Menu to quit a groupchat Напушти група %n user(s) in chat Number of users in chat New Message Online Вклучен(а) IdentitySettings Public Information Јавни Информации Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Овој куп на знаци им кажуваат на другите Tox clients како да ве контактираат. Споделете ги со вашите пријатели за да комуницирате. Your Tox ID (click to copy) Вашата Tox ID (стиснете да ископирате) Profile Профил Rename profile. tooltip for renaming profile button Преименувај профил. Go back to the login screen tooltip for logout button Врати се назад на екранот за најава Logout import profile button Одјава Remove password Отстрани лозинка Change password Промени лозинка This QR code contains your Tox ID. You may share this with your friends as well. Овој QR код ја содржи вашата Tox ID. Можете исто така да го споделите со вашите пријатели. Save image Зачувај слика Copy image Ископирај слика Rename rename profile button Преименувај Delete profile. delete profile button tooltip Избриши профил. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Ви дозволува да го извезете вашиот Tox профил во датотека. Профилот не ја содржи вашата историја. Export export profile button Извези Delete delete profile button Избриши Server Сервер Hide my name from the public list Скриј го моето име од јавната листа Register Регистрирај се Your password Вашата лозинка Update Ажурирај Register on ToxMe Регистрирај ме на ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Име за услугата ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Изборно. Нешто за вас. Или за вашата мачка. Optional. Something about you. Or your cat. Tooltip for the Biography field. Изборно. Нешто за вас. Или за вашата мачка. ToxMe service to register on. ToxMe услуга за регистрација. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ако не е поставено, ToxMe записите ќе бидат јавно видливи. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Отстранете ја вашата лозинка и енкрипција од вашиот профил. Name input Внеси име Name visible to contacts Име видливо за контактите Status message input Внеси статус порака Status message visible to contacts Статус порака видлива за контактите Your Tox ID Вашата Tox ID Save QR image as file Зачувај QR слика како датотека Copy QR image to clipboard Ископирај QR слика во меморија ToxMe username to be shown on ToxMe ToxMe корисничко име да биде прикажано на ToxMe Optional ToxMe biography to be shown on ToxMe Изборна ToxMe биографија да биде прикажана на ToxMe ToxMe service address ToxMe адреса на услуга Visibility on the ToxMe service Видливост на ToxMe услугата Password Лозинка Update ToxMe entry Ажурирај ToxMe запис Rename profile. Преименувај профил. Delete profile. Избриши профил. Export profile Извези профил Remove password from profile Отстрани лозинка од профил Change profile password Смени лозинка на профил My name: Моето име: My status: Мојот статус: My username Моето корисничко име My biography Мојата биографија My profile Мојот профил LoadHistoryDialog Load History Dialog Дијалог Вчитај Историја Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Корисничко име: Password: Лозинка: Confirm: Потврди: Password strength: %p% Јачина на лозинка: %p% Create Profile Создади Профил If the profile does not have a password, qTox can skip the login screen Ако профилот нема лозинка, qTox може да го прескокне екранот за најава Load automatically Вчитај автоматски Load Вчитај Load Profile Вчитај Профил New Profile Нов Профил Couldn't create a new profile Не може да се создаде нов профил The username must not be empty. Корисничкото име не смее да биде празно. The password must be at least 6 characters long. Лозинката мора да содржи најмалку 6 знаци. The passwords you've entered are different. Please make sure to enter same password twice. Лозинките што ги внесовте се различни. Осигурете се дека ја внесувате истата лозинка два пати. A profile with this name already exists. Профил со ова име веќе постои. Password protected profiles can't be automatically loaded. Профилите заштитени со лозинка не може да се вчитуваат автоматски. Couldn't load profile Не може да се вчита профилот There is no selected profile. You may want to create one. Не е избран ниеден профил. Можете да креирате нов. Couldn't load this profile Не може да се вчита овој профил This profile is already in use. Овој профил е веќе во употреба. Wrong password. Погрешна лозинка. Import Username input field Поле за внес на корисничко име Password input field, you can leave it empty (no password), or type at least 6 characters Полето за внес на лозинка, можете да го оставите празно (без лозинка), или да внесете најмалку 6 карактери Password confirmation field Поле за потврда на лозинка Create a new profile button Копче за креирање нов профил Profile list Листа профили List of profiles Листа профили Password input Load automatically checkbox Потврда за автоматско вчитување Import profile Увоз на профил Load selected profile button Копче за вчитување на избраниот профил New profile creation page Страница за креирање нов профил Loading existing profile page Страница за вчитување постоен профил MainWindow Your name Ваше име Your status Ваш статус ... Ausgelassen Add friends Додади пријатели Create a group chat Креирај групен разговор View completed file transfers Прикажи ги комлетираните трансфери на датотеки Change your settings Измена на вашите поставки Close Затвори Open profile Отвори профил Open profile page when clicked Отворија страницата на профилот по кликнувањето Status message input Set your status message that will be shown to others Поставете порака за вашиот статус која ќе биде прикажувана на другите Status Set availability status Поставете статус на достапност Contact search Пребарување контакти Contact search input for known friends Барање контакти помеѓу пријателите Sorting and visibility Сортирање и видливост Set friends sorting and visibility Поставете сортирање и видливост на пријатели Open Add friends page Отвори страница Додај пријатели Groupchat Групен чет Open groupchat management page Отвори ја страница за управување со групен чет File transfers history Историја на преземења Open File transfers history Отвори Историја на преземања Settings Поставки Open Settings Отвори Поставки Nexus View OS X Menu bar Window OS X Menu bar Прозорец Minimize OS X Menu bar Минимизирај Bring All to Front OS X Menu bar Донесе Се напред Exit Fullscreen Излез од цел екран Enter Fullscreen Отвори на цел екран NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Потврда Do you want to permanently delete all chat history? Дали сакате трајно да ја избришете историјата на четување? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Вашите пријатели ќе можат да видат кога куцате. Send typing notifications Испраќај известувања за куцање Keep chat history Чувај ја историјата на четување NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam е дел од вашиот Tox ID. Ако ве спамираат со барања за пријателство, треба да го промените вашиот NoSpam. Другите нема да бидат во можност да ве додадат под старото ID, а вашите пријатели ќе останат во контактите. NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam е дел од вашето ID и можете да го промените по своја желба. Ако ве спамираат со барања за пријателство, променете го NoSpam. Generate random NoSpam Создадете NoSpam по случаен избор Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Чувањето на историјата на четување е сеуште во развој. Можни се промени во форматот на снимање, што може да доведе до губење на податоци. Privacy BlackList Црна листа Filter group message by group member's public key. Put public key here, one per line. Филтрирање на групните пораки по јавни клучеви на членовите на групата. Внесете го овде јавниот клуч, еден по линија. Profile Failed to derive key from password, the profile won't use the new password. Неуспешно произлегувањена на клуч од лозинката, профилот нема да ја користи новата лозинка. Couldn't change password on the database, it might be corrupted or use the old password. Неуспешна промена на лозинката на базата на податоци, може да е оштетена или да користи стара лозинка. Toxing on qTox Токсирање на qTox ProfileForm Choose a profile picture Избери профилна слика Error Rename "%1" renaming a profile Преимени "%1" Unable to open this file. Не може да се отвори оваа датотека. Current profile: Моментален профил: Remove Отстрани Unable to read this image. Не може да биде прочитана оваа слика. The supplied image is too large. Please use another image. Доставената слика е премногу голема. Ве молиме користете друга слика. Couldn't rename the profile to "%1" Профилот не може да биде преименуван во "%1" Location not writable Title of permissions popup Оваа локација не е дозволена за запишување You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Вие немате дозвола за запишување на таа локација. Изберете друга, или затворете го дијалогот за зачувување. Failed to copy file Неуспешно копирање на датотеката The file you chose could not be written to. Не може да се запишува во датотеката која ја избравте. Really delete profile? deletion confirmation title Навистина сакате да го избришете профилот? Nothing to remove Нема ништо за отстранување Your profile does not have a password! Вашиот профил нема лозинка! Really delete password? deletion confirmation title Навистина сакате да ја избришете лозинката? Please enter a new password. Ве молиме внесете нова лозинка. Are you sure you want to delete this profile? deletion confirmation text Дали сте сигурни дека сакате да го избришете овој профил? Save save qr image Зачувување Save QrCode (*.png) save dialog filter Зачувај QrCode (*.png) Files could not be deleted! deletion failed title Датотеките не можат да бидат избришани! Register (processing) Регистрација (во тек) Update (processing) Ажурирање (во тек) Done! Завршено! Account %1@%2 updated successfully Сметка %1@%2 е успешно ажурирана Successfully added %1@%2 to the database. Save your password Сметката %1@%2 е успешно додадена во базбата на податоци. Зачувајте ја вашата лозинка Toxme error Грешка во Toxme Register Регистрирај се Update Change password button text Промени лозинка Set profile password button text Постави лозинка на профилот Current profile location: %1 Моментална локација на профилот: %1 Couldn't change password Неуспешна промена на лозинката This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Овој куп од карактери им кажува на другите Tox клиенти како да контактираат со вас. Споделете го со вашите пријатели за да комуницирате. Ова ID содржи NoSpam код (во плава боја), и сума за проверка (во сива боја). Empty path is unavaliable Празната патека е недостапна Failed to rename Неуспешно преименување Profile already exists Профилот веќе постои A profile named "%1" already exists. Профил со името "%1" веќе постои. Empty name Празно име Empty name is unavaliable Празно име е недозволиво Empty path Празна патека Couldn't change password on the database, it might be corrupted or use the old password. Не може да се промени лозинката на базата на податоци, можеби е оштетена или користи стара лозинка. Export profile Извези профил Tox save file (*.tox) save dialog filter Tox зачувај датотека (*.tox) The following files could not be deleted: deletion failed text part 1 Следниве датотеки не можат да бидат избришани: Please manually remove them. deletion failed text part 2 Ве молиме рачно отстанете ги. Are you sure you want to delete your password? deletion confirmation text Дали сте сигурни дека сакате да ја избришете вашата лозинка? Images (%1) filetype filter Слики (%1) ProfileImporter Import profile import dialog title Увези профил Tox save file (*.tox) import dialog filter Tox зачувај датотека (*.tox) Ignoring non-Tox file popup title Игнорирај не-Tox датотека Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Предупредување: Избравте датотека којашто не е Tox зачувана датотека; се игнорира. Profile already exists import confirm title Профилот веќе постои A profile named "%1" already exists. Do you want to erase it? import confirm text Профил со име "%1" веќе постои. Дали сакате да го избришете? File doesn't exist Датотеката не постои Profile doesn't exist Профилот не постои Profile imported Профилот е увезен %1.tox was successfully imported %1.tox беше успешно увезена QApplication Ok Cancel Откажи Yes Да No LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend Не може да се додаде пријател %1 is not a valid Toxme address. %1 не е валидна Toxme адреса. You can't add yourself as a friend! When trying to add your own Tox ID as friend Не можеш да се додадеш сам себе за пријател! QObject Tox URI to parse Tox URI за парсирање Starts new instance and loads specified profile. Отвора нова инстанца и го вчитува специфицираниот профил. profile профил Default Blue Сина Olive Маслинеста Red Црвена Violet Виолетова Incoming call... Дојдовен повик... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 тука! Напиши ми на Tox? None No camera device set Ниедна Desktop Desktop as a camera input for screen sharing Работна површина Server doesn't support Toxme Серверот не го поддржува Toxme You're making too many requests. Wait an hour and try again Правите премногу барања. Почекајте час и пробајте повторно This name is already in use Ова име веќе се користи This Tox ID is already registered under another name Оваа Tox ID е веќе регистрирана под друго име Please don't use a space in your name Ве молиме не користете празно место во вашето име Password incorrect Неточна лозинка You can't use this name Не може да го користите ова име Name not found Името не е најдено Tox ID not sent Tox ID не е пратена That user does not exist Тој корисник не постои Error Грешка qTox couldn't open your chat logs, they will be disabled. qTox не можеше да ги отвори вашите разговорни дневници, тие ќе бидат оневозможени. Problem with HTTPS connection Проблем со HTTPS врската Internal ToxMe error Внатрешна ToxMe грешка Reformatting text in progress.. Преформатирање на текстот е во тек.. Starts new instance and opens the login screen. Започни нов пример и отвори го екранот за најава. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status вклучен away contact status далеку busy contact status зафатен(а) offline contact status исклучен blocked contact status RemoveFriendDialog Remove friend Отстрани пријател Also remove chat history Исто така отстрани ја чет историјата Remove Отстрани Are you sure you want to remove %1 from your contacts list? Дали сте сигурни дека сакате да го отстаните %1 од вашата листа на контакти? Remove all chat history with the friend if set Отстанете ја целата чет историја со пријателот ако е така поставено ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Кликнете и повлечете за да изберете регион. Притиснете %1 за да го покажете/сокриете qTox прозорецот, или %2 да го откажете. Space [Space] key on the keyboard Место Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Притиснете %1 за да испратите скриншот од селекцијата, %2 за да го сокриете/прикажете qTox прозорецот, или %3 или да исклучите. Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Форма Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Поставете ја вашата лозинка Confirm: Потврдете: Password: Password strength: %p% Јачина на лозинка: %p% The password is too short Лозинката е премногу кратка The password doesn't match. Лозинката не се поклопува. Confirm password Потврди лозинка Confirm password input Потврди лозинка Password input Password input field, minimum 6 characters long Потврдувањето на лозинката беше неуспешно, минимум 6 карактери долго Settings Circle #%1 Круг #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Додај пријател Do you want to add %1 as a friend? Дали сакате да го додадете %1 за пријател? User ID: Корисничко ID: Friend request message: Порака за барање за пријателство: Send Send a friend request Cancel Don't send a friend request Откажи UserInterfaceForm None User Interface Кориснички интерјфејс UserInterfaceSettings Chat Base font: Основен фонт: px Size: Големина: New text styling preference may not load until qTox restarts. Следната поставка за стилизирање на текст може да не биде вчитана додека qTox не биде рестартиран. Text Style format: Select text styling preference. Изберете поставка за стилизирање на текст. Plaintext Обичен текст Show formatting characters Прикажи карактери за форматирање Don't show formatting characters Не ги прекажувај карактерите за форматирање New message Нова порака Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Отвори qTox's прозорец кога добиваш нова порака и ако сеуште нема отворен прозорец Open window Отвори прозорец Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Ако е означено, групните четувања ќе бидат поставени на врвот на листата на пријатели, во спротивно, ќе бидат поставени под листата на пријатели кои се вклучени (онлајн). Place groupchats at top of friend list Постави ги групните четови на врвот на листата на пријатели Your contact list will be shown in compact mode. toolTip for compact layout setting Вашата контакт листа ќе се прикаже во компактен режим. Compact contact list Компактна листа на контакти Multiple windows mode Режим на повеќе прозорци Open each chat in an individual window Отвори го секој чет во индивидуален прозорец Emoticons Емотикони (смајли) Use emoticons Користи емотикони (смајли) Smiley Pack: Text on smiley pack label Смајли пакет: Emoticon size: Големина на емотикон (смајли): px Theme Тема Style: Стил: Theme color: Боја на тема: Timestamp format: Формат на временска ознака: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Ако е овозможено, секој контакт без поставен аватар ќе има генериран аватар базиран на својата Tox ID наместо стандардна слика. Потреба е престартување за да се применат промените. Use identicons instead of empty avatars Користи идентикони наместо празни аватари Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Пушти звук Play sound while Busy Пушти звук кога сум Зафатен Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Вклучен(а) Away Button to set your status to 'Away' Далеку Busy Button to set your status to 'Busy' Зафатен(а) toxcore failed to start, the application will terminate after you close this message. toxcore не се стартуваше, апликацијата ќе престане со работа после затворањето на оваа проака. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore не се стартуваше со вашите прокси поставки. qTox не може да се стартува; ве молиме изменете ги вашите поставки и рестартирајте го. File Edit Profile Уреди профил Change Status Промени статус Log out Одјави се Edit Уреди Logout Tray action menu to logout user Одјавување Exit Tray action menu to exit tox Излез Filter... Contacts Add Contact... Додај контакт... Next Conversation Следен разговор Previous Conversation Претходен разговор Executable file popup title Извршна датотека You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Побаравте qTox да отвори извршна датотека. Извршните датотеки можат да му нанесат штета на вашиот компјутер. Дали сте сигурни дека сакате да ја отворите оваа датотека? Couldn't request friendship Не може да се испрати барање за пријателство Status Your name Ваше име Message failed to send Неуспешно праќање на пораката Create new group... Создај нова група… Add new circle... Додај нов круг... %n New Friend Request(s) %n New Group Invite(s) By Name By Activity По активност All Сите Online Offline Ausgelassen Friends Пријатели Groups Групи Search Contacts Пребарај контакти Groupchat #%1 Групен разговор #%1 Show Tray action menu to show qTox window Прикажи Add friend title of the window Додади пријател Group invites title of the window Групни покани File transfers title of the window Трансфери на датотека Settings title of the window Поставки My profile title of the window Мој профил Failed to send file "%1" Не успеа да се испрати датотеката "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/nl.ts000066400000000000000000003305501415623743500155330ustar00rootroot00000000000000 AVForm Audio/Video Audio/video Default resolution Standaardresolutie Disabled Uitgeschakeld Select region Selecteer regio Screen %1 Scherm %1 Audio Settings Audio-instellingen Gain Versterking Playback device Afspeelapparaat Use slider to set volume of your speakers. Gebruik de schuifbalk om het volume van je speakers in te stellen. Capture device Opnameapparaat Volume Volume Video Settings Video-instellingen Video device Video-apparaat Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Stel de resolutie van je camera in. Hoe hoger de resolutie, des te beter de videokwaliteit die je vrienden te zien krijgen. Let er echter op dat je voor een hogere resolutie ook een betere internetverbinding nodig hebt. Het is mogelijk dat je internetverbinding niet snel genoeg is om een hogere videokwaliteit te ondersteunen, wat tot problemen kan leiden met videogesprekken. Resolution Resolutie Rescan devices Apparaten opnieuw scannen Test Sound Testgeluid Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Schakelt de experimentele audioback-end met ondersteuning voor echo-onderdrukking in. qTox moet opnieuw worden opgestart om de wijziging van kracht te laten gaan. Enable experimental audio backend Experimentele audioback-end inschakelen Audio quality Audiokwaliteit Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Kwaliteit van verstuurde audio. Verlaag deze instelling als je te weinig bandbreedte hebt, of als je je internetverbruik wilt beperken. High (64 kbps) Hoog (64 kbps) Medium (32 kbps) Gemiddeld (32 kbps) Low (16 kbps) Laag (16 kbps) Very low (8 kbps) Zeer laag (8 kbps) Threshold Drempelwaarde AboutForm About Over Original author: %1 Oorspronkelijke auteur: %1 You are using qTox version %1. Je gebruikt qTox-versie %1. Commit hash: %1 Commit-hash: %1 toxcore version: %1 toxcore-versie: %1 Qt version: %1 Qt-versie: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Een lijst van bekende problemen kun je vinden op onze %1 op GitHub. Als je een probleem of beveiligingsfout in qTox tegenkomt, gelieve deze dan te melden volgens de richtlijnen in ons wiki-artikel %2. Click here to report a bug. Klik hier om een probleem te melden. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Bekijk een volledige lijst van %1 op GitHub bug-tracker Replaces `%1` in the `A list of all known…` bugtracker Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Nuttige probleemmeldingen schrijven contributors Replaces `%1` in `See a full list of…` bijdragers AboutFriendForm Dialog Dialoogvenster username gebruikersnaam status message statusbericht Used aliases: Gebruikte aliassen: HISTORY OF ALIASES ALIASGESCHIEDENIS Automatically accept files from contact if set Indien ingesteld automatisch bestanden van dit contact aanvaarden Auto accept files Bestanden automatisch aanvaarden Default directory to save files: Standaardlocatie om bestanden op te slaan: Auto accept for this contact is disabled Automatisch aanvaarden is uitgeschakeld voor dit contact Auto accept call: Oproep automatisch aanvaarden: Manual Handmatig Audio Audio Audio + Video Audio + video Automatically accept group chat invitations from this contact if set. Indien ingesteld groepsgespreksuitnodigingen van dit contact automatisch aanvaarden. Auto accept group invites Groepsuitnodigingen automatisch aanvaarden Remove history (operation can not be undone!) Geschiedenis verwijderen (kan niet ongedaan gemaakt worden!) Notes Notities Input field for notes about the contact Invoerveld voor notities over het contact You can save comment about this contact here. Hier kun je commentaren over dit contact opslaan. History removed Geschiedenis verwijderd Choose an auto accept directory popup title Kies een map om automatisch aanvaarde bestanden op te slaan <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Dit is de publieke sleutel van jouw vriend, gebruik het om zijn/haar identiteit te bevestigen over een ander kanaal. Je kan dit niet naar andere mensen sturen om dit contact toe te voegen.</p></body></html> Public key (not ToxID): Publieke sleutel (geen ToxID): Confirmation Bevestiging Are you sure to remove %1 chat history? Weet je zeker dat je %1 gespreksgeschiedenis wilt verwijderen? Failed to remove chat history with %1! Kon chatgeschiedenis met %1 niet verwijderen! AboutSettings Version Versie License Licentie Authors Auteurs Known Issues Bekende problemen Open update download link Update-downloadlink openen Update available Nieuwe versie beschikbaar qTox is up to date ✓ deze qTox versie is actueel ✓ AddFriendForm Add Friends Vrienden toevoegen Send friend request Vriendschapsverzoek sturen Couldn't add friend Kon vriend niet toevoegen Invalid Tox ID format Ongeldige Tox-ID Add a friend Vriend toevoegen Friend requests Vriendschapsverzoeken Accept Aanvaarden Reject Weigeren Tox ID, either 76 hexadecimal characters or name@example.com Tox-ID, ofwel 76 hexadecimale tekens, ofwel naam@voorbeeld.be Type in Tox ID of your friend Voer de Tox-ID van je vriend in Friend request message Bericht voor vriendschapsverzoek Type message to send with the friend request or leave empty to send a default message Voer een bericht in om samen met het vriendschapsverzoek te sturen, of laat leeg om een standaardbericht te sturen %1 Tox ID is invalid or does not exist Toxme error %1 Tox-ID is ongeldig of bestaat niet You can't add yourself as a friend! When trying to add your own Tox ID as friend Je kunt jezelf niet als vriend toevoegen! Open contact list Contactlijst openen Couldn't open file Kon bestand niet openen Couldn't open the contact file Error message when trying to open a contact list file to import Kon het contactbestand niet openen Invalid file Ongeldig bestand We couldn't find any contacts to import in this file! We konden in dit bestand geen contacten vinden om te importeren! Tox ID Tox ID of the person you're sending a friend request to Tox-ID either 76 hexadecimal characters or name@example.com Tox ID format description ofwel 76 hexadecimale tekens, ofwel naam@voorbeeld.be Message The message you send in friend requests Bericht Open Button to choose a file with a list of contacts to import Openen Send friend requests Vriendschapsverzoeken sturen %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 hier! Tox met me! Import a list of contacts, one Tox ID per line Importeer een lijst van contacten, één Tox-ID per regel Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Klaar om %n contact te importeren, klik om te bevestigen Klaar om %n contacten te importeren, klik om te bevestigen Import contacts Contacten importeren AdvancedForm Advanced Geavanceerd Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Tenzij je %1 weet wat je doet, wijzig je hier best %2. Wijzigingen die je hier doorvoert kunnen tot problemen met qTox leiden, en zelfs tot verlies van je gegevens, bijvoorbeeld je geschiedenis. really écht not niets IMPORTANT NOTE BELANGRIJKE OPMERKING Reset settings Instellingen herstellen All settings will be reset to default. Are you sure? Alle instellingen zullen teruggebracht worden naar de standaardinstellingen. Weet je het zeker? Yes Ja No Nee Call active popup title In gesprek You can't disconnect while a call is active! popup text Je kan niet offline gaan terwijl je in een gesprek zit! Save File Bestand opslaan Logs (*.log) Logboeken (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Sla instellingen op in de map waarin qTox draait in plaats van de normale configuratielocatie Make Tox portable Tox portabel maken Reset to default settings Standaardinstellingen herstellen Portable Portabel Connection Settings Verbindingsinstellingen Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Gebruik IPv6 (aanbevolen) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Door dit uit te schakelen kun je bijvoorbeeld Tox via Tor gebruiken. Het is wel zwaarder voor het Tox-netwerk, dus schakel dit alleen uit indien noodzakelijk. Enable UDP (recommended) Text on checkbox to disable UDP Gebruik UDP (aanbevolen) Proxy type: Proxy-type: Address: Text on proxy addr label Adres: Port: Text on proxy port label Poort: None Geen SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Opnieuw verbinden Debug Debug Export Debug Log Debuglogboek exporteren Copy Debug Log Debuglogboek kopiëren Enable LAN discovery LAN-herkenning aanzetten ChatForm Send a file Bestand versturen qTox wasn't able to open %1 qTox kon %1 niet openen %1 calling %1 belt Unable to open Openen mislukt Bad idea Slecht idee Failed to open temporary file Temporary file for screenshot Kon tijdelijk bestand niet openen qTox wasn't able to save the screenshot qTox kon de schermafdruk niet opslaan Call with %1 ended. %2 Gesprek met %1 beëindigd. %2 Call duration: Gespreksduur: Calling %1 %1 bellen %1 is typing %1 is aan het typen Copy Kopiëren You're trying to send a sequential file, which is not going to work! Je probeert een sequentieel bestand te sturen, maar dat kan niet! %1 is now %2 e.g. "Dubslow is now online" %1 is nu %2 Call with %1 ended unexpectedly. %2 Gesprek met %1 is onverwacht beëindigd. %2 Filename contained illegal characters Bestandsnaam bevatte niet-toegestane tekens Illegal characters have been changed to _ so you can save the file on windows. Niet-toegestane tekens zijn vervangen door _ zodat je het bestand op kunt slaan in windows. ChatFormHeader Can't start audio call Kan audiogesprek niet starten Start audio call Audiogesprek starten End audio call Audiogesprek beëindigen Cancel audio call Audiogesprek annuleren Accept audio call Audiogesprek aanvaarden Can't start video call Kan videogesprek niet starten Start video call Videogesprek starten End video call Videogesprek beëindigen Cancel video call Videogesprek annuleren Accept video call Videogesprek aanvaarden Sound can be disabled only during a call Geluid kan enkel uitgeschakeld worden tijdens een gesprek Unmute call Gesprek niet meer dempen Mute call Gesprek dempen Microphone can be muted only during a call Microfoon kan enkel gedempt worden tijdens een gesprek Unmute microphone Microfoon ontdempen Mute microphone Microfoon dempen ChatLog Copy Kopiëren Select all Alles selecteren pending wachtend ChatTextEdit Type your message here... Typ hier je bericht… CircleWidget Rename circle Menu for renaming a circle Cirkel hernoemen Remove circle Menu for removing a circle Cirkel verwijderen Open all in new window Alles openen in nieuw venster Core /me offers friendship, "%1" /me biedt vriendschap aan, ‘%1’ Invalid Tox ID Error while sending friendship request Ongeldige Tox-ID You need to write a message with your request Error while sending friendship request Je vriendschapsverzoek moet een bericht bevatten Your message is too long! Error while sending friendship request Je bericht is te lang! Friend is already added Error while sending friendship request Vriend is al toegevoegd Groupchat %1 Groepschat %1 DesktopNotify New message Nieuw bericht Incoming file transfer Inkomende bestandsoverdracht Friend request received Vriendschapsverzoek ontvangen New group message Nieuw groepsbericht Group invite received Groepsuitnodiging ontvangen FileTransferWidget Form Formulier 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 Verwachte resterende downloadtijd:10:10 Filename Bestandsnaam Waiting to send... file transfer widget Wachten om te versturen… Accept to receive this file file transfer widget Aanvaard om dit bestand te ontvangen Location not writable Title of permissions popup Locatie niet schrijfbaar You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Je hebt geen toegang om een bestand op deze locatie op te slaan. Kies een andere locatie of annuleer het opslaan. Paused file transfer widget Gepauzeerd Resuming... file transfer widget Hervatten… Open file Bestand openen Open file directory Bestandsmap openen Pause transfer Bestandsoverdracht pauzeren Cancel transfer Bestandsoverdracht annuleren Resume transfer Bestandsoverdracht hervatten Accept transfer Bestandsoverdracht aanvaarden Save a file Title of the file saving dialog Bestand opslaan Remote Paused file transfer widget Extern onderbroken FilesForm Transferred Files "Headline" of the window Overgedragen bestanden Downloads Downloads Uploads Uploads FriendListWidget Today Vandaag Yesterday Gisteren Last 7 days Afgelopen 7 dagen This month Deze maand Older than 6 Months Ouder dan 6 maanden Never Nooit FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Vriendschapsverzoek Someone wants to make friends with you Iemand wil vriendschap met je sluiten User ID: Gebruikers-ID: Friend request message: Bericht voor vriendschapsverzoek: Accept Accept a friend request Aanvaarden Reject Reject a friend request Weigeren FriendWidget Invite to group Menu to invite a friend to a groupchat Uitnodigen voor groep Move to circle... Menu to move a friend into a different circle Verplaatsen naar cirkel… To new circle Naar nieuwe cirkel Remove from circle '%1' Verwijderen uit cirkel ‘%1’ Move to circle "%1" Verplaatsen naar cirkel ‘%1’ Set alias... Alias instellen… Auto accept files from this friend context menu entry Bestanden van deze vriend automatisch aanvaarden Remove friend Menu to remove the friend from our friendlist Vriend verwijderen Choose an auto accept directory popup title Kies een map om automatisch aanvaarde bestanden op te slaan New message Nieuw bericht Online Online Away Afwezig Busy Bezet Offline Offline Open chat in new window Chat openen in nieuw venster Remove chat from this window Chat uit dit venster verwijderen To new group Naar nieuwe groep Invite to group '%1' Uitnodigen voor groep ‘%1’ Show details Details tonen GeneralForm General Algemeen Choose an auto accept directory popup title Kies een map om automatisch aanvaarde bestanden op te slaan GeneralSettings General Settings Algemene instellingen The translation may not load until qTox restarts. De vertaling wordt mogelijk pas geladen als qTox opnieuw is opgestart. Language: Taal: Show system tray icon Pictogram tonen in systeemvak Enable light tray icon. toolTip for light icon setting Licht systeemvakpictogram inschakelen. Light icon Licht pictogram qTox will start minimized in tray. toolTip for Start in tray setting qTox zal geminimaliseerd in het systeemvak starten. Start in tray In systeemvak starten After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Indien je op sluiten (X) klikt, zal qTox naar het systeemvak minimaliseren, in plaats van af te sluiten. Close to tray Naar systeemvak sluiten After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Indien je op minimaliseren (_) klikt, zal qTox naar het systeemvak minimaliseren, in plaats van naar de taakbalk. Minimize to tray Naar systeemvak minimaliseren Set where files will be saved. Stel in waar bestanden opgeslagen worden. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Je kan dit per vriend instellen door met de rechtermuisknop op een vriend te klikken. Autoaccept files Bestanden automatisch aanvaarden Set to 0 to disable Stel in op 0 om uit te schakelen Your status is changed to Away after set period of inactivity. Je status zal automatisch op afwezig gezet worden na een bepaalde periode van inactiviteit. Auto away after (0 to disable): Automatisch afwezig na (0 om uit te schakelen): Start qTox on operating system startup (current profile). qTox starten bij opstarten van besturingssysteem (huidige profiel). Show contacts' status changes Statusveranderingen van contacten tonen Autostart Automatisch starten Default directory to save files: Standaardmap om bestanden op te slaan: Check for updates Op nieuwe versies controleren Spell checking Spellingscontrole Max autoaccept file size (0 to disable): Max. grootte automatisch geaccepteerde bestanden (0 om te deactiveren): MB MB GenericChatForm Send message Bericht versturen Smileys Smileys Send file(s) Bestand(en) versturen Save chat log Chatgeschiedenis opslaan Send a screenshot Schermafdruk sturen Clear displayed messages Getoonde berichten wissen Cleared FIXME: Weird translation Gewist Quote selected text Geselecteerde tekst citeren Copy link address Koppelingsadres kopiëren Confirmation Bevestiging You are sure that you want to clear all displayed messages? Weet je zeker dat je alle weergegeven berichten wilt wissen? Search in text Tekst doorzoeken Go to current date Naar huidige datum gaan Load chat history... Chatgeschiedenis laden… Export to file Naar bestand exporteren GenericNetCamView Tox video Tox-video Show Messages Berichten tonen Hide Messages Berichten verbergen Full Screen Volledig scherm Toggle video preview Videovoorbeeld wisselen Mute audio Audio dempen Mute microphone Microfoon dempen End video call Videogesprek beëindigen Exit full screen Volledig scherm afsluiten GroupChatForm %1 has set the title to %2 %1 heeft de titel gewijzigd naar %2 %1 has joined the group %1 neemt nu deel aan de groep %1 is now known as %2 %1 staat nu bekend als %2 %1 has left the group %1 heeft de groep verlaten %n user(s) in chat Number of users in chat mute dempen unmute ontdempen GroupInviteForm Groups Groepen Create new group Nieuwe groep aanmaken Group invites Groepsuitnodigingen GroupInviteWidget Invited by %1 on %2 at %3. Uitgenodigd door %1 op %2 om %3. Join Deelnemen Decline Weigeren GroupWidget Set title... Stel titel in… Quit group Menu to quit a groupchat Groep verlaten Open chat in new window Chat openen in nieuw venster Remove chat from this window Chat verwijderen uit dit venster %n user(s) in chat Number of users in chat New Message Nieuw bericht Online Online IdentitySettings Public Information Publieke informatie Tox ID Tox-ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Deze combinatie van tekens vertelt andere Tox-cliënten hoe ze contact met je op moeten nemen. Deel dit met je vrienden om te communiceren. Your Tox ID (click to copy) Jouw Tox-ID (klik om te kopiëren) This QR code contains your Tox ID. You may share this with your friends as well. Deze QR-code bevat je Tox-ID. Je kunt deze met vrienden delen. Save image Afbeelding opslaan Copy image Afbeelding kopiëren Profile Profiel Rename profile. tooltip for renaming profile button Profiel hernoemen. Delete profile. delete profile button tooltip Profiel verwijderen. Go back to the login screen tooltip for logout button Ga terug naar het aanmeldscherm Logout import profile button Uitloggen Remove password Wachtwoord verwijderen Change password Wachtwoord wijzigen Rename rename profile button Hernoemen Export export profile button Exporteren Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Exporteer je Tox-profiel naar een bestand. Dit bestand bevat geen chatgeschiedenis. Delete delete profile button Verwijderen Server Server Hide my name from the public list Mijn naam niet in de openbare lijst opnemen Register Registreren Your password Je wachtwoord Update Bijwerken Register on ToxMe Registreren op ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Naam van de ToxMe-dienst. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optioneel. Iets over jezelf, of over je kat. Optional. Something about you. Or your cat. Tooltip for the Biography field. Optioneel. Iets over jezelf, of over je kat. ToxMe service to register on. ToxMe-dienst om bij te registreren. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Indien niet ingesteld is je naam openbaar zichtbaar op ToxMe. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Verwijder je wachtwoord en versleuteling van je profiel. Name input Naaminvoer Name visible to contacts Naam zichtbaar voor contacten Status message input Invoer voor statusbericht Status message visible to contacts Statusbericht zichtbaar voor contacten Your Tox ID Je Tox-ID Save QR image as file QR-afbeelding opslaan als bestand Copy QR image to clipboard QR-afbeelding kopiëren naar klembord ToxMe username to be shown on ToxMe ToxMe-gebruikersnaam om te tonen op ToxMe Optional ToxMe biography to be shown on ToxMe Optionele ToxMe-biografie om te tonen op ToxMe ToxMe service address ToxMe-dienstadres Visibility on the ToxMe service Zichtbaarheid op de ToxMe-dienst Password Wachtwoord Update ToxMe entry ToxMe-invoer bijwerken Rename profile. Profiel hernoemen. Delete profile. Profiel verwijderen. Export profile Profiel exporteren Remove password from profile Wachtwoord van profiel verwijderen Change profile password Wachtwoord van profiel wijzigen My name: Mijn naam: My status: Mijn status: My username Mijn gebruikersnaam My biography Mijn biografie My profile Mijn profiel LoadHistoryDialog Load History Dialog Geschiedenis laden Load history Geschiedenis laden from van to tot (about 100 messages are loaded) (er zijn ongeveer 100 berichten geladen) Select Date Dialog Dialoogvenster datum selecteren Select a date Selecteer een datum LoginScreen Username: Gebruikersnaam: Password: Wachtwoord: Confirm: Bevestig: Password strength: %p% Wachtwoordsterkte: %p% Create Profile Profiel aanmaken If the profile does not have a password, qTox can skip the login screen Als het profiel geen wachtwoord heeft kan qTox het aanmeldscherm overslaan New Profile Nieuw profiel Couldn't create a new profile Kon geen nieuw profiel aanmaken The username must not be empty. De gebruikersnaam mag niet leeg zijn. The password must be at least 6 characters long. Het wachtwoord moet minstens 6 tekens lang zijn. The passwords you've entered are different. Please make sure to enter same password twice. De wachtwoorden die je hebt ingevoerd komen niet overeen. Voer tweemaal hetzelfde wachtwoord in. A profile with this name already exists. Er bestaat al een profiel met deze naam. Couldn't load this profile Kon dit profiel niet laden This profile is already in use. Dit profiel is al in gebruik. Wrong password. Verkeerd wachtwoord. Load automatically Automatisch laden Import Importeren Load Laden Load Profile Profiel laden Password protected profiles can't be automatically loaded. Profielen beschermd met wachtwoord kunnen niet automatisch geladen worden. Couldn't load profile Kon profiel niet laden There is no selected profile. You may want to create one. Er is geen profiel geselecteerd. Misschien wil je er een maken. Username input field Invoerveld voor gebruikersnaam Password input field, you can leave it empty (no password), or type at least 6 characters Wachtwoordinvoerveld, je kan dit leeg laten (geen wachtwoord), of minstens 6 tekens invoeren Password confirmation field Wachtwoordbevestigingsveld Create a new profile button Knop voor aanmaken van nieuw profiel Profile list Profiellijst List of profiles Lijst van profielen Password input Wachtwoordinvoer Load automatically checkbox Selectievakje voor automatisch laden Import profile Profiel importeren Load selected profile button Knop voor laden van geselecteerd profiel New profile creation page Pagina voor aanmaken van nieuw profiel Loading existing profile page Pagina voor laden van bestaand profiel MainWindow Your name Je naam Your status Je status ... Add friends Vrienden toevoegen Create a group chat Groepschat aanmaken View completed file transfers Voltooide bestandsoverdrachten bekijken Change your settings Je instellingen wijzigen Close Sluiten Open profile Profiel openen Open profile page when clicked Profielpagina openen wanneer aangeklikt Status message input Invoer voor statusbericht Set your status message that will be shown to others Je statusbericht instellen, dat aan anderen getoond zal worden Status Status Set availability status Beschikbaarheidsstatus instellen Contact search Contacten doorzoeken Contact search input for known friends Invoer voor zoeken naar bekende vrienden in contacten Sorting and visibility Sortering en zichtbaarheid Set friends sorting and visibility Sortering en zichtbaarheid van vrienden instellen Open Add friends page Pagina voor toevoegen van vrienden openen Groupchat Groepschat Open groupchat management page Groepschat-beheerpagina openen File transfers history Bestandsoverdrachtgeschiedenis Open File transfers history Bestandsoverdrachtgeschiedenis openen Settings Instellingen Open Settings Instellingen openen Nexus View OS X Menu bar Weergave Window OS X Menu bar Venster Minimize OS X Menu bar Minimaliseren Bring All to Front OS X Menu bar Alles naar de voorgrond brengen Exit Fullscreen Volledig scherm verlaten Enter Fullscreen Volledig scherm gebruiken NotificationEdgeWidget Unread message(s) Ongelezen bericht Ongelezen berichten PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK INGESCHAKELD PrivacyForm Privacy Privacy Confirmation Bevestiging Do you want to permanently delete all chat history? Wil je alle chatsgeschiedenis voorgoed verwijderen? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Je vrienden kunnen zien wanneer je typt. Send typing notifications Typmeldingen versturen Keep chat history Chatgeschiedenis behouden NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam is een deel van je Tox-ID. Als je gebombardeerd wordt met vriendschapsverzoeken is het verstandig om je NoSpam te veranderen. Mensen die je oude Tox-ID hebben kunnen je dan niet meer toevoegen, maar je huidige vriendenlijst wordt behouden. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam is een deel van je Tox-ID dat veranderd kan worden. Verander de NoSpam als je gebombardeerd wordt met vriendschapsverzoeken. Generate random NoSpam Willekeurige NoSpam genereren Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Het opslaan van chatgeschiedenis is nog in ontwikkeling. Het is mogelijk dat er zich veranderingen in het formaat voordoen, wat kan leiden tot gegevensverlies. Privacy Privacy BlackList Zwarte lijst Filter group message by group member's public key. Put public key here, one per line. Groepsberichten op basis van publieke sleutel van groepslid filteren. Plaats hier één publieke sleutel per regel. Profile Failed to derive key from password, the profile won't use the new password. Sleutel ophalen van wachtwoord mislukt, het profiel zal het nieuwe wachtwoord niet gebruiken. Couldn't change password on the database, it might be corrupted or use the old password. Kon wachtwoord van database niet wijzigen, het is mogelijk beschadigd of gebruikt het oude wachtwoord. Toxing on qTox Toxt op qTox ProfileForm Current profile: Huidig profiel: Choose a profile picture Kies een profielfoto Error Fout Unable to open this file. Kan dit bestand niet openen. Unable to read this image. Kan deze afbeelding niet lezen. The supplied image is too large. Please use another image. De geselecteerde afbeelding is te groot. Gebruik een andere afbeelding. Rename "%1" renaming a profile ‘%1’ hernoemen Couldn't rename the profile to "%1" Kon het profiel niet hernoemen naar ‘%1’ Location not writable Title of permissions popup Locatie niet schrijfbaar You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Je hebt geen toegang om een bestand op deze locatie op te slaan. Kies een andere locatie of annuleer het opslaan. Failed to copy file Kopiëren van bestand mislukt The file you chose could not be written to. Het bestand dat je hebt gekozen kan niet geschreven worden. Really delete profile? deletion confirmation title Het profiel echt verwijderen? Are you sure you want to delete this profile? deletion confirmation text Weet je zeker dat je dit profiel wilt verwijderen? Save save qr image Opslaan Save QrCode (*.png) save dialog filter QR-code opslaan (*.png) Nothing to remove Niets om te verwijderen Your profile does not have a password! Je profiel heeft geen wachtwoord! Really delete password? deletion confirmation title Het wachtwoord echt verwijderen? Please enter a new password. Voer een nieuw wachtwoord in. Remove Verwijderen Files could not be deleted! deletion failed title Bestanden konden niet verwijderd worden! Register (processing) Registreren (in verwerking) Update (processing) Bijwerken (in verwerking) Done! Klaar! Account %1@%2 updated successfully Account %1@%2 met succes bijgewerkt Successfully added %1@%2 to the database. Save your password %1@%2 is met success toegevoegd aan de database. Sla je wachtwoord op Toxme error ToxMe-fout Register Registreren Update Bijwerken Change password button text Wachtwoord wijzigen Set profile password button text Profielwachtwoord instellen Current profile location: %1 Huidige profiellocatie: %1 Couldn't change password Kon wachtwoord niet wijzigen This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Deze hoop tekens vertelt andere Tox-cliënten hoe ze je moeten contacteren. Deel hem met je vrienden om te communiceren. Deze ID bevat de NoSpam-code (in het blauw) en de controlesom (in het grijs). Empty path is unavaliable Leeg pad is niet beschikbaar Failed to rename Hernoemen mislukt Profile already exists Profiel bestaat al A profile named "%1" already exists. Een profiel met de naam ‘%1’ bestaat al. Empty name Lege naam Empty name is unavaliable Lege naam is niet beschikbaar Empty path Leeg pad Couldn't change password on the database, it might be corrupted or use the old password. Kon wachtwoord van database niet wijzigen, het is mogelijk beschadigd of gebruikt het oude wachtwoord. Export profile Profiel exporteren Tox save file (*.tox) save dialog filter Tox-opslagbestand (*.tox) The following files could not be deleted: deletion failed text part 1 De volgende bestanden konden niet verwijderd worden: Please manually remove them. deletion failed text part 2 Verwijder ze handmatig. Are you sure you want to delete your password? deletion confirmation text Weet je zeker dat je je wachtwoord wilt verwijderen? Images (%1) filetype filter Afbeeldingen (%1) ProfileImporter Import profile import dialog title Profiel importeren Tox save file (*.tox) import dialog filter Tox-opslagbestand (*.tox) Ignoring non-Tox file popup title Niet-Tox-bestand wordt genegeerd Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Waarschuwing: het door jou gekozen bestand is geen Tox-opslagbestand; het wordt genegeerd. Profile already exists import confirm title Profiel bestaat al A profile named "%1" already exists. Do you want to erase it? import confirm text Er bestaat al een profiel met de naam ‘%1’. Wil je het wissen? File doesn't exist Bestand bestaat niet Profile doesn't exist Profiel bestaat niet Profile imported Profiel geïmporteerd %1.tox was successfully imported %1.tox is met succes geïmporteerd QApplication Ok Oké Cancel Annuleren Yes Ja No Nee LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Kon vriend niet toevoegen %1 is not a valid Toxme address. %1 is geen geldig ToxMe-adres. You can't add yourself as a friend! When trying to add your own Tox ID as friend Je kunt jezelf niet als vriend toevoegen! QObject Tox URI to parse Te verwerken Tox-URI Starts new instance and loads specified profile. Start nieuwe instantie en laadt specifiek profiel. profile profiel Default Standaard Blue Blauw Olive Olijfgroen Red Rood Violet Violet Incoming call... Inkomende oproep… %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 hier! Tox met me! Server doesn't support Toxme Server biedt geen ondersteuning voor ToxMe You're making too many requests. Wait an hour and try again Je doet te veel verzoeken. Wacht een uur en probeer het opnieuw This name is already in use Deze naam is al in gebruik This Tox ID is already registered under another name Deze Tox-ID is al geregistreerd onder een andere naam Please don't use a space in your name Je naam mag geen spaties bevatten Password incorrect Wachtwoord onjuist You can't use this name Deze naam kun je niet gebruiken Name not found Naam niet gevonden Tox ID not sent Tox-ID niet verstuurd That user does not exist Die gebruiker bestaat niet Error Fout qTox couldn't open your chat logs, they will be disabled. qTox kon je gespreksgeschiedenis niet openen, deze zal uitgeschakeld worden. None No camera device set Geen Desktop Desktop as a camera input for screen sharing Bureaublad Problem with HTTPS connection Probleem met HTTPS-verbinding Internal ToxMe error Interne ToxMe-fout Reformatting text in progress.. Tekst wordt opnieuw geformatteerd… Starts new instance and opens the login screen. Start nieuwe instantie en opent aanmeldscherm. Dark Donker Dark blue Donkerblauw Dark olive Donkerolijfgroen Dark red Donkerrood Dark violet Donkerviolet Failed to load profile automatically. Kon profiel niet automatisch laden. online contact status online away contact status afwezig busy contact status bezet offline contact status offline blocked contact status geblokkeerd RemoveFriendDialog Remove friend Vriend verwijderen Also remove chat history Gespreksgeschiedenis ook verwijderen Remove Verwijderen Are you sure you want to remove %1 from your contacts list? Weet je zeker dat je %1 uit je contactenlijst wil verwijderen? Remove all chat history with the friend if set Indien ingesteld alle gespreksgeschiedenis met de vriend verwijderen ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Klik en sleep om een gebied te selecteren. Druk op %1 om het qTox-venster te verbergen/herstellen, of %2 om te annuleren. Space [Space] key on the keyboard Spatie Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Druk op %1 om een schermafdruk van de selectie te versturen, op %2 om het qTox-scherm te verbergen/herstellen, of op %3 om te annuleren. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. De tekst kon niet gevonden worden. Start Start SearchSettingsForm Form Formulier Start search: Zoekopdracht starten: from the end vanaf het einde from the beginning vanaf het begin after date na datum before date vóór datum 00.00.0000 00.00.0000 Case sensitive Hoofdlettergevoelig Whole words only Alleen hele woorden Use regular expressions Reguliere expressies gebruiken SetPasswordDialog Set your password Je wachtwoord instellen The password is too short Het wachtwoord is te kort The password doesn't match. Het wachtwoord komt niet overeen. Confirm: Bevestig: Password: Wachtwoord: Password strength: %p% Wachtwoordsterkte: %p% Confirm password Wachtwoord bevestigen Confirm password input Invoer voor bevestigen van wachtwoord Password input Wachtwoordinvoer Password input field, minimum 6 characters long Wachtwoordinvoerveld, minimaal 6 tekens Settings Circle #%1 Cirkel #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Vriend toevoegen Do you want to add %1 as a friend? Wil je %1 als vriend toevoegen? User ID: Gebruikers-ID: Friend request message: Bericht voor vriendschapsverzoek: Send Send a friend request Versturen Cancel Don't send a friend request Annuleren UserInterfaceForm None Geen User Interface Gebruikersinterface UserInterfaceSettings Chat Chat Base font: Basislettertype: px px Size: Grootte: New text styling preference may not load until qTox restarts. De nieuwe tekststijlvoorkeur wordt mogelijk niet geladen totdat qTox opnieuw is opgestart. Text Style format: Tekststijlformaat: Select text styling preference. Selecteer een tekststijlvoorkeur. Plaintext Platte tekst Show formatting characters Formatteringstekens tonen Don't show formatting characters Formatteringstekens niet tonen New message Nieuw bericht Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting qTox-venster openen wanneer je een bericht ontvangt en er nog geen venster open is. Open window Venster openen Contact list Contactlijst If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Indien geselecteerd zullen groepsgesprekken bovenaan de vriendenlijst gezet worden, anders komen ze onder online vrienden. Place groupchats at top of friend list Groepsgesprekken bovenaan vriendenlijst plaatsen Your contact list will be shown in compact mode. toolTip for compact layout setting Je contactlijst zal in compacte modus getoond worden. Compact contact list Compacte contactenlijst Multiple windows mode Meerderevenstersmodus Open each chat in an individual window Elke chat openen in een apart venster Emoticons Emoticons Use emoticons Emoticons gebruiken Smiley Pack: Text on smiley pack label Smileypakket: Emoticon size: Emoticongrootte: px px Theme Thema Style: Stijl: Theme color: Themakleur: Timestamp format: Tijdsaanduiding: Date format: Datumformaat: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Indien ingeschakeld krijgt elk contact zonder profielfoto een automatisch gegenereerde afbeelding gebaseerd op zijn/haar Tox-ID, in plaats van een standaardfoto. Toepassen vereist een herstart. Use identicons instead of empty avatars Identicons gebruiken in plaats van lege avatars Use colored nicknames in chats Gekleurde bijnamen in chats gebruiken Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Toon een notificatie wanneer je een nieuw bericht ontvangt en het venster niet geselecteerd is. Notify Melden Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Nieuwe berichten in groepschats alleen melden als je genoemd wordt. Group chats only notify when mentioned Groepchats alleen melden als je genoemd wordt Play sound Geluid afspelen Play sound while Busy Geluid afspelen indien Bezet Notify via desktop notifications Via bureaubladmeldingen melden Hide message sender and contents Afzender en berichtinhoud verbergen Widget Online Button to set your status to 'Online' Online Away Button to set your status to 'Away' Afwezig Busy Button to set your status to 'Busy' Bezet toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Toxcore kon niet opstarten met deze proxyinstellingen. Hierdoor kan qTox niet starten. Verander je instellingen en herstart. File Bestand Edit Profile Profiel bewerken Change Status Status wijzigen Log out Uitloggen Edit Bewerken Filter... Filteren… Contacts Contacten Add Contact... Contact toevoegen… Next Conversation Volgend gesprek Previous Conversation Vorig gesprek toxcore failed to start, the application will terminate after you close this message. toxcore kon niet opstarten, de toepassing zal afsluiten nadat je dit bericht gesloten hebt. Executable file popup title Uitvoerbaar bestand You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Je hebt qTox gevraagd een uitvoerbaar bestand te openen. Uitvoerbare bestanden kunnen schade toebrengen aan je computer. Weet je zeker dat je dit bestand wilt openen? Couldn't request friendship Kon geen vriendschapsverzoek sturen Status Status Message failed to send Bericht kon niet verstuurd worden Add new circle... Nieuwe cirkel toevoegen… By Name Op naam By Activity Op activiteit All Alle Online Online Offline Offline Friends Vrienden Groups Groepen Search Contacts Contacten doorzoeken Your name Je naam Groupchat #%1 Groepsgesprek #%1 Create new group... Nieuwe groep aanmaken… %n New Friend Request(s) %n nieuw vriendschapsverzoek %n nieuwe vriendschapsverzoeken %n New Group Invite(s) %n nieuwe groepsuitnodiging %n nieuwe groepsuitnodigingen Logout Tray action menu to logout user Uitloggen Exit Tray action menu to exit tox Afsluiten Show Tray action menu to show qTox window Tonen Add friend title of the window Vriend toevoegen Group invites title of the window Groepsuitnodigingen File transfers title of the window Bestandsoverdrachten Settings title of the window Instellingen My profile title of the window Mijn profiel Failed to send file "%1" Kon bestand ‘%1’ niet verzenden File sent Bestand verzonden sent you a friend request. heeft je een vriendschapsverzoek gestuurd. invites you to join a group. nodigt je uit om aan een groep deel te nemen. qTox/translations/nl_BE.ts000066400000000000000000003302511415623743500160770ustar00rootroot00000000000000 AVForm Audio/Video Audio/video Default resolution Standaardresolutie Disabled Uitgeschakeld Select region Selecteer regio Screen %1 Scherm %1 Audio Settings Audio-instellingen Gain Versterking Playback device Afspeelapparaat Use slider to set volume of your speakers. Gebruik de schuifbalk voor het volume van uw speakers in te stellen. Capture device Opnameapparaat Volume Video Settings Video-instellingen Video device Video-apparaat Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Stel de resolutie van uw camera in. Hoe hoger de resolutie, hoe beter de videokwaliteit die uw vrienden te zien krijgen. Let er echter op dat ge voor een hogere resolutie ook een betere internetverbinding nodig hebt. Het is mogelijk dat uw internetverbinding niet snel genoeg is voor een hogere videokwaliteit te ondersteunen, wat tot problemen kan leiden met videogesprekken. Resolution Resolutie Rescan devices Apparaten opnieuw scannen Test Sound Testgeluid Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Schakelt den experimentelen audioback-end met ondersteuning voor echo-cancelling in. qTox moet opnieuw opstarten voor de wijziging door te voeren. Enable experimental audio backend Experimentelen audioback-end inschakelen Audio quality Audiokwaliteit Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Geluidskwaliteit van verstuurde audio. Verlaag deze instelling als ge te weinig bandbreedte hebt, of als ge uw internetverbruik wilt beperken. High (64 kbps) Hoog (64 kbps) Medium (32 kbps) Gemiddeld (32 kbps) Low (16 kbps) Laag (16 kbps) Very low (8 kbps) Zeer laag (8 kbps) Threshold Drempel AboutForm About Over Original author: %1 Oorspronkelijke auteur: %1 You are using qTox version %1. Ge gebruikt qTox-versie %1. Commit hash: %1 Commit-hash: %1 toxcore version: %1 toxcore-versie: %1 Qt version: %1 Qt-versie: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Ne lijst met gekende problemen kunt g vinden op onzen %1 op GitHub. Als ge een probleem of beveiligingsfout in qTox tegenkomt, gelieve deze dan te melden volgens de richtlijnen in ons wiki-artikel %2. Click here to report a bug. Klik hier voor een probleem te melden. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Bekijk ne volledige lijst op %1 op GitHub bug-tracker Replaces `%1` in the `A list of all known…` bugtracker Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Nuttige probleemmeldingen schrijven contributors Replaces `%1` in `See a full list of…` bijdragers AboutFriendForm Dialog Dialoog username gebruikersnaam status message statusbericht Used aliases: Gebruikte aliassen: HISTORY OF ALIASES ALIASGESCHIEDENIS Automatically accept files from contact if set Indien ingesteld automatisch bestanden van dit contact aanvaarden Auto accept files Bestanden automatisch aanvaarden Default directory to save files: Standaardlocatie voor bestanden in op te slaan: Auto accept for this contact is disabled Automatisch aanvaarden is uitgeschakeld voor dit contact Auto accept call: Oproep automatisch aanvaarden: Manual Handmatig Audio Audio + Video Audio + video Automatically accept group chat invitations from this contact if set. Indien ingesteld groepsgespreksuitnodigingen van dit contact automatisch aanvaarden. Auto accept group invites Groepsgespreksuitnodigingen automatisch aanvaarden Remove history (operation can not be undone!) Geschiedenis verwijderen (kan niet ongedaan gemaakt worden!) Notes Notities Input field for notes about the contact Invoerveld voor notities over het contact You can save comment about this contact here. Hier kunt ge commentaren over dit contact opslaan. History removed Geschiedenis verwijderd Choose an auto accept directory popup title Kies een map voor automatisch aanvaarde bestanden in op te slaan <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Bevestiging Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Versie License Licentie Authors Auteurs Known Issues Bekende problemen Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Vrienden toevoegen Invalid Tox ID format Ongeldig Tox-ID-formaat Send friend request Vriendschapsverzoek sturen Add a friend Vriend toevoegen Friend requests Vriendschapsverzoeken Accept Aanvaarden Reject Weigeren Couldn't add friend Kon vriend niet toevoegen Tox ID, either 76 hexadecimal characters or name@example.com Tox-ID, oftewel 76 hexadecimale tekens, oftewel naam@voorbeeld.be Type in Tox ID of your friend Voer den Tox-ID van uwe vriend in Friend request message Bericht voor vriendschapsverzoek Type message to send with the friend request or leave empty to send a default message Voer een bericht in voor samen met het vriendschapsverzoek te sturen, of laat leeg voor een standaardbericht te sturen %1 Tox ID is invalid or does not exist Toxme error %1 Tox-ID is ongeldig of bestaat niet You can't add yourself as a friend! When trying to add your own Tox ID as friend Ge kunt uzelf niet als vriend toevoegen! Open contact list Contactenlijst openen Couldn't open file Kon bestand niet openen Couldn't open the contact file Error message when trying to open a contact list file to import Kon het contactbestand niet openen Invalid file Ongeldig bestand We couldn't find any contacts to import in this file! We konden in dit bestand geen contacten vinden voor te importeren! Tox ID Tox ID of the person you're sending a friend request to Tox-ID either 76 hexadecimal characters or name@example.com Tox ID format description oftewel 76 hexadecimale tekens, oftewel naam@voorbeeld.be Message The message you send in friend requests Bericht Open Button to choose a file with a list of contacts to import Openen Send friend requests Vriendschapsverzoeken sturen %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Het is hier %1! Tox met mij! Import a list of contacts, one Tox ID per line Importeer ne lijst met contacten, enen Tox-ID per regel Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Klaar voor %n contact te importeren, klik voor te bevestigen Klaar voor %n contacten te importeren, klik voor te bevestigen Import contacts Contacten importeren AdvancedForm Advanced Geavanceerd Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Tenzij dat ge %1 weet wat ge doet, wijzigt ge hier best %2. Wijzigingen die ge hier doorvoert kunnen tot problemen met qTox leiden, en zelfs tot verlies van uw gegevens, bijvoorbeeld uw geschiedenis. really écht not niks IMPORTANT NOTE BELANGRIJKEN OPMERKING Reset settings Instellingen herstellen All settings will be reset to default. Are you sure? Alle instellingen zullen teruggebracht worden naar de standaardinstellingen. Zijt ge het zeker? Yes Ja No Nee Call active popup title In gesprek You can't disconnect while a call is active! popup text Ge kunt niet offline gaan terwijl dat ge in een gesprek zit! Save File Bestand opslaan Logs (*.log) Logboeken (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Sla instellingen op in de map waarin dat qTox draait in plaats van de normale configuratielocatie Make Tox portable Maak Tox draagbaar Reset to default settings Standaardinstellingen herstellen Portable Draagbaar Connection Settings Verbindingsinstellingen Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Gebruik IPv6 (aanbevolen) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Door dit uit te schakelen kunt ge bijvoorbeeld Tox via Tor gebruiken. Het is wel zwaarder voor het Tox-netwerk, dus schakel dit alleen uit indien noodzakelijk. Enable UDP (recommended) Text on checkbox to disable UDP Gebruik UDP (aanbevolen) Proxy type: Proxytype: Address: Text on proxy addr label Adres: Port: Text on proxy port label Poort: None Geen SOCKS5 HTTP Reconnect reconnect button Opnieuw verbinden Debug Export Debug Log Debuglogboek exporteren Copy Debug Log Debuglogboek kopiëren Enable LAN discovery ChatForm Send a file Verstuur een bestand qTox wasn't able to open %1 qTox kan %1 niet openen Unable to open Openen mislukt Bad idea Slecht idee %1 calling %1 belt Calling %1 %1 bellen Failed to open temporary file Temporary file for screenshot Kon tijdelijk bestand niet openen qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox kon de schermafdruk niet opslaan Call with %1 ended. %2 Gesprek met %1 beëindigd. %2 Call duration: Gesprekstijd: %1 is typing %1 is aan het typen Copy Kopiëren You're trying to send a sequential file, which is not going to work! Ge probeert een sequentieel bestand te sturen, maar dat kan niet! %1 is now %2 e.g. "Dubslow is now online" %1 is nu %2 Call with %1 ended unexpectedly. %2 Gesprek met %1 is onverwacht beëindigd. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Kan audiogesprek niet starten Start audio call Audiogesprek starten End audio call Audiogesprek beëindigen Cancel audio call Audiogesprek annuleren Accept audio call Audiogesprek aanvaarden Can't start video call Kan videogesprek niet starten Start video call Videogesprek starten End video call Videogesprek beëindigen Cancel video call Videogesprek annuleren Accept video call Videogesprek aanvaarden Sound can be disabled only during a call Geluid kan enkel uitgeschakeld worden tijdens een gesprek Unmute call Gesprek niet meer dempen Mute call Gesprek dempen Microphone can be muted only during a call Microfoon kan enkel gedempt worden tijdens een gesprek Unmute microphone Microfoon niet meer dempen Mute microphone Microfoon dempen ChatLog Copy Kopiëren Select all Alles selecteren pending wachtend ChatTextEdit Type your message here... Typ uw bericht hier… CircleWidget Rename circle Menu for renaming a circle Cirkel hernoemen Remove circle Menu for removing a circle Cirkel verwijderen Open all in new window Alles openen in nieuw venster Core /me offers friendship, "%1" /me biedt vriendschap aan, ‘%1’ Invalid Tox ID Error while sending friendship request Ongeldigen Tox-ID You need to write a message with your request Error while sending friendship request Uw vriendschapsverzoek moet een bericht bevatten Your message is too long! Error while sending friendship request Uw bericht is te lang! Friend is already added Error while sending friendship request Vriend is al toegevoegd Groupchat %1 DesktopNotify New message Nieuw bericht Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen Formulier 10Mb Ausgelassen 0kb/s Ausgelassen ETA:10:10 Ausgelassen Filename Ausgelassen Bestandsnaam Waiting to send... file transfer widget Wachten voor te versturen… Accept to receive this file file transfer widget Bestand aanvaarden Location not writable Title of permissions popup Locatie niet schrijfbaar You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Ge hebt genen toegang voor een bestand op deze locatie op te slaan. Kies een andere locatie of annuleer het opslaan. Resuming... file transfer widget Hervatten… Cancel transfer Bestandsoverdracht annuleren Pause transfer Bestandsoverdracht pauzeren Paused file transfer widget Gepauzeerd Open file Bestand openen Open file directory Bestandsmap openen Resume transfer Bestandsoverdracht hervatten Accept transfer Bestandsoverdracht aanvaarden Save a file Title of the file saving dialog Bestand opslaan Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Overgedragen bestanden Downloads Uploads FriendListWidget Today Vandaag Yesterday Gisteren Last 7 days Afgelopen 7 dagen This month Deze maand Older than 6 Months Ouder dan 6 maand Never Nooit FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Vriendschapsverzoek Someone wants to make friends with you Iemand wilt u als vriend toevoegen User ID: Gebruikers-ID: Friend request message: Bericht voor vriendschapsverzoek: Accept Accept a friend request Aanvaarden Reject Reject a friend request Weigeren FriendWidget Invite to group Menu to invite a friend to a groupchat Uitnodigen in groep Move to circle... Menu to move a friend into a different circle Verplaatsen naar cirkel… To new circle Naar nieuwe cirkel Remove from circle '%1' Verwijderen uit cirkel ‘%1’ Move to circle "%1" Verplaatsen naar cirkel ‘%1’ Open chat in new window Gesprek openen in nieuw venster Remove chat from this window Gesprek verwijderen uit dit venster To new group Naar nieuwe groep Invite to group '%1' Uitnodigen in groep ‘%1’ Set alias... Alias instellen… Auto accept files from this friend context menu entry Bestanden van deze vriend automatisch aanvaarden Remove friend Menu to remove the friend from our friendlist Vriend verwijderen Show details Details tonen Choose an auto accept directory popup title Kies een map voor automatisch aanvaarde bestanden in op te slaan New message Nieuw bericht Online Away Afwezig Busy Bezet Offline Ausgelassen GeneralForm General Algemeen Choose an auto accept directory popup title Kies een map voor automatisch aanvaarde bestanden in op te slaan GeneralSettings General Settings Algemene instellingen The translation may not load until qTox restarts. De vertaling zal niet laden totdat qTox herstart. Language: Taal: Show system tray icon Pictogram tonen in systeemvak Enable light tray icon. toolTip for light icon setting Licht systeemvakpictogram inschakelen. Light icon Licht pictogram qTox will start minimized in tray. toolTip for Start in tray setting qTox zal geminimaliseerd naar het systeemvak starten. Start in tray Starten in systeemvak After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Indien ge op sluiten (X) klikt, zal qTox naar het systeemvak minimaliseren, in plek van af te sluiten. Close to tray Sluiten naar systeemvak After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Indien ge op minimaliseren (_) klikt, zal qTox naar het systeemvak minimaliseren, in plaats van naar den taakbalk. Minimize to tray Minimaliseren naar systeemvak Autostart Automatisch starten Set where files will be saved. Stel in waar dat bestanden opgeslagen worden. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Ge kunt dit per vriend instellen door met de rechtermuisknop op ne vriend te klikken. Autoaccept files Bestanden automatisch aanvaarden Set to 0 to disable Stel in op 0 voor uit te schakelen Your status is changed to Away after set period of inactivity. Uwe status zal automatisch op afwezig gezet worden na een bepaalde periode van inactiviteit. Auto away after (0 to disable): Automatisch afwezig na (0 voor uit te schakelen): Show contacts' status changes Toon statusverandering van contacten Start qTox on operating system startup (current profile). qTox starten bij opstarten van besturingssysteem (huidig profiel). Default directory to save files: Standaardlocatie voor bestanden in op te slaan: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Bericht versturen Smileys Send file(s) Bestand(en) versturen Send a screenshot Schermafdruk sturen Save chat log Chatgeschiedenis opslaan Clear displayed messages Getoonde berichten verwijderen Cleared Geleegd Quote selected text Geselecteerden tekst citeren Copy link address Koppelingsadres kopiëren Confirmation Bevestiging You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Gespreksgeschiedenis laden… Export to file Exporteren naar bestand GenericNetCamView Tox video Tox-video Show Messages Berichten tonen Hide Messages Berichten verbergen Full Screen Toggle video preview Mute audio Mute microphone Microfoon dempen End video call Videogesprek beëindigen Exit full screen GroupChatForm %1 has set the title to %2 %1 heeft den titel ingesteld op %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Groepen Create new group Nieuwe groep aanmaken Group invites Groepsuitnodigingen GroupInviteWidget Invited by %1 on %2 at %3. Uitgenodigd in %1, door %2, om %3. Join Deelnemen Decline Weigeren GroupWidget Set title... Stel den titel in… Open chat in new window Gesprek openen in nieuw venster Remove chat from this window Gesprek verwijderen uit dit venster Quit group Menu to quit a groupchat Groep verlaten %n user(s) in chat Number of users in chat New Message Online IdentitySettings Public Information Publieke informatie Tox ID Tox-ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Deze combinatie van tekens vertelt andere Tox-cliënten hoe ze contact met u moeten opnemen. Deel dit met uw vrienden voor te communiceren. Your Tox ID (click to copy) Uwen Tox-ID (klik voor te kopiëren) Profile Profiel Rename profile. tooltip for renaming profile button Profiel hernoemen. Go back to the login screen tooltip for logout button Ga terug naar het aanmeldingsscherm Logout import profile button Uitloggen Remove password Paswoord verwijderen Change password Paswoord wijzigen This QR code contains your Tox ID. You may share this with your friends as well. Deze QR-code bevat uwen Tox-ID. Ge kunt hem met uw vrienden delen. Save image Afbeelding opslaan Copy image Afbeelding kopiëren Rename rename profile button Hernoemen Delete profile. delete profile button tooltip Profiel verwijderen. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Exporteer uw Tox-profiel naar een bestand. Dit bestand bevat geen chatgeschiedenis. Export export profile button Exporteren Delete delete profile button Verwijderen Server Hide my name from the public list Verberg mijne naam in den openbare lijst Register Registreren Your password Uw paswoord Update Bijwerken Register on ToxMe Registreren op ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Naam van den ToxMe-dienst. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optioneel. Iets over uzelf, of over uw kat. Optional. Something about you. Or your cat. Tooltip for the Biography field. Optioneel. Iets over uzelf, of over uw kat. ToxMe service to register on. ToxMe-dienst voor op te registreren. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Indien niet ingesteld is uwe naam openbaar zichtbaar op ToxMe. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Verwijder uw paswoord en versleuteling van uw profiel. Name input Naaminvoer Name visible to contacts Naam zichtbaar voor contacten Status message input Invoer voor statusbericht Status message visible to contacts Statusbericht zichtbaar voor contacten Your Tox ID Uwen Tox-ID Save QR image as file QR-afbeelding opslaan als bestand Copy QR image to clipboard QR-afbeelding kopiëren naar klembord ToxMe username to be shown on ToxMe ToxMe-gebruikersnaam voor te tonen op ToxMe Optional ToxMe biography to be shown on ToxMe Optionele ToxMe-biografie voor te tonen op ToxMe ToxMe service address ToxMe-dienstadres Visibility on the ToxMe service Zichtbaarheid op den ToxMe-dienst Password Paswoord Update ToxMe entry ToxMe-invoer bijwerken Rename profile. Profiel hernoemen. Delete profile. Profiel verwijderen. Export profile Profiel exporteren Remove password from profile Paswoord van profiel verwijderen Change profile password Paswoord van profiel wijzigen My name: Mijne naam: My status: Mijne status: My username Mijne gebruikersnaam My biography Mijnen biografie My profile Mijn profiel LoadHistoryDialog Load History Dialog Geschiedenis laden Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Gebruikersnaam: Password: Paswoord: Confirm: Bevestig: Password strength: %p% Paswoordsterkte: %p% Create Profile Profiel aanmaken If the profile does not have a password, qTox can skip the login screen Als het profiel geen paswoord heeft kan qTox het aanmeldingsscherm overslaan Load automatically Automatisch laden Load Laden Load Profile Profiel laden New Profile Nieuw profiel Couldn't create a new profile Kon geen nieuw profiel aanmaken The username must not be empty. De gebruikersnaam mag niet leeg zijn. The password must be at least 6 characters long. Het paswoord moet minstens 6 tekens lang zijn. The passwords you've entered are different. Please make sure to enter same password twice. De paswoorden die ge hebt ingevoerd komen niet overeen. Voer twee keer hetzelfde paswoord in. A profile with this name already exists. Een profiel met deze naam bestaat al. Password protected profiles can't be automatically loaded. Profielen beschermd met paswoord kunnen niet automatisch worden geladen. Couldn't load profile Kon profiel niet laden There is no selected profile. You may want to create one. Der is geen profiel geselecteerd. Ge kunt der een aanmaken. Couldn't load this profile Kon dit profiel niet laden This profile is already in use. Dit profiel is al in gebruik. Wrong password. Verkeerd paswoord. Import Importeren Username input field Invoerveld voor gebruikersnaam Password input field, you can leave it empty (no password), or type at least 6 characters Paswoordinvoerveld, ge kunt dit leeg laten (geen paswoord), of minstens 6 tekens invoeren Password confirmation field Paswoordbevestigingsveld Create a new profile button Knop voor aanmaken van nieuw profiel Profile list Profiellijst List of profiles Lijst van profielen Password input Paswoordinvoer Load automatically checkbox Selectievakje voor automatisch laden Import profile Profiel importeren Load selected profile button Knop voor laden van geselecteerd profiel New profile creation page Pagina voor aanmaken van nieuw profiel Loading existing profile page Pagina voor laden van bestaand profiel MainWindow Your name Uwe naam Your status Uwe status ... Ausgelassen Add friends Vrienden toevoegen Create a group chat Groepsgesprek aanmaken View completed file transfers Bekijk voltooide bestandsoverdrachten Change your settings Verander uw instellingen Close Sluiten Open profile Profiel openen Open profile page when clicked Profielpagina openen wanneer aangeklikt Status message input Invoer voor statusbericht Set your status message that will be shown to others Stel uw statusbericht in dat aan anderen getoond zal worden Status Set availability status Stel beschikbaarheidsstatus in Contact search Zoeken naar contacten Contact search input for known friends Invoer voor zoeken naar gekende vrienden in contacten Sorting and visibility Sorteren en zichtbaarheid Set friends sorting and visibility Stel sorteren en zichtbaarheid van vrienden in Open Add friends page Open pagina voor toevoegen van vrienden Groupchat Groepsgesprek Open groupchat management page Open pagina voor beheer van groepsgesprek File transfers history Bestandsoverdrachtgeschiedenis Open File transfers history Open bestandsoverdrachtgeschiedenis Settings Instellingen Open Settings Open instellingen Nexus View OS X Menu bar Weergave Window OS X Menu bar Venster Minimize OS X Menu bar Minimaliseren Bring All to Front OS X Menu bar Breng alles naar de voorgrond Exit Fullscreen Volledig scherm verlaten Enter Fullscreen Volledig scherm gebruiken NotificationEdgeWidget Unread message(s) Ongelezen bericht Ongelezen berichten PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK INGESCHAKELD PrivacyForm Privacy Confirmation Bevestiging Do you want to permanently delete all chat history? Wilt ge voorgoed alle gespreksgeschiedenis verwijderen? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Uw vrienden kunnen op deze manier zien wanneer dat ge typt. Send typing notifications Typmeldingen sturen Keep chat history Gespreksgeschiedenis behouden NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam is een deel van uwen Tox-ID. Als ge gebombardeerd wordt met vriendschapsverzoeken is het verstandig voor uwe NoSpam te veranderen. Mensen die uwen ouden Tox-ID hebben kunnen u dan niet meer toevoegen, maar uwen huidige vriendenlijst wordt wel behouden. NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam is een deel van uwen Tox-ID dat veranderd kan worden. Verander de NoSpam als ge gebombardeerd wordt met vriendschapsverzoeken. Generate random NoSpam Genereer ne willekeurige NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Het opslaan van gespreksgeschiedenis is nog in ontwikkeling. Het is mogelijk dat er zich veranderingen in het formaat voordoen, wat kan leiden tot gegevensverlies. Privacy BlackList Zwarte lijst Filter group message by group member's public key. Put public key here, one per line. Groepsberichten filteren op publieke sleutel van groepslid. Plaats hier ene publieke sleutel per regel. Profile Failed to derive key from password, the profile won't use the new password. Sleutel ophalen van paswoord mislukt, het profiel zal het nieuw paswoord niet gebruiken. Couldn't change password on the database, it might be corrupted or use the old password. Kon paswoord van databank niet wijzigen, het is mogelijk beschadigd of gebruikt het oud paswoord. Toxing on qTox Toxt met qTox ProfileForm Choose a profile picture Kies ne profielfoto Error Fout Rename "%1" renaming a profile ‘%1’ hernoemen Unable to open this file. Kan dit bestand niet openen. Current profile: Huidig profiel: Remove Verwijderen Unable to read this image. Kan deze foto niet lezen. The supplied image is too large. Please use another image. De geselecteerde afbeelding is te groot. Gebruik een andere afbeelding. Couldn't rename the profile to "%1" Kon het profiel niet hernoemen naar ‘%1’ Location not writable Title of permissions popup Locatie niet schrijfbaar You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Ge hebt geen toelating voor een bestand op deze locatie op te slaan. Kies een andere locatie of annuleer het opslaan. Failed to copy file Kopiëren van bestand mislukt The file you chose could not be written to. Het bestand dat ge hebt gekozen kan niet geschreven worden. Really delete profile? deletion confirmation title Het profiel echt verwijderen? Nothing to remove Niks te verwijderen Your profile does not have a password! Uw profiel heeft geen paswoord! Really delete password? deletion confirmation title Het paswoord echt verwijderen? Please enter a new password. Voer een nieuw paswoord in. Are you sure you want to delete this profile? deletion confirmation text Zijt ge zeker dat ge dit profiel wilt verwijderen? Save save qr image Opslaan Save QrCode (*.png) save dialog filter QR-code opslaan (*.png) Files could not be deleted! deletion failed title Bestanden konden niet verwijderd worden! Register (processing) Registreren (in verwerking) Update (processing) Update (in verwerking) Done! Klaar! Account %1@%2 updated successfully Account %1@%2 is bijgewerkt Successfully added %1@%2 to the database. Save your password %1@%2 is toegevoegd aan den databank. Sla uw paswoord op Toxme error ToxMe-fout Register Registreren Update Change password button text Paswoord wijzigen Set profile password button text Profielpaswoord instellen Current profile location: %1 Huidige profiellocatie: %1 Couldn't change password Kon paswoord niet wijzigen This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Dezen hoop tekens vertelt andere Tox-cliënten hoe dat ze u moeten contacteren. Deel hem met uw vrienden voor te communiceren. Dezen ID bevat de NoSpam-code (in het blauw) en de controlesom (in het grijs). Empty path is unavaliable Leeg pad is niet beschikbaar Failed to rename Hernoemen mislukt Profile already exists Profiel bestaat al A profile named "%1" already exists. Een profiel met de naam ‘%1’ bestaat al. Empty name Lege naam Empty name is unavaliable Lege naam is niet beschikbaar Empty path Leeg pad Couldn't change password on the database, it might be corrupted or use the old password. Kon paswoord van databank niet wijzigen, het is mogelijk beschadigd of gebruikt het oud paswoord. Export profile Profiel exporteren Tox save file (*.tox) save dialog filter Tox-opslagbestand (*.tox) The following files could not be deleted: deletion failed text part 1 Volgende bestanden konden niet verwijderd worden: Please manually remove them. deletion failed text part 2 Verwijder ze handmatig. Are you sure you want to delete your password? deletion confirmation text Zijt ge zeker dat ge uw paswoord wilt verwijderen? Images (%1) filetype filter Afbeeldingen (%1) ProfileImporter Import profile import dialog title Profiel importeren Tox save file (*.tox) import dialog filter Tox-opslagbestand (*.tox) Ignoring non-Tox file popup title Niet-Tox-bestand wordt genegeerd Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Waarschuwing: ge hebt een bestand gekozen dat geen Tox-opslagbestand is; dit bestand wordt genegeerd. Profile already exists import confirm title Profiel bestaat al A profile named "%1" already exists. Do you want to erase it? import confirm text Der bestaat al een profiel met de naam ‘%1’. Wilt ge het verwijderen? File doesn't exist Bestand bestaat niet Profile doesn't exist Profiel bestaat niet Profile imported Profiel geïmporteerd %1.tox was successfully imported %1.tox is geïmporteerd QApplication Ok Oké Cancel Annuleren Yes Ja No Nee LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend Kon vriend niet toevoegen %1 is not a valid Toxme address. %1 is geen geldig ToxMe-adres. You can't add yourself as a friend! When trying to add your own Tox ID as friend Ge kunt uzelf niet als vriend toevoegen! QObject Tox URI to parse Te verwerken Tox-URI Starts new instance and loads specified profile. Start nieuwe instantie en laadt specifiek profiel. profile profiel Default Standaard Blue Blauw Olive Olijf Red Rood Violet Incoming call... Inkomenden oproep… %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Het is hier %1! Tox met mij! None No camera device set Geen Desktop Desktop as a camera input for screen sharing Bureaublad Server doesn't support Toxme Server biedt geen ondersteuning voor ToxMe You're making too many requests. Wait an hour and try again Ge doet te veel verzoeken. Wacht een uur en probeer het opnieuw This name is already in use Deze naam is al in gebruik This Tox ID is already registered under another name Dezen Tox-ID is al geregistreerd onder nen andere naam Please don't use a space in your name Gebruik geen spaties in uwe naam Password incorrect Paswoord verkeerd You can't use this name Ge kunt deze naam niet gebruiken Name not found Naam niet gevonden Tox ID not sent Tox-ID niet verstuurd That user does not exist Die gebruiker bestaat niet Error Fout qTox couldn't open your chat logs, they will be disabled. qTox kon uw gespreksgeschiedenis niet openen, ze zal uitgeschakeld worden. Problem with HTTPS connection Probleem met HTTPS-verbinding Internal ToxMe error Interne ToxMe-fout Reformatting text in progress.. Tekst wordt opnieuw geformatteerd… Starts new instance and opens the login screen. Start nieuwe instantie en opent aanmeldingsscherm. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status away contact status afwezig busy contact status bezet offline contact status blocked contact status RemoveFriendDialog Remove friend Vriend verwijderen Also remove chat history Gespreksgeschiedenis ook verwijderen Remove Verwijderen Are you sure you want to remove %1 from your contacts list? Zijt ge zeker dat ge %1 uit uw contactenlijst wilt verwijderen? Remove all chat history with the friend if set Indien ingesteld alle gespreksgeschiedenis met de vriend verwijderen ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Klik en sleep voor een gebied te selecteren. Druk op %1 voor het qTox-venster te verbergen/herstellen, of %2 voor te annuleren. Space [Space] key on the keyboard Spatie Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Druk op %1 voor ne schermafdruk van de selectie te sturen, %2 voor het qTox-scherm te verbergen/herstellen, of %3 voor te annuleren. Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Formulier Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Stel uw paswoord in Confirm: Bevestig: Password: Paswoord: Password strength: %p% Paswoordsterkte: %p% The password is too short Het paswoord is te kort The password doesn't match. Het paswoord komt niet overeen. Confirm password Paswoord bevestigen Confirm password input Paswoordinvoerbevestiging Password input Paswoordinvoer Password input field, minimum 6 characters long Paswoordinvoerveld, minimaal 6 tekens Settings Circle #%1 Cirkel #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Vriend toevoegen Do you want to add %1 as a friend? Wilt ge %1 als vriend toevoegen? User ID: Gebruikers-ID: Friend request message: Bericht voor vriendschapsverzoek: Send Send a friend request Versturen Cancel Don't send a friend request Annuleren UserInterfaceForm None Geen User Interface Gebruikersinterface UserInterfaceSettings Chat Base font: Basislettertype: px Size: Grootte: New text styling preference may not load until qTox restarts. qTox moet mogelijk herstarten voor de nieuwe tekststijlvoorkeur van kracht te laten gaan. Text Style format: Tekststijlformaat: Select text styling preference. Selecteer nen tekststijlvoorkeur. Plaintext Platte tekst Show formatting characters Formatteringstekens tonen Don't show formatting characters Formatteringstekens niet tonen New message Nieuw bericht Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting qTox-venster openen wanneer ge een bericht ontvangt en der nog geen venster open is. Open window Venster openen Contact list Contactenlijst If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Indien geselecteerd zullen groepsgesprekken bovenaan de vriendenlijst gezet worden, anders komen ze onder online vrienden. Place groupchats at top of friend list Groepsgesprekken bovenaan vriendenlijst plaatsen Your contact list will be shown in compact mode. toolTip for compact layout setting Uwe contactenlijst zal in compacte modus getoond worden. Compact contact list Compacte contactenlijst Multiple windows mode Meerderevenstersmodus Open each chat in an individual window Elk gesprek openen in apart venster Emoticons Use emoticons Emoticons gebruiken Smiley Pack: Text on smiley pack label Smileypakket: Emoticon size: Emoticongrootte: px Theme Thema Style: Stijl: Theme color: Themakleur: Timestamp format: Tijdsaanduiding: Date format: Datumformaat: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Indien ingeschakeld krijgt elk contact zonder profielfoto een automatisch gegenereerde afbeelding gebaseerd op hunnen Tox-ID, in plek van ne standaardfoto. Toepassen vereist nen herstart. Use identicons instead of empty avatars Identicons gebruiken in plaats van lege profielfoto’s Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Geluid afspelen Play sound while Busy Geluid afspelen indien Bezet Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Away Button to set your status to 'Away' Afwezig Busy Button to set your status to 'Busy' Bezet toxcore failed to start, the application will terminate after you close this message. toxcore kon niet opstarten, de toepassing zal afsluiten na het sluiten van dit bericht. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore kon niet opstarten met deze proxyinstellingen. Hierdoor kan qTox niet starten. Verander uw instellingen en herstart. File Bestand Edit Profile Profiel bewerken Change Status Status wijzigen Log out Uitloggen Edit Bewerken Logout Tray action menu to logout user Uitloggen Exit Tray action menu to exit tox Afsluiten Filter... Filteren… Contacts Contacten Add Contact... Contact toevoegen… Next Conversation Volgend gesprek Previous Conversation Vorig gesprek Executable file popup title Uitvoerbaar bestand You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Ge hebt qTox gevraagd voor een uitvoerbaar bestand te openen. Uitvoerbare bestanden kunnen schade toebrengen aan uwe computer. Zijt ge zeker dat ge dit bestand wilt openen? Couldn't request friendship Kon geen vriendschapsverzoek sturen Status Your name Uwe naam Message failed to send Bericht kon niet verstuurd worden Create new group... Nieuwe groep aanmaken… Add new circle... Nieuwe cirkel toevoegen… %n New Friend Request(s) %n nieuw vriendschapsverzoek %n nieuwe vriendschapsverzoeken %n New Group Invite(s) %n nieuwe groepsuitnodiging %n nieuwe groepsuitnodigingen By Name Op naam By Activity Op activiteit All Alle Online Offline Ausgelassen Friends Vrienden Groups Groepen Search Contacts Contacten doorzoeken Groupchat #%1 Groepsgesprek #%1 Show Tray action menu to show qTox window Tonen Add friend title of the window Vriend toevoegen Group invites title of the window Groepsuitnodigingen File transfers title of the window Bestandsoverdrachten Settings title of the window Instellingen My profile title of the window Mijn profiel Failed to send file "%1" Kon bestand ‘%1’ niet verzenden File sent sent you a friend request. invites you to join a group. qTox/translations/no_nb.ts000066400000000000000000003256551415623743500162270ustar00rootroot00000000000000 AVForm Audio/Video Audio/Video Default resolution Forvalgt oppløsning Disabled Avskrudd Select region Velg regio Screen %1 Skjerm %1 Audio Settings Lydinnstillinger Gain Forsterkningsnivå Playback device Avspillingsenhet Use slider to set volume of your speakers. Bruk glidebryteren for å sette lydstyrken på høytalerne dine. Capture device Opptaksenhet Volume Volum Video Settings Videoinnstillinger Video device Videoenhet Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Sett oppløsningen for ditt kamera. Høyere verdier øker sjansen for at dine venner får bedre videokvalitet. NB! Høyere videokvalitet krever raskere internett-tilkobling. Det kan oppstå problemer med videosamtalene hvis du har valgt høyere videokvalitet enn hva din internett-tilkoblingen klarer å levere. Resolution Oppløsning Rescan devices Se etter enheter Test Sound Testlyd Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Skrur på eksperimentell lyd-bakende med ekkokanselleringsstøtte. qTox må startes på nytt for at dette skal tre i effekt. Enable experimental audio backend Skru på eksperimentell lyd-bakende Audio quality Lydkvalitet Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Sendt lydkvalitet. Senk denne hvis din båndbredde ikke er høy nok, eller hvis du ønsker å senke databruken. High (64 kbps) Høy (64 kbps) Medium (32 kbps) Middels (32 kbps) Low (16 kbps) Lav (16 kbps) Very low (8 kbps) Veldig lav (8 kbps) Threshold Terskel AboutForm About Om Original author: %1 Opprinnelig utvikler: %1 You are using qTox version %1. Du bruker qTox versjon %1. Commit hash: %1 Innsendelsessjekksum: %1 toxcore version: %1 toxcore-versjon: %1 Qt version: %1 Qt-versjon: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` En liste over alle kjente feil er å finne på vår %1 på GitHub. Hvis du oppdager en feil eller sikkerhetssårbarhet i qTox, rapporter det inn i henhold til retningslinjene i vår wiki-artikkel om %2. Click here to report a bug. Klikk her for å rapportere feil. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Se en fullstendig liste over %1 på GitHub bug-tracker Replaces `%1` in the `A list of all known…` feilrapporteringsside Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Hvordan skrive nyttige feilrapporter contributors Replaces `%1` in `See a full list of…` bidragsytere AboutFriendForm Dialog Dialogvindu username brukernavn status message statusmelding Used aliases: Brukte alias: HISTORY OF ALIASES ALIASHISTORIKK Automatically accept files from contact if set Automatisk godta filer fra kontakt hvis valgt Auto accept files Godta filer automatisk Default directory to save files: Standard mappe for fillagring: Auto accept for this contact is disabled Automatisk filgodkjenning for denne kontakten er deaktivert Auto accept call: Svar på samtale automatisk: Manual Manuell Audio Lyd Audio + Video Lyd og video Automatically accept group chat invitations from this contact if set. Godta gruppesamtaleinvitasjoner fra denne kontakten automatisk hvis valgt. Auto accept group invites Godta gruppeinvitasjoner automatisk Remove history (operation can not be undone!) Fjern historikk (kan ikke omgjøres!) Notes Notiser Input field for notes about the contact Inndatafelt for notiser om denne kontakten You can save comment about this contact here. Du kan lagre en kommentar om denne brukeren her. History removed Historikk fjernet Choose an auto accept directory popup title Velg en filsti for auto-aksepterte filer <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Dette er den offentlige nøkkelen tilhørende din venn, bruk den til å bekrefte identiteten via en annen kanal. Du kan ikke sende dette til andre folk slik at de kan legge til denne kontakten.</p></body></html> Public key (not ToxID): Offentlig nøkel (ikke Tox ID): Confirmation Bekreftelse Are you sure to remove %1 chat history? Er du sikker på at du ønsker å fjerne %1 sludrehistorikk? Failed to remove chat history with %1! Klarte ikke å fjerne sludrehistorikk med %1. AboutSettings Version Versjon License Lisens Authors Utviklere Known Issues Kjente problemer Open update download link Åpne oppdateringsnedlastingslenke Update available Oppgradering tilgjengelig qTox is up to date ✓ qTox er av nyeste dato ✓ AddFriendForm Add Friends Legg til venner Send friend request Send venneforespørsel Couldn't add friend Kunne ikke legge til venn Invalid Tox ID format Ugyldig format for Tox-ID Add a friend Legg til en venn Friend requests Venneforespørsler Accept Godta Reject Avvis Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, enten 76 heksadesimale tegn eller navn@eksempel.no Type in Tox ID of your friend Skriv inn Tox-ID tilhørende din venn Friend request message Venneforespørselsmelding Type message to send with the friend request or leave empty to send a default message Skriv melding for å legge ved venneforespørselen, eller la stå tom for å sende forvalgt melding %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID er ugyldig eller så finnes den ikke You can't add yourself as a friend! When trying to add your own Tox ID as friend Du kan ikke legge deg selv til som en venn! Open contact list Åpne kontaktliste Couldn't open file Kunne ikke åpne fil Couldn't open the contact file Error message when trying to open a contact list file to import Kunne ikke åpne kontaktfil Invalid file Ugyldig fil We couldn't find any contacts to import in this file! Vi kunne ikke finne noen kontakter å importere fra filen! Tox ID Tox ID of the person you're sending a friend request to Tox-ID either 76 hexadecimal characters or name@example.com Tox ID format description enten 76 heksadesimale tegn eller navn@eksempel.no Message The message you send in friend requests Melding Open Button to choose a file with a list of contacts to import Åpne Send friend requests Send venneforespørsler %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 her! Tox meg kanskje? Import a list of contacts, one Tox ID per line Importer en liste med kontakter, én Tox ID per linje Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Klar til å importere %n kontakt, klikk send for å bekrefte Klar til å importere %n kontakter, klikk send for å bekrefte Import contacts Importer kontakter AdvancedForm Advanced Avansert Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Med mindre du %1 vet hva du gjør, %2 gjør endringer her. Endringer gjort her kan føre til problemer med qTox, og selv datatap, f.eks. historikk. really virkelig not ikke IMPORTANT NOTE VIKTIG NOTIS Reset settings Tilbakestill innstillinger All settings will be reset to default. Are you sure? Alle innstillinger vil bli stilt tilbake til forvalg. Er du sikker? Yes Ja No Nei Call active popup title Pågående samtale You can't disconnect while a call is active! popup text Du kan ikke koble fra mens en samtale pågår! Save File Lagre fil Logs (*.log) Loggføring (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox beskriver gjørToxBærbar avkrysningsboks Lagre instillinger til arbeidsmappen i stedet for den vanlige konfigurasjonsmappen Make Tox portable Gjør Tox bærbar Reset to default settings Reset til standardinstillinger Portable Bærbar Connection Settings Tilkoblingsinnstillinger Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Skru på IPv6 (anbefalt) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Å skru av dette av tillater f.eks. toxing over Tor. Det legger derimot en last på Tox-nettverket, så bare skru det på når dette når er nødvendig. Enable UDP (recommended) Text on checkbox to disable UDP Skru på UDP (anbefalt) Proxy type: Mellomtjenertype: Address: Text on proxy addr label Adresse: Port: Text on proxy port label Port: None Ingen SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Koble til igjen Debug Feilrett Export Debug Log Eksporter feilrettingslogg Copy Debug Log Kopier feilrettingslogg Enable LAN discovery Skru på LAN-oppdagelse ChatForm Send a file Send en fil qTox wasn't able to open %1 qTox kunne ikke åpne %1 %1 calling %1 ringer Failed to open temporary file Temporary file for screenshot Midlertidig fil for skjermbilde Mislyktes å åpne midlertidig fil qTox wasn't able to save the screenshot qTox kunne ikke lagre skjermbilde Call with %1 ended. %2 Samtale med %1 avsluttet. %2 Call duration: Samtalens varighet: Unable to open Klarte ikke å åpne Bad idea Dårlig idé Calling %1 Ringer %1 %1 is typing %1 skriver Copy Kopier You're trying to send a sequential file, which is not going to work! Du prøver å sende en sekvensiell fil, men det kommer ikke til å gå! %1 is now %2 e.g. "Dubslow is now online" %1 er nå %2 Call with %1 ended unexpectedly. %2 Uventet slutt på samtale med %1. %2 Filename contained illegal characters Filnavnet inneholdt ulovlige tegn Illegal characters have been changed to _ so you can save the file on windows. Ulovlige tegn har blitt endret til _ slik at du kan lagre filen på Windows. ChatFormHeader Can't start audio call Kan ikke ringe Start audio call Start lydsamtale End audio call Legg på Cancel audio call Avbryt lydsamtale Accept audio call Godta lydsamtale Can't start video call Kan ikke starte videosamtale Start video call Start videosamtale End video call Avslutt videosamtale Cancel video call Avbryt videosamtale Accept video call Godta videosamtale Sound can be disabled only during a call Lyd kan bare skrus av under en samtale Unmute call Deaktiver demping av samtale Mute call Demp samtale Microphone can be muted only during a call Mikrofonen kan bare slås av under en samtale Unmute microphone Deaktiver demping av mikrofon Mute microphone Demp mikrofon ChatLog Copy Kopier Select all Velg alle pending i påvente ChatTextEdit Type your message here... Skriv din melding her... CircleWidget Rename circle Menu for renaming a circle Gi sirkelen nytt navn Remove circle Menu for removing a circle Fjern sirkel Open all in new window Åpne alle i nytt vindu Core /me offers friendship, "%1" /me tilbyr vennskap, "%1" Invalid Tox ID Error while sending friendship request Ugyldig Tox-ID You need to write a message with your request Error while sending friendship request Du må legge en melding ved din forespørsel Your message is too long! Error while sending friendship request Din melding er for lang! Friend is already added Error while sending friendship request Kontakt allerede lagt til Groupchat %1 Gruppesludring %1 DesktopNotify New message Ny melding Incoming file transfer Innkommende filoverføring Friend request received Venneforespørsel mottatt New group message Ny gruppemelding Group invite received Gruppeinvitasjon mottatt FileTransferWidget Form Skjema 10Mb 0kb/s ETA:10:10 Filename Filnavn Waiting to send... file transfer widget Venter på å sende... Accept to receive this file file transfer widget Aksepter til å motta denne filen Location not writable Title of permissions popup Lokasjon ikke skrivbar You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Du har ikke skriverettigheter til den lokasjonen. Velg en annen, eller avbryt lagringsdialogen. Resuming... file transfer widget Fortsetter... Cancel transfer Avbryt filoverføring Pause transfer Pause filoverføring Resume transfer Gjenoppta filoverføring Accept transfer Aksepter filoverføring Save a file Title of the file saving dialog Lagre en fil Paused file transfer widget Satt på pause Open file Åpne fil Open file directory Åpne filsystem Remote Paused file transfer widget Annensteds fra pauset FilesForm Downloads Nedlastinger Uploads Opplastinger Transferred Files "Headline" of the window Overførte filer FriendListWidget Today I dag Yesterday I går Last 7 days Siste 7 dager This month Denne måneden Older than 6 Months Eldre enn 6 måneder Never Aldri FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Venneforespørsel Someone wants to make friends with you Noen vil være venn med deg User ID: Bruker-ID: Friend request message: Venneforespørselsmelding: Accept Accept a friend request Aksepter Reject Reject a friend request Avvis FriendWidget Invite to group Menu to invite a friend to a groupchat Inviter til gruppe Move to circle... Menu to move a friend into a different circle Flytt til sirkel… To new circle Til ny sirkel Remove from circle '%1' Fjern fra sirkel "%1" Move to circle "%1" Flytt til sirkelen "%1" Set alias... Sett alias... Auto accept files from this friend context menu entry Auto-aksepter filer fra denne kontakten Remove friend Menu to remove the friend from our friendlist Fjern kontakt Choose an auto accept directory popup title Velg en mappe for auto-aksepterte filer New message Ny melding Online Pålogget Away Borte Busy Opptatt Offline Avlogget Open chat in new window Åpne sludring i nytt vindu Remove chat from this window Fjern sludring fra dette vinduet To new group Til ny gruppe Invite to group '%1' Inviter til gruppen "%1" Show details Vis detaljer GeneralForm General Generelt Choose an auto accept directory popup title Velg en mappe for auto-aksepterte filer GeneralSettings General Settings Generelle Instillinger The translation may not load until qTox restarts. Oversettelsen trer kanskje ikke i kraft før qTox starter om. Language: Språk: qTox will start minimized in tray. toolTip for Start in tray setting qTox starter minimert i statusfeltet. Start in tray Start i statusfeltet Show system tray icon Vis ikonet i statusfeltet After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Etter å ha valgt å lukke programmet (X) vil qTox minimeres til statusfeltet, i stedet for å lukke seg selv. Close to tray Lukk til statusfeltet Enable light tray icon. toolTip for light icon setting Aktiver lyst ikon i statusfeltet. Light icon Lyst ikon After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Etter å ha valg å minimere (_) vil qTox minimere seg til statusfeltet i stedet for til oppgavelinjen. Minimize to tray Minimer til statusfeltet Your status is changed to Away after set period of inactivity. Din status vil bli endret til Borte etter følgende periode med inaktivitet. Auto away after (0 to disable): Automatisk Borte etter (0 for å deaktivere): Set to 0 to disable Sett til 0 for å deaktivere Autostart Autostart You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Du kan velge dette på en per-kontakt-basis ved å høyreklikke på de. Set where files will be saved. Velg hvor filene blir lagret. Autoaccept files Godta filoverføringer automatisk Show contacts' status changes Vis kontakt statusendringer Start qTox on operating system startup (current profile). Start qTox ved oppstart (gjeldende profil). Default directory to save files: Standard filsti for lagring av filer: Check for updates Se etter nye versjoner Spell checking Stavekontroll Max autoaccept file size (0 to disable): Maksimal automatisk godkjente filstørrelse (0 for å skru av): MB MB GenericChatForm Send message Send melding Smileys Smilefjes Send file(s) Send fil(er) Send a screenshot Send et skjermbilde Save chat log Lagre chat-logg Clear displayed messages Fjern viste meldinger Cleared Fjernet Quote selected text Siter valgt tekst Copy link address Kopier lenkeadresse Confirmation Bekreftelse You are sure that you want to clear all displayed messages? Er du sikker på at du ønsker å tømme alle viste meldinger? Search in text Søk i tekst Go to current date Gå til nåværende dato Load chat history... Last inn samtalehistorikk… Export to file Eksporter til fil GenericNetCamView Tox video Video-Tox Show Messages Vis meldinger Hide Messages Skjul meldinger Full Screen Fullskjermsvisning Toggle video preview Veksle videoforhåndsvisning Mute audio Forstum lyd Mute microphone Demp mikrofon End video call Avslutt videosamtale Exit full screen Avslutt fullskjermsvisning GroupChatForm %1 has set the title to %2 %1 har endret tittelen til %2 %1 has joined the group %1 har tatt del i gruppen %1 is now known as %2 %1 går nå under navnet %2 %1 has left the group %1 har forlatt gruppen %n user(s) in chat Number of users in chat %n bruker i sludringen %n brukere i sludringen mute forstum unmute opphev forstumming GroupInviteForm Groups Grupper Create new group Opprett ny gruppe Group invites Gruppeinvitasjoner GroupInviteWidget Invited by %1 on %2 at %3. Invitert av %1 den %2 klokken %3. Join Ta del Decline Avslå GroupWidget Set title... Velg tittel... Quit group Menu to quit a groupchat Avslutt gruppe Open chat in new window Åpne sludring i nytt vindu Remove chat from this window Fjern sludring fra dette vinduet %n user(s) in chat Number of users in chat %n bruker i sludringen %n brukere i sludringen New Message Ny melding Online Pålogget IdentitySettings Public Information Offentlig Informasjon Tox ID Tox-ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Denne raden med tegn forteller andre Tox-klienter hvordan de skal kontakte deg. Del den med venner du vil kommunisere med. Your Tox ID (click to copy) Din Tox-ID (klikk for å kopiere) Profile Profil Rename profile. tooltip for renaming profile button Endre navn på profil. Go back to the login screen tooltip for logout button Gå tilbake til innloggingsskjermen Logout import profile button Logg ut Remove password Fjern passord Change password Bytt passord This QR code contains your Tox ID. You may share this with your friends as well. Denne QR-koden inneholder din Tox-ID. Du kan også dele denne med dine venner. Save image Lagre bilde Copy image Kopier bilde Rename rename profile button Endre navn Delete profile. delete profile button tooltip Slett profil. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Lar deg eksportere Tox-profilen din til en fil. Export export profile button Eksporter Delete delete profile button Slett Server Tjener Hide my name from the public list Skjul mitt navn fra offentlig navneliste Register Registrer Your password Ditt passord Update Oppdater Register on ToxMe Registrer på ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Navn på ToxMe-tjeneste. Optional. Something about you. Or your cat. Tooltip for the Biography text. Valgfri. Noe om deg. Eller din katt. Optional. Something about you. Or your cat. Tooltip for the Biography field. Valgfri. Noe om deg. Eller din katt. ToxMe service to register on. ToxMe-tjeneste å registrere på. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Hvis ikke satt, er ToxMe-oppføringer synlige offentlig. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Fjern passordet og krypteringen fra din profil. Name input Navneinndata Name visible to contacts Navn synlig for kontakter Status message input Statusmeldingsinndata Status message visible to contacts Statusmelding synlig for kontakter Your Tox ID Din Tox-ID Save QR image as file Lagre QR-bilde som fil Copy QR image to clipboard Kopier QR-bilde til utklippstavle ToxMe username to be shown on ToxMe ToxMe-brukernavn å vise på ToxMe Optional ToxMe biography to be shown on ToxMe Valgri ToxMe-biografi å vise på ToxMe ToxMe service address Tjenesteadresse for ToxMe Visibility on the ToxMe service Synlighet på ToxMe-tjeneste Password Passord Update ToxMe entry Oppdater ToxMe-oppføring Rename profile. Endre navn på profil. Delete profile. Slett profil. Export profile Eksporter profil Remove password from profile Fjern passord fra profil Change profile password Endre profilpassord My name: Mitt navn: My status: Min status: My username Mitt brukernavn My biography Min biografi My profile Min profil LoadHistoryDialog Load History Dialog Last Historikk-dialog Load history Last inn historikk from fra to til (about 100 messages are loaded) (omtrent 100 meldinger innlastet) Select Date Dialog Velg datodialog Select a date Velg en dato LoginScreen Username: Brukernavn: Password: Passord: Confirm: Bekreft: Password strength: %p% Passordsstyrke: %p% If the profile does not have a password, qTox can skip the login screen Hvis profilen ikke har et passord kan qTox hoppe over innlogging New Profile Ny profil Couldn't create a new profile Kunne ikke opprette ny profil The username must not be empty. Brukernavnet kan ikke være tomt. The password must be at least 6 characters long. Passordet må være minst 6 tegn langt. The passwords you've entered are different. Please make sure to enter same password twice. De oppgitte passordene samsvarer ikke. Skriv inn samme passord to ganger. A profile with this name already exists. En profil med dette navnet finnes allerede. Couldn't load this profile Kunne ikke laste inn profil This profile is already in use. Denne profilen er allerede i bruk. Wrong password. Feil passord. Create Profile Opprett profil Load automatically Last inn automatisk Import Importer Load Last inn Load Profile Last inn profil Password protected profiles can't be automatically loaded. Passordbeskyttede profiler kan ikke lastes inn automatisk. Couldn't load profile Kunne ikke laste inn profil There is no selected profile. You may want to create one. Ingen profil valgt. Det kan hende du ønsker å opprette en. Username input field Inndatafelt for brukernavn Password input field, you can leave it empty (no password), or type at least 6 characters Inndatafelt for brukernavn, du kan la det stå tomt (inget passord), eller skrive minst 6 tegn Password confirmation field Passordbekreftelsesfelt Create a new profile button Opprett en ny profilknapp Profile list Profilliste List of profiles Liste over profiler Password input Passordinnmating Load automatically checkbox Merk avkryssningsboks automatisk Import profile Importer profil Load selected profile button Last valgt profilknapp New profile creation page Side for opprettelse av ny profil Loading existing profile page Laster eksisterende profilside MainWindow Your name Ditt navn Your status Din status ... Add friends Legg til kontakt Create a group chat Lag en gruppesamtale View completed file transfers Vis ferdige filoverføringer Change your settings Endre dine innstillinger Close Lukk Open profile Åpne profil Open profile page when clicked Åpne profilside ved klikk Status message input Statusmeldingsinndata Set your status message that will be shown to others Sett din statusmelding, slik vist til andre Status Status Set availability status Sett tilgjengelighetsstatus Contact search Kontaktsøk Contact search input for known friends Kontaktsøkinndata for kjente venner Sorting and visibility Sortering og synlighet Set friends sorting and visibility Sett vennesortering og synlighet Open Add friends page Åpne "Legg til venner"-siden Groupchat Gruppesludring Open groupchat management page Åpne behandlingssiden gruppesludring File transfers history Filoverføringshistorikk Open File transfers history Åpne filoverføringshistorikk Settings Innstillinger Open Settings Åpne innstillinger Nexus View OS X Menu bar Vis Window OS X Menu bar Vindu Minimize OS X Menu bar Minimer Bring All to Front OS X Menu bar Bring alle til forgrunnen Exit Fullscreen Gå ut av fullskjermsvisning Enter Fullscreen Fullskjermsvisning NotificationEdgeWidget Unread message(s) Ulest melding Uleste meldinger PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK PÅSLÅTT PrivacyForm Privacy Personvern Confirmation Bekreftelse Do you want to permanently delete all chat history? Ønsker du å slette all sludrehistorikk for godt? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Dine kontakter vil kunne se når du holder på å skrive. Send typing notifications Send forvarsel om skriving Keep chat history Behold samtalehistorikk NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam er en del av din Tox-ID. Hvis du blir nedrent av venneforespørsler burde du endre din NoSpam. Folk vil ikke kunne legge deg til med din gamle ID, men du kan beholde vennene dine. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam er en del av din ID som kan endres for eget forgodtbefinnende. Hvis du blir nedrent av venneforespørsler, endre din NoSpam. Generate random NoSpam Generer tilfeldig NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Samtalehistorikk er fortsatt under utvikling. Endringer av lagringsformat er mulig, som kan forårsake data tap. Privacy Personvern BlackList Svarteliste Filter group message by group member's public key. Put public key here, one per line. Filtrer gruppemelding etter gruppemedlemmets offentlige nøkkel. Putt offentlig nøkkel her, én per linje. Profile Failed to derive key from password, the profile won't use the new password. Klarte ikke å utlede nøkkel fra passord, profilen vil ikke bruke det nye passordet. Couldn't change password on the database, it might be corrupted or use the old password. Kunne ikke endre passord på databasen, den kan være skadet eller benytte seg av det gamle passordet. Toxing on qTox Toxer på qTox ProfileForm Choose a profile picture Velg et profilbilde Error Feilmelding Unable to open this file. Kunne ikke åpne filen. Unable to read this image. Kunne ikke lese bildet. Rename "%1" renaming a profile Endre navn på "%1" The supplied image is too large. Please use another image. Angitt bilde er for stort. Velg et annet bilde. Couldn't rename the profile to "%1" Kunne ikke endre navn på profilen til "%1" Location not writable Title of permissions popup Lokasjon er ikke skrivbar You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Du har ikke skriverettigheter for den lokasjonen. Velg en annen, eller avbryt lagringsdialogen. Failed to copy file Mislyktes å kopiere fil The file you chose could not be written to. Kunne ikke skrive til filen du valgte. Really delete profile? deletion confirmation title Vil du virkellig slette profilen? Nothing to remove Ingenting å fjerne Your profile does not have a password! Profilen din har inget passord! Really delete password? deletion confirmation title Vil du virkelig slette passordet? Please enter a new password. Skriv inn nytt passord. Are you sure you want to delete this profile? deletion confirmation text Er du sikker du vil slette denne profilen? Save save qr image Lagre Save QrCode (*.png) save dialog filter Lagre QR-kode (*.png) Current profile: Gjeldende profil: Remove Fjern Files could not be deleted! deletion failed title Filer kunne ikke slettes! Register (processing) Registrer (behandler) Update (processing) Oppdatering (behandler) Done! Ferdig! Account %1@%2 updated successfully Kontoen %1@%2 ble oppdatert Successfully added %1@%2 to the database. Save your password %1@%2 lagt til i databasen. Lagre passordet ditt Toxme error ToxMe-feil Register Registrer Update Oppdater Change password button text Bytt passord Set profile password button text Sett profilpassord Current profile location: %1 Gjeldende profilplassering: %1 Couldn't change password Kunne ikke endre passord This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Dette knippet tegn forteller Tox-klienter hvordan de skal koble til deg. Del det med dine venner for å kommunisere. Denne ID-en inkluderer NoSpam-koden (i blått) og sjekksummen (i grått). Empty path is unavaliable Tom sti utilgjengelig Failed to rename Endring av navn mislyktes Profile already exists Profilen finnes allerede A profile named "%1" already exists. En profil ved navn "%1" finnes allerede. Empty name Tomt navn Empty name is unavaliable Tomt navn utilgjengelig Empty path Tomt navn Couldn't change password on the database, it might be corrupted or use the old password. Kunne ikke endre passord på databasen, den kan være skadet eller bruker det gamle passordet. Export profile Eksporter profil Tox save file (*.tox) save dialog filter Tox-lagringsfil (*.tox) The following files could not be deleted: deletion failed text part 1 Følgende filer kunne ikke slettes: Please manually remove them. deletion failed text part 2 Fjern dem manuelt. Are you sure you want to delete your password? deletion confirmation text Er du sikker på at du vil slette passordet ditt? Images (%1) filetype filter Bilder (%1) ProfileImporter Import profile import dialog title Importer profil Tox save file (*.tox) import dialog filter Tox-lagringsfil (*.tox) Ignoring non-Tox file popup title Ignorerer ikke-Tox-fil Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Advarsel: Du har valgt ei fil som ikke er ei Tox-lagringsfil; ignorerer. Profile already exists import confirm title Profilen finnes allerede A profile named "%1" already exists. Do you want to erase it? import confirm text En profil med navn "%1" finnes allerede. Vil du slette den? File doesn't exist Fila finnes ikke Profile doesn't exist Profilen finnes ikke Profile imported Profil importert %1.tox was successfully imported %1.tox ble importert QApplication Ok OK Cancel Avbryt Yes Ja No Nei LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout VTH QMessageBox Couldn't add friend Kunne ikke legge til venn %1 is not a valid Toxme address. %1 er ikke en gyldig ToxMe-adresse. You can't add yourself as a friend! When trying to add your own Tox ID as friend Du kan ikke legge deg selv til som venn! QObject Tox URI to parse Tox-URI som skal analyseres Starts new instance and loads specified profile. Starter en ny instanse og laster valgt profil. profile profil Default Standard Blue Blå Olive Oliven Red Rød Violet Fiolett Incoming call... Inkommende samtale... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 her! Tox meg kanskje? Server doesn't support Toxme Tjeneren støtter ikke ToxMe You're making too many requests. Wait an hour and try again Du sender for mange forespørsler. Vent én time og prøv igjen This name is already in use Dette navnet er allerede i bruk This Tox ID is already registered under another name Denne Tox-ID-en er allerede registrert under et annet navn Please don't use a space in your name Ikke bruk mellomrom i navnet ditt Password incorrect Feil passord You can't use this name Du kan ikke bruke dette navnet Name not found Navn ikke funnet Tox ID not sent Tox-ID ikke sendt That user does not exist Den brukeren finnes ikke Error Feilmelding qTox couldn't open your chat logs, they will be disabled. qTox kunne ikke åpne din sludrehistorikk, den vil skrus av. None No camera device set Ingen Desktop Desktop as a camera input for screen sharing Skrivebord Problem with HTTPS connection Problem med HTTPS-tilknytning Internal ToxMe error Intern ToxMe-feil Reformatting text in progress.. Endring av tekstformatering pågår… Starts new instance and opens the login screen. Starter ny instans og åpner innloggingsskjermen. Dark Mørkt Dark blue Mørkeblått Dark olive Mørk olivengrønn Dark red Mørkerød Dark violet Mørkelilla Failed to load profile automatically. Klarte ikke å laste inn profil automatisk. online contact status pålogget away contact status borte busy contact status opptatt offline contact status avlogget blocked contact status blokkert RemoveFriendDialog Remove friend Fjern venn Also remove chat history Fjern sludrehistorikk også Remove Fjern Are you sure you want to remove %1 from your contacts list? Er du sikker på at du vil fjerne %1 fra din kontaktliste? Remove all chat history with the friend if set Fjern all samtalehistorikk mellom deg vennen din hvis valgt ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Klikk og dra for å velge en region. Trykk %1 for å skjule/vise qTox-vinduet, eller %2 for å avbryte. Space [Space] key on the keyboard Mellomrom Escape [Escape] key on the keyboard Esc Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Trykk %1 for å sende en skjermavbildning av utvalget, %2 for å skjule/vise qTox-vinduet, eller %3 for å avbryte. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Fant ikke teksten Start Start SearchSettingsForm Form Skjema Start search: Søk: from the end Fra slutten from the beginning Fra begynnelsen after date Etter dato before date Før dato 00.00.0000 00.00.0000 Case sensitive Forskjell på små og store bokstaver Whole words only Kun hele ord Use regular expressions Bruk regulære uttrykk SetPasswordDialog Set your password Velg ditt passord The password is too short Passordet er for kort The password doesn't match. Passordet samsvarer ikke. Confirm: Bekreft: Password: Passord: Password strength: %p% Passordsstyrke: %p% Confirm password Bekreft passord Confirm password input Bekreft passordsinntasting Password input Passordsinntasting Password input field, minimum 6 characters long Passordsinntastingsfelt, minimum 6 tegn Settings Circle #%1 Sirkel #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Legg til en venn Do you want to add %1 as a friend? Har du lyst til å legge til %1 som en venn? User ID: Bruker-ID: Friend request message: Venneforespørselmelding: Send Send a friend request Send Cancel Don't send a friend request Avbryt UserInterfaceForm None Ingen User Interface Brukergrensesnitt UserInterfaceSettings Chat Samtale Base font: Standard skrift: px px Size: Størrelse: New text styling preference may not load until qTox restarts. Det kan hende det nye stilvalget ikke trår i kraft før qTox startes om. Text Style format: Tekststilformat: Select text styling preference. Velg tekststilvalg. Plaintext Klartekst Show formatting characters Vis formateringstegn Don't show formatting characters Ikke vis formateringstegn New message Ny melding Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Åpne qTox sitt vindu når du mottar ei melding og det ikke er noe åpent vindu enda. Open window Åpne vindu Contact list Kontaktliste If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Hvis valgt, vil gruppe-sludringer bli plassert på toppen av kontaktlisten, ellers vil de ligge under påloggede kontakter. Place groupchats at top of friend list Plasser gruppe-sludringer på toppen av kontaktlisten Your contact list will be shown in compact mode. toolTip for compact layout setting Din kontaktliste vil bli vist i kompakt modus. Compact contact list Kompakt kontaktliste Multiple windows mode Multivindusmodus Open each chat in an individual window Åpne hver sludring i eget vindu Emoticons Humørsymboler Use emoticons Bruk emojier Smiley Pack: Text on smiley pack label Smilefjes-pakke: Emoticon size: Humørsymbolsstørrelse: px px Theme Drakt Style: Stil: Theme color: Draktfarge: Timestamp format: Tidsstempelformat: Date format: Datoformat: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Hvis aktivert, vil hver kontakt uten en avatar få en basert på deres Tox ID istedenfor standardbildet. Krever omstart for å tre i effekt. Use identicons instead of empty avatars Bruk identikoner istedenfor tomme avatarer Use colored nicknames in chats Bruk fargede kallenavn i sludringer Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Vis en merknad når du mottar en ny melding, og vinduet ikke er i fokus. Notify Varsle Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Kun varsle om nye meldinger i gruppesludringer når nevnt. Group chats only notify when mentioned Gruppesludringer varsler kun når nevnt Play sound Spill av lyd Play sound while Busy Spill lyd mens opptatt Notify via desktop notifications Varsle via skrivebordsmerknader Hide message sender and contents Skjul meldingsavsender og innhold Widget Online Button to set your status to 'Online' Pålogget Away Button to set your status to 'Away' Borte Busy Button to set your status to 'Busy' Opptatt All Alle Add new circle... Legg til ny sirkel… By Name Etter navn By Activity Etter aktivitet Online Pålogget Offline Avlogget Friends Venner Groups Grupper File Fil Edit Profile Rediger profil Change Status Endre status Log out Logg ut Edit Rediger Filter... Filtrer… Contacts Venner Add Contact... Legg til venn… Next Conversation Neste samtale Previous Conversation Forrige samtale toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore mislyktes i å starte med dine proxy-innstillinger. qTox kan ikke kjøre; vennligst forandre på instillingene og restart. Executable file popup title Kjørbar fil You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Du har spurt qTox om å åpne en kjørbar fil. Kjørbare filer kan forårsake skader på din maskin. Er du sikker du vil åpne denne filen? Couldn't request friendship Kunne ikke lage venneforespørsel Search Contacts Søk i kontakter Status Status Message failed to send Mislyktes å sende melding toxcore failed to start, the application will terminate after you close this message. Klarte ikke å starte toxcore, programmet vil stenges etter at du lukker denne meldingen. Your name Ditt navn Groupchat #%1 Gruppesludring #%1 Create new group... Opprett ny gruppe… %n New Friend Request(s) %n ny venneforespørsel %n nye venneforespørsler %n New Group Invite(s) %n ny gruppeinvitasjon %n nye gruppeinvitasjoner Logout Tray action menu to logout user Logg ut Exit Tray action menu to exit tox Avslutt Show Tray action menu to show qTox window Vis Add friend title of the window Legg til venn Group invites title of the window Gruppeinvitasjoner File transfers title of the window Filoverføringer Settings title of the window Innstillinger My profile title of the window Min profil Failed to send file "%1" Klarte ikke å sende filen "%1" File sent Fil sendt sent you a friend request. sendte deg en venneforespørsel. invites you to join a group. inviterer deg til en gruppe. qTox/translations/pl.ts000066400000000000000000003343261415623743500155420ustar00rootroot00000000000000 AVForm Audio/Video Audio/Wideo Default resolution Domyślna rozdzielczość Disabled Wyłączone Select region Wybierz region Screen %1 Ekran %1 Audio Settings Ustawienia audio Gain Głośniej Playback device Urządzenie wyjściowe Use slider to set volume of your speakers. Użyj suwaka by ustawić poziom głośności. Capture device Urządzenie wejściowe Volume Głośność Video Settings Ustawienia wideo Video device Urządzenie wideo Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Ustaw rozdzielczość swojej kamery. Im większa wartość, tym lepszą jakość obrazu uzyskają twoi znajomi. Do lepszej jakości obrazu potrzebne jest jednak lepsze połączenie z internetem. Czasem twoje połączenie może nie być wystarczająco dobre do uzyskania lepszej jakości obrazu, co może powodować problemy z rozmowami wideo. Resolution Rozdzielczość Rescan devices Skanuj ponownie urządzenia Test Sound Dźwięk testowy Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Włącza eksperymentalny podkład dźwiękowy z obsługą anulowania echa, potrzebuje ponownego uruchomienia qTox, aby zatwierdzić zmiany. Enable experimental audio backend Włącza eksperymentalny podkład dźwiękowy Audio quality Jakość dźwięku Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Przesłana jakość dźwięku. Zmniejsz to ustawienie, jeśli przepustowość nie jest wystarczająco wysoka lub jeśli chcesz obniżyć zużycie Internetu. High (64 kbps) Wysoka (64 kbps) Medium (32 kbps) Średnia (32 kbps) Low (16 kbps) Niska (16 kbps) Very low (8 kbps) Bardzo niska (8 kbps) Threshold Próg AboutForm About better translation? O programie Original author: %1 Oryginalny autor: %1 You are using qTox version %1. Używasz wersję qToxa %1. Commit hash: %1 Commit hash: %1 toxcore version: %1 Wersja toxcore: %1 Qt version: %1 Wersja Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` better translation? Lista znanych problemów może zostać znaleziona na naszym %1 na Githubie. Jeśli odkryjesz błąd lub lukę bezpieczeństwa w qToxie, proszę zgłoś to wedłóg naszych artykułu %2. Click here to report a bug. Kliknij tutaj by zgłosić błąd. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Zobacz pełną listę %1 na Githubie bug-tracker Replaces `%1` in the `A list of all known…` bug-trackerze Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Zgłaszanie użytecznych raportów o błędach contributors Replaces `%1` in `See a full list of…` współtwórców AboutFriendForm Dialog Okno dialogowe username Nazwa użytkownika status message komunikat o stanie Used aliases: Użyte aliasy: HISTORY OF ALIASES HISTORIA ALIASÓW Automatically accept files from contact if set Jeśli zaznaczone, automatycznie odbieraj pliki Auto accept files Automatycznie akceptuj pliki Default directory to save files: Domyślny katalog do zapisu plików: Auto accept for this contact is disabled Automatyczny odbiór plików jest dla tego kontaktu wyłączony Auto accept call: Automatycznie akceptuj rozmowy: Manual Ręcznie Audio Audio Audio + Video Audio + Wideo Automatically accept group chat invitations from this contact if set. Automatycznie akceptuj zaproszenia do czatu grupowego od tego kontaktu. Auto accept group invites Automatycznie akceptuj zaproszenia grupowe Remove history (operation can not be undone!) Usuń historię (operacji nie można cofnąć!) Notes Notatki Input field for notes about the contact Pole na notatki o kontakcie You can save comment about this contact here. Tutaj możesz zapisać komentarz o tym kontakcie. History removed Historia usunięta Choose an auto accept directory popup title Wybierz domyślną ścieżkę dla plików <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Potwierdzenie Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Wersja License Licencja Authors Autorzy Known Issues Znane problemy Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Dodaj znajomych Send friend request Wyślij zapytanie do znajomego Couldn't add friend better translation? Nie udało się dodać znajomego Invalid Tox ID format Nieprawidłowy format Tox ID Add a friend Dodaj znajomych Friend requests Zaproś znajomych Accept Zaakceptuj Reject Odrzuć Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, 76 heksadecymalnych znaków lub nazwa@domena.com Type in Tox ID of your friend Wpisz Tox ID znajomego Friend request message better translation? Wiadomość w zapytaniu do znajomych Type message to send with the friend request or leave empty to send a default message Napisz coś, lub zostaw puste by wysłać domyślną wiadomość w zapytaniu %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID jest nieprawidłowy lub nie istnieje You can't add yourself as a friend! When trying to add your own Tox ID as friend Nie możesz dodać siebie jako przyjaciela! Open contact list Otwórz listę kontaktów Couldn't open file Nie można otworzyć pliku Couldn't open the contact file Error message when trying to open a contact list file to import Nie można otworzyć pliku z kontaktami Invalid file Nieprawidłowy plik We couldn't find any contacts to import in this file! Nie mogliśmy znaleźć żadnych kontaktów do zaimportowania w tym pliku! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description 76 heksadecymalnych znaków lub nazwa@domena.com Message The message you send in friend requests Wiadomość Open Button to choose a file with a list of contacts to import Otwórz Send friend requests Wyślij zaproszenia %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Tutaj %1. Toxniesz do mnie? Import a list of contacts, one Tox ID per line Zaimportuj listę kontaktów - jedno Tox ID na każdą linię Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Gotowy do zaimportowania %n kontaktu, kliknij "Wyślij" aby potwierdzić Gotowy do zaimportowania %n kontaktów, kliknij "Wyślij" aby potwierdzić Gotowe do zaimportowania %n kontaktów, kliknij "Wyślij" aby potwierdzić Import contacts Zaimportuj kontakty AdvancedForm Advanced Zaawansowane Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. O ile nie wiesz co %1 robisz, proszę, %2 zmieniaj tutaj niczego. Zmiany tutaj mogą prowadzić do problemów z qToxem, a nawet do utraty twoich danych, eg. historii. really naprawdę not nie IMPORTANT NOTE WAŻNE Reset settings Reset ustawień All settings will be reset to default. Are you sure? better translation? Wrzystkie ustawienia zostaną zresetowane do domyślnych. Czy jesteś pewien? Yes Tak No Nie Call active popup title Aktywna rozmowa You can't disconnect while a call is active! popup text Nie możesz się rozłączyć w trakcie rozmowy! Save File Zapisz plik Logs (*.log) Logi (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Zamiast domyślnego katalogu użyj obecnego do zapisania ustawień Make Tox portable Zrób Tox przenośnym Reset to default settings Reset do domyślnych ustawień Portable better translation? Przenośny Connection Settings Ustawienia połączenia Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Używaj IPv6 (zalecane) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Wyłączenie pozwala np. na toxowanie przez Tora. Niestety obciąża to sieć Tox, więc używaj tylko w razie potrzeby. Enable UDP (recommended) Text on checkbox to disable UDP Używaj UDP (zalecane) Proxy type: Typ proxy: Address: Text on proxy addr label Adres: Port: Text on proxy port label Port: None Brak SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Połącz ponownie Debug Debug Export Debug Log Eksportuj debug log Copy Debug Log Kopiuj debug log Enable LAN discovery ChatForm Send a file Wyślij plik qTox wasn't able to open %1 qTox nie był w stanie otworzyć %1 Unable to open Nie można otworzyć tego pliku Bad idea Zły pomysł %1 calling %1 dzwoni Failed to open temporary file Temporary file for screenshot Nie udało się otworzyć tymczasowego pliku qTox wasn't able to save the screenshot better translation? qTox nie był w stanie zapisać screenshota Call with %1 ended. %2 Rozmowa z %1 została zakończona. %2 Call duration: Czas trwania rozmowy: Calling %1 Łączenie z %1 %1 is typing %1 pisze Copy Kopiuj You're trying to send a sequential file, which is not going to work! Próbujesz wysłać plik sekwencyjny, co nie zadziała! %1 is now %2 e.g. "Dubslow is now online" %1 jest teraz %2 Call with %1 ended unexpectedly. %2 Połączenie z %1 zakończyło się niespodziewanie. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Nie można nawiązać połączenia audio Start audio call Rozpocznij rozmowę audio End audio call Zakończ rozmowę audio Cancel audio call Anuluj rozmowę audio Accept audio call Zaakceptuj rozmowę audio Can't start video call Nie można rozpocząć połączenia wideo Start video call Rozpocznij rozmowę wideo End video call Zakończ rozmowę wideo Cancel video call Anuluj rozmowę wideo Accept video call Zaakceptuj rozmowę wideo Sound can be disabled only during a call Dźwięk może zostać wyłączony tylko podczas połączenia Unmute call Wyłącz wyciszenie rozmowy Mute call Wycisz rozmowę Microphone can be muted only during a call Mikrofon może zostać wyciszony tylko podczas połączenia Unmute microphone Wyłącz wyciszenie mikrofonu Mute microphone Wycisz mikrofon ChatLog Copy Kopiuj Select all Zaznacz wszystko pending oczekujące ChatTextEdit Type your message here... Napisz swoją wiadomość tutaj... CircleWidget Rename circle Menu for renaming a circle better translation? Zmień nazwę kręgu Remove circle Menu for removing a circle Usuń krąg Open all in new window Otwórz wszystkie w nowym oknie Core /me offers friendship, "%1" /me oferuje znajomość, "%1" Invalid Tox ID Error while sending friendship request Niepoprawne Tox ID You need to write a message with your request Error while sending friendship request Musisz napisać wiadomość z zapytaniem Your message is too long! Error while sending friendship request Twoja wiadomość jest za długa! Friend is already added Error while sending friendship request Znajomy jest już dodany Groupchat %1 DesktopNotify New message Nowa wiadomość Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Od 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 ETA:10:10 Filename Nazwa Waiting to send... file transfer widget better translation? Czekanie na odbiór... Accept to receive this file file transfer widget Zaakceptuj by odebrać ten plik Location not writable Title of permissions popup better translation? Nie można zapisać w lokacji You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nie masz uprawnienia by zapisać w tej lokacji. Wybierz inną lub anuluj zapis. Resuming... file transfer widget Wznawianie... Cancel transfer Anuluj transfer Pause transfer Wstrzymaj transfer Paused file transfer widget Wstrzymany Open file Otwórz plik Open file directory Otwórz katalog z plikiem Resume transfer Wznów transfer Accept transfer Zaakceptuj transfer Save a file Title of the file saving dialog Zapisz plik Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Przesłane pliki Downloads Pobrane Uploads Wysłane FriendListWidget Today Dziś Yesterday Wczoraj Last 7 days Ostatnie 7 dni This month Ten miesiąc Older than 6 Months Starsze niż 6 miesięcy Never Nigdy FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Prośba o dodanie do kontaktów Someone wants to make friends with you Ktoś chce zostać Twoim znajomym User ID: ID użytkownika: Friend request message: Treść zapytania: Accept Accept a friend request Zaakceptuj Reject Reject a friend request Odrzuć FriendWidget Set alias... Ustaw alias... Auto accept files from this friend context menu entry Odbieraj pliki automatycznie New message Nowa wiadomość Online Online Away Nieobecna/y Busy Zajęta/y Offline Offline Invite to group Menu to invite a friend to a groupchat Zaproś do grupy Open chat in new window Otwórz rozmowę w nowym oknie Remove chat from this window Usuń rozmowę z tego okna Move to circle... Menu to move a friend into a different circle Przenieś do kręgu... To new circle Do nowego kręgu Remove from circle '%1' Usuń z '%1' kręgu Move to circle "%1" Przenieś do kręgu "%1" Remove friend Menu to remove the friend from our friendlist Usuń kontakt Choose an auto accept directory popup title Wybierz domyślną ścieżkę dla plików To new group Do nowej grupy Invite to group '%1' Zaproś do grupy '%1' Show details Pokaż szczegóły GeneralForm General Główne Choose an auto accept directory popup title better translation? Wybierz domyślną ścieżkę dla plików GeneralSettings General Settings Główne ustawienia The translation may not load until qTox restarts. Zmiana języka może wymagać restartu aplikacji. Language: Język: Enable light tray icon. toolTip for light icon setting better translation? Użyj jasnej ikony w trayu. Start in tray Uruchamiaj w trayu qTox will start minimized in tray. toolTip for Start in tray setting qTox będzię się uruchamiał zminimalizowany w trayu. Close to tray Zamykaj do traya After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Po naciśnięciu "zamknij" (X) qTox zminimalizuje się do traya, zamiast zakończyć swe działanie. Minimize to tray Minimalizuj do traya After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting better translation? Po naciścięciu "minimalizuj" (_) qTox zminimalizuje się to traya, zamiast do paska zadań. Set where files will be saved. Ustaw gdzie pliki będą zapisywane. Autostart Autostart Auto away after (0 to disable): Automatycznie nieobecny/a po (0 by wyłączyć): Show contacts' status changes Pokazuj zmiany statusów Start qTox on operating system startup (current profile). Uruchom qTox wraz ze startem systemu (obecny profil). Set to 0 to disable Ustaw na 0 by wyłączyć Your status is changed to Away after set period of inactivity. Twój status zmieni się na nieobecny/a po ustawionym okresie braku aktywności. Show system tray icon better translation? Pokaż ikonę w trayu Light icon Jasna ikona You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip better translation? Możesz to ustawić dla każdego znajomego klikając nań prawym. Autoaccept files Automatycznie akceptuj pliki Default directory to save files: Domyślny katalog do zapisu plików: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Wyślij wiadomość Smileys Uśmiechy Send file(s) Wyślij plik(i) Send a screenshot could "zrzut ekranu" be better? Wyślij screenshot Save chat log Zapisz historię rozmowy Clear displayed messages Wyczyść wyświetlane wiadomości Cleared Wyczyszczono Quote selected text Cytuj wybrany tekst Copy link address Kopiuj adres odsyłacza Confirmation Potwierdzenie You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Wczytaj historię rozmów... Export to file Eksportuj do pliku GenericNetCamView Tox video Tox wideo Show Messages Pokaż wiadomości Hide Messages Ukryj wiadomości Full Screen Toggle video preview Mute audio Mute microphone Wycisz mikrofon End video call Zakończ rozmowę wideo Exit full screen GroupChatForm %1 has set the title to %2 %1 zmienił(a) nazwę grupy na %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Grupy Create new group Utwórz nową grupę Group invites Zaproszenia do grup GroupInviteWidget Invited by %1 on %2 at %3. Zaproszony przez %1 na %2 przy %3. Join Dołącz Decline Odmów GroupWidget Set title... better translation? (I have considered translating this as 'tytuł', but it doesn't look well along with other things IMHO) Zmień nazwę... Open chat in new window Otwórz rozmowę w nowym oknie Remove chat from this window Usuń rozmowę z tego okna Quit group Menu to quit a groupchat Opuść grupę %n user(s) in chat Number of users in chat New Message Online Online IdentitySettings Public Information Informacje publiczne Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Ten zlepek znaków mówi innym klientom Tox jak się z tobą skontaktować. Podziel się tym ze znajomymi by się komunikować. Your Tox ID (click to copy) Twój Tox ID (kliknij by skopiować) This QR code contains your Tox ID. You may share this with your friends as well. better translation? Ten kod QR zawiera twój Tox ID. Możesz podzielić się tym ze swoimi znajomymi. Save image Zapisz obraz Copy image Kopiuj obraz Profile Profil Rename profile. tooltip for renaming profile button Zmień nazwę profilu. Delete profile. delete profile button tooltip Usuń profil. Go back to the login screen tooltip for logout button Wróć do strony logowania Logout import profile button Wyloguj Remove password Usuń hasło Change password Zmień hasło Rename rename profile button Zmień nazwę Export export profile button Eksportuj Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Pozwala na wyeksportowanie twojego profilu Tox do pliku. Profil nie zawiera twojej historii. Delete delete profile button Usuń Server Serwer Hide my name from the public list Ukryj moją nazwę na publicznej liście Register Zarejestruj Your password Twoje hasło Update Aktualizacja Register on ToxMe Zarejestruj na ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. better translation? Nazwa w serwisie ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Opcjonalne. Coś o tobie. Albo twoim kocie. Optional. Something about you. Or your cat. Tooltip for the Biography field. Opcjonalne. Coś o tobie. Albo twoim kocie. ToxMe service to register on. Serwis ToxMe do rejestracji. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Jeśli nie jest ustawione, wpis ToxMe jest publicznie widoczny. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Usuń swoje hasło i szyfrowanie z profilu. Name input better translation? Pole imienia Name visible to contacts Nazwa widoczna dla kontaktów Status message input Wprowadzanie statusu wiadomości Status message visible to contacts Komunikat o stanie widoczny dla kontaktów Your Tox ID Twój Tox ID Save QR image as file Zapisz obraz QR jako plik Copy QR image to clipboard Kopiuj obraz QR do schowka ToxMe username to be shown on ToxMe better translation? Nazwa do pokazana na ToxMe Optional ToxMe biography to be shown on ToxMe Opcjonalna biografia pokazana na ToxMe ToxMe service address Adres serwisu ToxMe Visibility on the ToxMe service Widoczność na serwisie ToxMe Password Hasło Update ToxMe entry Aktualizuj wpis ToxMe Rename profile. Zmień nazwę profilu. Delete profile. Usuń profil. Export profile Eksportuj profil Remove password from profile Usuń hasło z profilu Change profile password Zmień hasło My name: Moje imię: My status: Mój status: My username Moja nazwa użytkownika My biography Moja biografia My profile Mój profil LoadHistoryDialog Load History Dialog Wczytaj historię Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Nazwa użytkownika: Password: Hasło: Confirm: Powtórz: Password strength: %p% Siła hasła: %p% Create Profile Utwórz Profil Load automatically Załaduj automatycznie Load Załaduj Load Profile Załaduj Profil If the profile does not have a password, qTox can skip the login screen Jeśli profil nie ma hasła, qTox może pominąć okno logowania New Profile Nowy profil Couldn't create a new profile better translation? Nie udało się utworzyć profilu The username must not be empty. Nazwa użytkownika nie może być pusta. The password must be at least 6 characters long. Hasło musi mieć przynajmniej 6 znaków. The passwords you've entered are different. Please make sure to enter same password twice. Wpisane hasła różnią się. Proszę, upewnij się iż wpisano to samo hasło. A profile with this name already exists. Profil z tą nazwą już istnieje. Couldn't load this profile Nie udało się wczytać tego profilu This profile is already in use. Ten profil jest już w użyciu. Wrong password. Złe hasło. Import Importuj Password protected profiles can't be automatically loaded. Profil chroniony hasłem nie może być automatycznie załadowany. Couldn't load profile Nie udało się załadować profilu There is no selected profile. You may want to create one. Nie wybrano profilu. Możesz otworzyć nowy. Username input field Pole nazwy użytkownika Password input field, you can leave it empty (no password), or type at least 6 characters Pole wpisu hasła, możesz zostawić puste (brak hasła), lub wpisać przynajmniej 6 znaków Password confirmation field Pole potwierdzenia hasła Create a new profile button Przycisk utworzenia nowego profilu Profile list Lista profili List of profiles Lista profili Password input Pole hasła Load automatically checkbox Załaduj automatycznie pole wyboru Import profile Importuj profil Load selected profile button Przycisk do załadowania wybranego profilu New profile creation page Strona tworzenia nowego profilu Loading existing profile page Strona ładowania istniejącego profilu MainWindow ... ... Add friends Dodaj znajomych Create a group chat Utwórz czat grupowy View completed file transfers Zobacz zakończone transfery plików Change your settings translated as "change settings"; seems to be simpler this way Zmień ustawienia Close Zamknij Your name Twój nick Your status Twój status Open profile Otwórz profil Open profile page when clicked Otwórz stronę profilu po kliknięciu Status message input Wprowadzanie statusu wiadomości Set your status message that will be shown to others Ustaw swój status, który będzie pokazany innym Status Status Set availability status Ustaw status dostępności Contact search Wyszukiwanie kontaktu Contact search input for known friends Wpisuj dane do wyszukiwania dla znanych znajomych Sorting and visibility Sortowanie i widoczność Set friends sorting and visibility Ustaw sortowanie znajomych i widoczność Open Add friends page Otwórz stronę Dodaj znajomych Groupchat Czat grupowy Open groupchat management page Otwórz stronę menadżera czatu grupowego File transfers history Historia transferów plików Open File transfers history Otwórz historię transferów plików Settings Ustawienia Open Settings Otwórz ustawienia Nexus View OS X Menu bar Pokaż Window OS X Menu bar Okno Minimize OS X Menu bar Minimalizuj Bring All to Front OS X Menu bar better translation? Pokaż na pierwszym planie Exit Fullscreen better translation? Opuść pełny ekran Enter Fullscreen Pełny ekran NotificationEdgeWidget Unread message(s) Nieprzeczytana wiadomość Nieprzeczytane wiadomości Nieprzeczytanych wiadomości PasswordEdit CAPS-LOCK ENABLED CAPS LOCK WŁĄCZONY PrivacyForm Privacy Prywatność Confirmation Potwierdzenie Do you want to permanently delete all chat history? Czy chcesz permamentnie usunąć całą historię rozmów? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Twoi znajomi będą w stanie zobaczyć kiedy do nich piszesz. Send typing notifications Pokazuj gdy tekst jest pisany Keep chat history Zachowaj historię rozmów NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam better translation? NoSpam jest częścią twojego Tox ID. Jeśli otrzymuje się spam w postaci zapytania o dodanie do znajomych, powinno się zmienić NoSpam. Znajomi nie będą w stanie dodać cię używając starego ID, ale zachowasz obecnych znajomych. NoSpam or perhaps should be translated? Bez Spamu NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. better translation? NoSpam jest częścią twojego ID, która może zostać zmieniona w razie potrzeby. Jeśli otrzymujesz spam w postaci zapytań o dodanie do znajomych, zmień NoSpam. Generate random NoSpam Wygeneruj losowy NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Zachowywanie historii rozmów jest wciąż rozwijane. Możliwe są zmianay formatu zapisu, co może skutkować utratą danych. Privacy Prywatność BlackList Czarna lista Filter group message by group member's public key. Put public key here, one per line. Filtruj wiadomości grupowe po kluczu publicznym danego użytkownika. Wprowadź tutaj po jednym kluczu publicznym na każdą linię. Profile Failed to derive key from password, the profile won't use the new password. Nie można uzyskać klucza z hasła, profil nie użyje nowego hasła. Couldn't change password on the database, it might be corrupted or use the old password. Nie można zmienić hasła w bazie danych, może być uszkodzone lub użyć starego hasła. Toxing on qTox Toxuję na qTox ProfileForm Choose a profile picture better translation? Wybierz obrazek profilu Error Błąd Rename "%1" renaming a profile Zmień nazwę "%1" Unable to open this file. Nie można otworzyć tego pliku. Current profile: Obecny profil: Unable to read this image. Nie można odczytać tego obrazka. The supplied image is too large. Please use another image. better translation? Dany obrazek jest za duży. Proszę, użyj innego obrazka. Couldn't rename the profile to "%1" Nie udało się zmienić nazwy profilu na "%1" Location not writable Title of permissions popup Nie można zapisać w lokacji You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nie masz uprawnienia by zapisać w tej lokacji. Wybierz inną lub anuluj zapis. Failed to copy file Nie udało się skopiować pliku The file you chose could not be written to. Nie udało się zapisać do wybranego pliku. Really delete profile? deletion confirmation title Czy na pewno usunąć profil? Nothing to remove Nic do usunięcia Your profile does not have a password! Twój profil nie ma hasła! Really delete password? deletion confirmation title Czy na pewno usunąć hasło? Please enter a new password. Proszę, podaj nowe hasło. Are you sure you want to delete this profile? deletion confirmation text Czy masz pewność, iż chcesz usunąć ten profil? Save save qr image Zapisz Save QrCode (*.png) save dialog filter shouldn&t that be something like "kod QR save&a" or something? Zapisz QrCode (*.png) Remove Usuń Files could not be deleted! deletion failed title Pliki nie zostały usunięte! Register (processing) Rejestracja (przetwarzanie) Update (processing) Aktualizacja (przetwarzanie) Done! Zrobione! Account %1@%2 updated successfully Konto %1@%2 pomyślnie zaktualizowane Successfully added %1@%2 to the database. Save your password Pomyślnie dodano %1@%2 do bazy danych. Zapisz swoje hasło Toxme error Błąd Toxme Register Rejestracja Update Aktualizacja Change password button text Zmień hasło Set profile password button text Ustaw hasło profilu Current profile location: %1 Obecna lokalizacja profilu: %1 Couldn't change password Nie można zmienić hasła This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Ten zestaw znaków mówi innym użytkownikom Toxa jak się z tobą skontaktować. Podziel się nim ze znajomymi. Ten identyfikator zawiera kod NoSpam (na niebiesko) i sumę kontrolną (na szaro). Empty path is unavaliable Pusta ścieżka jest niedostępna Failed to rename Nie udało się zmienić nazwy Profile already exists Profil już istnieje A profile named "%1" already exists. Profil o nazwie "%1" już istnieje. Empty name Pusta nazwa Empty name is unavaliable Pusta nazwa jest niedostępna Empty path Pusta ścieżka Couldn't change password on the database, it might be corrupted or use the old password. Nie można zmienić hasła w bazie danych, może być uszkodzone lub użyć starego hasła. Export profile Eksportuj profil Tox save file (*.tox) save dialog filter Plik zapisu Toxa (*.tox) The following files could not be deleted: deletion failed text part 1 Następujące pliki nie mogły zostać usunięte: Please manually remove them. deletion failed text part 2 Proszę usunąć je ręcznie. Are you sure you want to delete your password? deletion confirmation text Czy na pewno chcesz usunąć swoje hasło? Images (%1) filetype filter Obrazy (%1) ProfileImporter Import profile import dialog title Importuj profil Tox save file (*.tox) import dialog filter Plik zapisu Tox (*.tox) Ignoring non-Tox file popup title Zignorowano niepoprawny plik profilu Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Ostrzeżenie: Wybrano plik, który nie jest plikiem Tox; zignorowano. Profile already exists import confirm title Profil już istnieje A profile named "%1" already exists. Do you want to erase it? import confirm text Profil pod nazwą "%1" już istnieje. Czy chcesz go usunąć? File doesn't exist Plik nie istnieje Profile doesn't exist Profil nie istnieje Profile imported Zaimportowano profil %1.tox was successfully imported %1.tox został pomyślnie zaimportowany QApplication Ok Ok Cancel Anuluj Yes Tak No Nie LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Nie udało się dodać znajomego %1 is not a valid Toxme address. %1 nie jest prawidłowym adresem Toxme. You can't add yourself as a friend! When trying to add your own Tox ID as friend Nie możesz dodać siebie jako znajomego! QObject Tox URI to parse Adres URI Tox do sprawdzenia Starts new instance and loads specified profile. better translation? Uruchamia nową instancję i ładuje wybrany profil. profile profil Default Domyślny Blue Niebieski Olive Oliwkowy Red Czerwony Violet Fioletowy Incoming call... better translation? Rozmowa przychodząca... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Tutaj %1. Toxnij ze mną? None No camera device set Brak Desktop Desktop as a camera input for screen sharing better translation? Ekran Server doesn't support Toxme Ten serwer nie wspiera Toxme You're making too many requests. Wait an hour and try again Wysyłasz zbyt wiele zapytań. Poczekaj godzinę i spróbuj ponownie This name is already in use Ta nazwa jest już używana This Tox ID is already registered under another name Ten Tox ID jest już zarejestrowany pod inną nazwą Please don't use a space in your name Nie używaj spacji w nazwie Password incorrect Niepoprawne hasło You can't use this name Nie można użyć tej nazwy Name not found Nazwa nie znaleziona Tox ID not sent Nie wysłano Tox ID That user does not exist Ten użytkownik nie istnieje Error Błąd qTox couldn't open your chat logs, they will be disabled. qTox nie był w stanie otworzyć Twojej historii, zostanie ona wyłączona. Problem with HTTPS connection Problem z połączeniem HTTPS Internal ToxMe error Wewnętrzny błąd ToxMe Reformatting text in progress.. Trwa formatowanie tekstu... Starts new instance and opens the login screen. Otwiera nowe okno z ekranem logowania. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status online away contact status nieobecna/y busy contact status zajęta/y offline contact status offline blocked contact status RemoveFriendDialog Remove friend Usuń kontakt Also remove chat history Usuń także historię rozmów Remove Usuń Are you sure you want to remove %1 from your contacts list? Czy na pewno chcesz usunąć %1 z twojej listy kontaktów? Remove all chat history with the friend if set Jeśli zaznaczone, usuń całą historię rozmów ze znajomym ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Kliknij i przeciągnij aby wybrać obszar. Wciśnij %1 by ukryć/pokazać okno qToxa, lub %2 by anulować. Space [Space] key on the keyboard spację Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Wciśnij %1 by wysłać screenshot zaznaczonego obszaru, %2 by ukryć/pokazać okno qToxa, lub %3 by anulować. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form Od Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Ustaw swoje hasło The password is too short Hasło jest zbyt krótkie The password doesn't match. Hasła nie pasuje. Confirm: Powtórz: Password: Hasło: Password strength: %p% Siła hasła: %p% Confirm password Potwierdź hasło Confirm password input Pole potwierdzenia hasła Password input Pole hasła Password input field, minimum 6 characters long Pole wprowadzania hasła, długie na minimum 6 znaków Settings Circle #%1 Krąg #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Dodaj znajomych Do you want to add %1 as a friend? Czy chcesz dodać %1 do znajomych? User ID: ID użytkownika: Friend request message: Treść zapytania: Send Send a friend request Wyślij Cancel Don't send a friend request Anuluj UserInterfaceForm None Brak User Interface Interfejs użytkownika UserInterfaceSettings Chat Czat Base font: Podstawowa czcionka: px px Size: Rozmiar: New text styling preference may not load until qTox restarts. Nowe ustawienia stylu tekstu mogą załadować się dopiero po ponownym uruchomianiu programu qTox. Text Style format: Format stylu tekstu: Select text styling preference. Wybierz preferencję stylowania tekstu. Plaintext Zwykły tekst Show formatting characters Pokazuj znaki formatujące Don't show formatting characters Nie pokazuj znaków formatujących New message Nowa wiadomość Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Otwórz okno qToxa gdy otrzymasz nową wiadomość jeśli nie jest już otwarte. Open window Otwórz okno Contact list Lista kontaktów If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Jeśli zaznaczone, czaty grupowe będą umieszczone na szczycie listy znajomych, w innym wypadku będą umieszczone poniżej znajomych którzy są online. Place groupchats at top of friend list Umieść czaty grupowe na szczycie listy znajomych Your contact list will be shown in compact mode. toolTip for compact layout setting Twoja lista znajomych zostanie pokazana w trybie kompaktowym. Compact contact list Kompaktowa lista kontaktów Multiple windows mode Tryb wielu okien Open each chat in an individual window Otwórz każdą rozmowę w indywidualnym oknie Emoticons Emotikony Use emoticons Używaj emotikony Smiley Pack: Text on smiley pack label Paczka Uśmiechów: Emoticon size: Rozmiar emotikonów: px px Theme Motyw Style: Styl: Theme color: Kolor motywu: Timestamp format: Format czasu: Date format: Format daty: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Po włączeniu każdy kontakt bez awatara zamiast pustego obrazka będzie miał ikonę wygenerowaną na podstawie jego Tox ID. Zatwierdzenie zmiany wymaga restartu. Use identicons instead of empty avatars Użyj ikon zamiast pustych avatarów Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Odtwórz dźwięk Play sound while Busy Odtwórz dźwięk dla statusu Zajęty Notify via desktop notifications Hide message sender and contents Widget Online Online Online Button to set your status to 'Online' Online Away Button to set your status to 'Away' Nieobecny/a Busy Button to set your status to 'Busy' Zajęty/a toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Nie udało się uruchomić toxcore z twoimi ustawieniami proxy. qTox nie może działać, proszę zmodyfikuj ustawienia i zrestartuj. Add new circle... Dodaj nowy krąg... By Name better translation? Nazwa By Activity better translation? Aktywność All Wszyscy Offline Offline Friends Znajomi Groups Grupy Search Contacts better translation? Szukaj znajomych Executable file popup title better translation? Plik wykonywalny You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Zażądano od qToxa aby otworzyć plik wykonywalny. Wykonywalne pliki mogą potencjalnie uszkodzić twój komputer. Czy na pewno chcesz otworzyć ten plik? Couldn't request friendship better translation? Nie udało się dodać do znajomych Status Status toxcore failed to start, the application will terminate after you close this message. Nie udało się uruchomić toxcore. Aplikacja zostanie zakończona po zamknięciu wiadomości. Your name Twój nick Filter... Filter... File Plik Edit Edytuj Contacts Kontakty Change Status Zmień status Edit Profile Edytuj profil Log out Wyloguj Add Contact... Dodaj kontakt... Next Conversation Następna rozmowa Previous Conversation Poprzednia rozmowa Message failed to send Nie udało się wysłać wiadmości Groupchat #%1 Grupa #%1 Create new group... Utwórz nową grupę… %n New Friend Request(s) %n nowe zaproszenie %n nowe zaproszenia %n nowych zaproszeń %n New Group Invite(s) %n nowe zaproszenie do grup %n nowe zaproszenia do grup %n nowych zaproszeń do grup Logout Tray action menu to logout user Wyloguj Exit Tray action menu to exit tox Zakończ Show Tray action menu to show qTox window Pokaż Add friend title of the window Dodaj znajomego Group invites title of the window Zaproszenia do grup File transfers title of the window Transfer plików Settings title of the window Ustawienia My profile title of the window Mój profil Failed to send file "%1" Nie udało się wysłać pliku "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/pr.ts000066400000000000000000003172111415623743500155420ustar00rootroot00000000000000 AVForm Audio/Video Scryin' Default resolution Narrrmal size o' scry-port Disabled Slumberin' Select region Choose arear ta send Screen %1 Viewport %1 Audio Settings Voice Settings Gain Bellow Assist Playback device Sing out from Use slider to set volume of your speakers. Pick tha loudness o' yer qTox. Capture device Sing in from Volume Loudness Video Settings Face Settings Video device View fer sendin' Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Command th' quality o' yer scryin. The higher th' number, the better th' view fer yer hearties. Take heed, fer higher qualities demand clearer skies.If yer seas are stormy, yer scryin' will suffer. Resolution Quality Rescan devices Check fer more Test Sound Cry "Ho!" Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Medium (32 kbps) Low (16 kbps) Very low (8 kbps) Threshold AboutForm About Charter Original author: %1 Created by th' great an' magnifercent %1 You are using qTox version %1. Ye be usin' qTox version %1. Commit hash: %1 Commit hash: %1 toxcore version: %1 toxcore version: %1 Qt version: %1 Qt version: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` A big list o' all th' known problems is kept at tha %1 on Github. If ya discover any bilgerats, stowaways, or spies, haul yer keester over and report it in accordance wit' %2. Click here to report a bug. Click here ta report rats an' their ilk. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Peer a full record o' %1 at Github bug-tracker Replaces `%1` in the `A list of all known…` rat-tracker Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` How Ta Get Yer Complainin' Heard contributors Replaces `%1` in `See a full list of…` privateers an' deck-hands AboutFriendForm Dialog Dialog username Name status message Status report Used aliases: Past aliases: HISTORY OF ALIASES RECORD O' ALIASES Automatically accept files from contact if set Welcome all parcels Auto accept files Auto-accept parcels Default directory to save files: Default chest fer storin' parcels: Auto accept for this contact is disabled Auto-accept fer this hearty is disabled Auto accept call: Auto-answerin' scrys: Manual By hand Audio Just voices Audio + Video Voices and faces Automatically accept group chat invitations from this contact if set. Auto accept group invites Remove history (operation can not be undone!) Burn letters (what's fed to fire remains in ash) Notes Extra Notin' Input field for notes about the contact Page fer scrawlin' notes about the matey You can save comment about this contact here. Scrawl down any observations yer havin' about this matey. History removed All messages burnt Choose an auto accept directory popup title <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Versions License Law Authors Creators Known Issues Resolvin' Issues Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Add Hearties Invalid Tox ID format It ain't right Send friend request Fire off Add a friend Join up with a mate Friend requests Awaitin' hearties Accept Arrr! Reject Narrr... Couldn't add friend Failed ta add th' hearty Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, either 76 hex-a-dessamull characters or Jack@ship.sea Type in Tox ID of your friend Scrawl in yer mate's Tox ID Friend request message Message fer th' prospective matey Type message to send with the friend request or leave empty to send a default message Pen a salutation ta send with yer hearty request, or leave it be fer the default message %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend Don't gab at yerself, lad, 's unsightly Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description either 76 hexadecimal characters or Jack@ship.sea Message The message you send in friend requests State yer business Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Ahoy from %1, matey! Go on account wit me? Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced Voodoo Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Unless yer %1 with qTox workins, it'd be best %2 to muck around here. Orders issues here may lead to problems with qTox, and even to losin' all yer data. really really well-acquainted not NAHT IMPORTANT NOTE VITAL READIN' Reset settings Make 'er shipyard-new All settings will be reset to default. Are you sure? All yer customizations will be gone, hear? Still want to proceed? Yes Proceed No Abstain Call active popup title Yer mid-scry, bucko You can't disconnect while a call is active! popup text Ye can't sop off while yer scryin' a mate! Save File Store parcel Logs (*.log) Logs (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Save yer business to the working directory instead of the usual conf dir Make Tox portable Keep yer business in yer quarters Reset to default settings Make 'er shipyard-new Portable Portable Connection Settings Connection Settings Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Disablin' this allows Toxin' over Tor, but it adds load to yer Toxmates. Be thoughtful. Enable UDP (recommended) Text on checkbox to disable UDP Proxy type: Address: Text on proxy addr label Port: Text on proxy port label None SOCKS5 HTTP Reconnect reconnect button Rehook Debug Export Debug Log Store Debug Log Copy Debug Log Pick up Debug Log Enable LAN discovery ChatForm Send a file Launch a parcel qTox wasn't able to open %1 qTox failed to open %1 Unable to open 't couldn't be opened Bad idea Ye'll wish ye hadn't %1 calling Ahoy! %1 wants to scry Calling %1 Scryin' %1 Failed to open temporary file Temporary file for screenshot Failed ta open temporary file qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox wasn't able ta store taht screenshot Call with %1 ended. %2 Scryin' wit' %1 'sover. %2 Call duration: Scry duration: %1 is typing %1 be scrawlin' Copy Yank You're trying to send a sequential file, which is not going to work! Yer attemptin to send a "sequential file," and 'tain't natural! %1 is now %2 e.g. "Dubslow is now online" %1 has %2 Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Can't begin talkin' Start audio call Voice-scry yer hearty End audio call Cease voice-scryin' Cancel audio call Belay that Accept audio call Can't start video call Can't begin talkin' n' face showin' Start video call Face-scry yer hearty End video call Cease face-scryin' Cancel video call Belay that Accept video call Sound can be disabled only during a call Unmute call Mute call Microphone can be muted only during a call Unmute microphone Speak yer mind Mute microphone Shut yer gab ChatLog Copy Yank Select all Grab it all pending workin' on it ChatTextEdit Type your message here... Scrawl yer message here... CircleWidget Rename circle Menu for renaming a circle Rename guild Remove circle Menu for removing a circle Remove guild Open all in new window Open 'em all in a new viewport Core /me offers friendship, "%1" /me wants to be hearties, and %1 Invalid Tox ID Error while sending friendship request You need to write a message with your request Error while sending friendship request Ya need ta pen a note accompanyin' yer request Your message is too long! Error while sending friendship request Yer message is too long! Friend is already added Error while sending friendship request Yer already hearties Groupchat %1 DesktopNotify New message New writin' Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen 10Mb Ausgelassen 0kb/s Ausgelassen ETA:10:10 Ausgelassen Filename Ausgelassen Parcel tag Waiting to send... file transfer widget Awaitin' approval... Accept to receive this file file transfer widget Arr to receive this parcel Location not writable Title of permissions popup Yaain't got permission ta store there You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup That ain't yer chest. Pick another, or give up. Resuming... file transfer widget Pickin' back up... Cancel transfer Belay loadin' that parcel Pause transfer Put on hold Paused file transfer widget Awaitin' yer word Open file Unwrap parcel Open file directory Peer th' parcel-chest Resume transfer Pick back up wit' th' transfer Accept transfer Arrr! Save a file Title of the file saving dialog Store a parcel Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Transferred Parcels Downloads Received Uploads Sent Off FriendListWidget Today This Day Yesterday Last Morn Last 7 days Th' last week This month This moon Older than 6 Months More'n half a Year Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Hearty Request Someone wants to make friends with you Some bucko wants ta be hearties with ya User ID: Yar' own ID: Friend request message: They hail with: Accept Accept a friend request Arrr Reject Reject a friend request Narrr FriendWidget Invite to group Menu to invite a friend to a groupchat Invite aboard Move to circle... Menu to move a friend into a different circle Move ta guild... To new circle Ta new guild Remove from circle '%1' Remove from guild "%1" Move to circle "%1" Move ta guild "%1" Open chat in new window Open chat in new port Remove chat from this window Remove chat from this port To new group Ta new ship Invite to group '%1' Invite aboard ship "%1" Set alias... Issue fittin' nickname... Auto accept files from this friend context menu entry Auto-accept parcels from this hearty Remove friend Menu to remove the friend from our friendlist Cut ties with matey Show details Examine Choose an auto accept directory popup title Pick a parcel-chest New message New writin' Online Aboard Away Wanderin' Busy Occupied Offline Ausgelassen Landlubbin' GeneralForm General Choose an auto accept directory popup title Pick a chest fer auto-accepting parcels GeneralSettings General Settings The translation may not load until qTox restarts. Language: Tongue: Show system tray icon Enable light tray icon. toolTip for light icon setting Light icon qTox will start minimized in tray. toolTip for Start in tray setting ship will sail small in tray. Start in tray Sail in tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Close to tray After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Minimize to tray Autostart Self-Sailin' Set where files will be saved. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Autoaccept files Self-Movin' parcels Set to 0 to disable Your status is changed to Away after set period of inactivity. Auto away after (0 to disable): Show contacts' status changes Start qTox on operating system startup (current profile). Default directory to save files: Default chest fer storin' parcels: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Send yer writin' Smileys Send file(s) Send a screenshot Save chat log Clear displayed messages Cleared Quote selected text Copy link address Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... View old messages... Export to file GenericNetCamView Tox video Show Messages Examine log Hide Messages Stash log Full Screen Toggle video preview Mute audio Mute microphone Shut yer gab End video call Cease face-scryin' Exit full screen GroupChatForm %1 has set the title to %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Create new group Group invites GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Set title... Open chat in new window Open chat in new port Remove chat from this window Remove chat from this port Quit group Menu to quit a groupchat %n user(s) in chat Number of users in chat New Message Online Aboard IdentitySettings Public Information Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Your Tox ID (click to copy) Profile Rename profile. tooltip for renaming profile button Go back to the login screen tooltip for logout button Logout import profile button Remove password Change password This QR code contains your Tox ID. You may share this with your friends as well. Save image Copy image Rename rename profile button Delete profile. delete profile button tooltip Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Export export profile button Delete delete profile button Server Hide my name from the public list Register Your password Update Register on ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. Delete profile. Export profile Remove password from profile Change profile password My name: My status: My username My biography My profile LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Password: Confirm: Password strength: %p% Create Profile If the profile does not have a password, qTox can skip the login screen Load automatically Load Load Profile New Profile Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Password protected profiles can't be automatically loaded. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Import Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Your status ... Ausgelassen Add friends Create a group chat View completed file transfers Change your settings Close Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings Open Settings Nexus View OS X Menu bar Window OS X Menu bar Minimize OS X Menu bar Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox Toxin' aboard qTox ProfileForm Choose a profile picture Error Rename "%1" renaming a profile Unable to open this file. Current profile: Remove Unable to read this image. The supplied image is too large. Please use another image. Couldn't rename the profile to "%1" Location not writable Title of permissions popup Yaain't got permission ta store there You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup That ain't yer chest. Pick another, or give up. Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Nothing to remove Your profile does not have a password! Really delete password? deletion confirmation title Please enter a new password. Are you sure you want to delete this profile? deletion confirmation text Save save qr image Save QrCode (*.png) save dialog filter Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Change password button text Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Cancel Yes Proceed No Abstain LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend Failed ta add th' hearty %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend Don't gab at yerself, lad, 's unsightly QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Red Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Ahoy from %1, matey! Go on account wit me? None No camera device set Desktop Desktop as a camera input for screen sharing Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status come aboard away contact status gone wanderin' busy contact status become occupied offline contact status gone landlubbin' blocked contact status RemoveFriendDialog Remove friend Cut ties with matey Also remove chat history Remove Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Password: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Join up with a mate Do you want to add %1 as a friend? User ID: Yar' own ID: Friend request message: They hail with: Send Send a friend request Cancel Don't send a friend request UserInterfaceForm None User Interface UserInterfaceSettings Chat Base font: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message New writin' Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme Style: Theme color: Timestamp format: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Aboard Away Button to set your status to 'Away' Wanderin' Busy Button to set your status to 'Busy' Occupied toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File Edit Profile Change Status Log out Edit Logout Tray action menu to logout user Exit Tray action menu to exit tox Filter... Contacts Add Contact... Next Conversation Previous Conversation Executable file popup title You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Couldn't request friendship Status Your name Message failed to send Create new group... Add new circle... %n New Friend Request(s) %n New Group Invite(s) By Name By Activity All Online Aboard Offline Ausgelassen Landlubbin' Friends Groups Search Contacts Search Hearties Groupchat #%1 Crew #%1 Show Tray action menu to show qTox window Reveal Add friend title of the window Group invites title of the window File transfers title of the window Settings title of the window My profile title of the window Failed to send file "%1" Failed to deliver parcel "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/pt.ts000066400000000000000000003336331415623743500155520ustar00rootroot00000000000000 AVForm Audio/Video Áudio / vídeo Default resolution Resolução padrão Disabled Desativado Select region Selecionar região Screen %1 Ecrã %1 Audio Settings Configurações de áudio Gain Ganho Playback device Dispositivo de reprodução Use slider to set volume of your speakers. Deslize para ajustar o volume dos altifalantes. Capture device Dispositivo de captura Volume Volume Video Settings Configurações de vídeo Video device Dispositivo de vídeo Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Define a resolução da sua câmara. Quanto maior o valor, maior a qualidade do vídeo que os seus contactos poderão ter de si. No entanto note que uma qualidade de vídeo maior exige uma conexão mais rápida com a Internet. Mesmo assim, a sua conexão por vezes pode não ser suficiente boa para lidar com uma qualidade maior de vídeo, o que pode levar a problemas nas chamadas de vídeo. Resolution Resolução Rescan devices Tornar a detetar dispositivos Test Sound Testar som Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Ativa o painel de áudio experimental com suporte a cancelamento de eco, tem de reiniciar o qTox para para que esta alteração surta efeito. Enable experimental audio backend Ativar o painel de áudio experimental Audio quality Qualidade de áudio Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Qualidade do áudio transmitido. Reduza esta configuração se a sua largura de banda não é alta o suficiente ou se quer reduzir a utilização da Internet. High (64 kbps) Alta (64 kbps) Medium (32 kbps) Média (32 kbps) Low (16 kbps) Baixa (16 kbps) Very low (8 kbps) Muito baixa (8 kbps) Threshold Limite AboutForm About Sobre Original author: %1 Autor original: %1 You are using qTox version %1. Está a usar a versão %1 do qTox. Commit hash: %1 Confirmação de hash: %1 toxcore version: %1 Versão do toxcore: %1 Qt version: %1 Versão do Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Pode encontrar uma lista de todos os problemas conhecidos no nosso % 1 no Github. Se descobrir um erro ou vulnerabilidade de segurança no qTox, informe-nos de acordo com as diretrizes da nossa página wiki % 2. Click here to report a bug. Clique aqui para reportar um erro. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Veja a lista completa de %1 no Github bug-tracker Replaces `%1` in the `A list of all known…` rastreador de erros Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Como escrever relatórios de erros úteis contributors Replaces `%1` in `See a full list of…` colaboradores AboutFriendForm Dialog Diálogo username nome de utilizador status message mensagem de estado Used aliases: Pseudónimos usados: HISTORY OF ALIASES HISTÓRICO DE PSEUDÓNIMOS Automatically accept files from contact if set Se ativado, aceita automaticamente ficheiros do contacto Auto accept files Aceitar ficheiros automaticamente Default directory to save files: Pasta padrão para guardar ficheiros: Auto accept for this contact is disabled Aceitar automaticamente está desativado para este contacto Auto accept call: Aceitar chamada automaticamente: Manual Manual Audio Áudio Audio + Video Áudio e vídeo Automatically accept group chat invitations from this contact if set. Se ativado, aceita automaticamente os convites de conversação em grupo desse contacto. Auto accept group invites Aceitar automaticamente convites de grupos Remove history (operation can not be undone!) Eliminar histórico (operação irreversível!) Notes Notas Input field for notes about the contact Campo para introduzir notas sobre o contacto You can save comment about this contact here. Pode guardar comentários sobre este contacto aqui. History removed Histórico eliminado Choose an auto accept directory popup title Escolher uma pasta onde aceitar ficheiros automaticamente <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Isto é a chave pública do seu amigo. Use-a para verificar a identidade dele através de outro meio. Não pode enviar isto para outras pessoas para que elas possam adicionar este contacto.</p></body></html> Public key (not ToxID): Chave pública (não é o ToxID): Confirmation Confirmação Are you sure to remove %1 chat history? Tem a certeza que quer remover o histórico de conversas com %1? Failed to remove chat history with %1! Não foi possível remover o histórico de conversas com %1! AboutSettings Version Versão License Licença Authors Autores Known Issues Problemas conhecidos Open update download link Abrir a hiperligação da atualização Update available Atualização disponível qTox is up to date ✓ O qTox está atualizado ✓ AddFriendForm Add Friends Adicionar contactos Send friend request Enviar pedido de amizade Couldn't add friend Não foi possível adicionar o contacto Invalid Tox ID format Formato inválido da Referência do Tox Add a friend Adicionar um contacto Friend requests Solicitações de amizade Accept Aceitar Reject Rejeitar Tox ID, either 76 hexadecimal characters or name@example.com Referência do Tox, quer seja os 76 caracteres hexadecimais ou nome@exemplo.com Type in Tox ID of your friend Introduza a Referência do Tox do seu contacto Friend request message Mensagem de solicitação de amizade Type message to send with the friend request or leave empty to send a default message Introduza a mensagem a enviar com a solicitação de amizade ou deixe vazio para enviar uma mensagem padrão %1 Tox ID is invalid or does not exist Toxme error A Referência do Tox %1 é inválida ou não existe You can't add yourself as a friend! When trying to add your own Tox ID as friend Não pode adicionar-se como seu contacto! Open contact list Abrir lista de contactos Couldn't open file Não foi possível abrir o ficheiro Couldn't open the contact file Error message when trying to open a contact list file to import Não foi possível abrir o ficheiro de contactos Invalid file Ficheiro inválido We couldn't find any contacts to import in this file! Não foi possível encontrar neste ficheiro nenhum contacto para importar! Tox ID Tox ID of the person you're sending a friend request to Referência do Tox either 76 hexadecimal characters or name@example.com Tox ID format description quer sejam os 76 caracteres hexadecimais ou nome@exemplo.com Message The message you send in friend requests Mensagem Open Button to choose a file with a list of contacts to import Abrir Send friend requests Enviar pedidos de amizade %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Olá, sou %1 ! Queres adicionar-me ao Tox? Import a list of contacts, one Tox ID per line Importar uma lista de contactos, uma Referência do Tox por linha Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Pronto para importar %n contacto, clique em enviar para confirmar Pronto para importar %n contactos, clique em enviar para confirmar Import contacts Importar contactos AdvancedForm Advanced Avançado Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. A não ser que saiba %1 o que está a fazer, por favor %2 faça alterações aqui. As alterações feitas aqui podem levar a problemas com o qTox, e até à perda de informações, como o histórico. really realmente not não IMPORTANT NOTE NOTA IMPORTANTE Reset settings Repor configurações All settings will be reset to default. Are you sure? Todas configurações serão repostas nos valores de origem. Quer continuar? Yes Sim No Não Call active popup title Chamada ativa You can't disconnect while a call is active! popup text Não pode desconectar enquanto tiver uma chamada ativa! Save File Gravar o ficheiro Logs (*.log) Registos (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Guardar as configurações na pasta atual em vez da pasta padrão Make Tox portable Tornar o Tox portável Reset to default settings Repor as configurações de origem Portable Portável Connection Settings Configurações de conexão Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Ativar IPv6 (recomendado) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Desativando esta opção, permite por exemplo, utilizar o Tox na rede Tor. No entanto isto sobrecarrega a rede Tor, por isso desative apenas se for mesmo necessário. Enable UDP (recommended) Text on checkbox to disable UDP Ativar UDP (recomendado) Proxy type: Tipo de proxy: Address: Text on proxy addr label Endereço: Port: Text on proxy port label Porta: None Nenhum SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Reconectar Debug Depurar Export Debug Log Exportar registo de depuração Copy Debug Log Copiar registo de depuração Enable LAN discovery Ativar descoberta de LAN ChatForm Send a file Enviar um ficheiro qTox wasn't able to open %1 O qTox não conseguiu abrir %1 %1 calling %1 a chamar Failed to open temporary file Temporary file for screenshot Não foi possível abrir o ficheiro temporário qTox wasn't able to save the screenshot O qTox não conseguiu guardar a captura de ecrã Call with %1 ended. %2 Chamada para %1 terminada. %2 Call duration: Duração da chamada: Unable to open Impossível abrir Bad idea Má ideia Calling %1 A chamar %1 %1 is typing %1 está a escrever Copy Copiar You're trying to send a sequential file, which is not going to work! Está a tentar enviar um ficheiro sequencial, mais isso não vai funcionar! %1 is now %2 e.g. "Dubslow is now online" %1 agora é %2 Call with %1 ended unexpectedly. %2 A chamada com %1 terminou inesperadamente. %2 Filename contained illegal characters O nome do ficheiro contém caracteres não permitidos Illegal characters have been changed to _ so you can save the file on windows. Os caracteres não permitidos foram alterados para _ para que posa aguardar o ficheiro no Windows. ChatFormHeader Can't start audio call Não foi possível iniciar a chamada de áudio Start audio call Iniciar chamada de áudio End audio call Terminar chamada de áudio Cancel audio call Cancelar chamada de áudio Accept audio call Aceitar chamada de áudio Can't start video call Não foi possível iniciar a chamada de vídeo Start video call Iniciar chamada de vídeo End video call Terminar chamada de vídeo Cancel video call Cancelar chamada de vídeo Accept video call Aceitar chamada de vídeo Sound can be disabled only during a call O som só pode ser desativado durante uma chamada Unmute call Ativar áudio da chamada Mute call Desativar áudio da chamada Microphone can be muted only during a call O microfone só pode ser desativado durante uma chamada Unmute microphone Ativar microfone Mute microphone Desativar microfone ChatLog Copy Copiar Select all Tudo ou todos? Selecionar tudo pending Plural? Singular? pendente ChatTextEdit Type your message here... Escreva a sua mensagem aqui... CircleWidget Rename circle Menu for renaming a circle Alterar nome do círculo Remove circle Menu for removing a circle Remover círculo Open all in new window Abrir tudo numa janela nova Core /me offers friendship, "%1" /me oferece amizade, "%1" Invalid Tox ID Error while sending friendship request Referência do Tox inválida You need to write a message with your request Error while sending friendship request Tem de escrever uma mensagem junto com o seu pedido Your message is too long! Error while sending friendship request A sua mensagem é muito longa! Friend is already added Error while sending friendship request O amigo já foi adicionado Groupchat %1 DesktopNotify New message Nova mensagem Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Formulário 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 Tempo estimado:10:10 Filename Nome do ficheiro Waiting to send... file transfer widget A esperar para enviar... Accept to receive this file file transfer widget Aceite para poder receber este ficheiro Location not writable Title of permissions popup Localização sem permissão de escrita You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Não tem permissão de escrita aqui. Escolha outro local ou cancele a operação. Resuming... file transfer widget A continuar... Cancel transfer Cancelar transferência Pause transfer Pausar transferência Resume transfer Continuar transferência Accept transfer Aceitar transferência Save a file Title of the file saving dialog Gravar um ficheiro Paused file transfer widget Pausado Open file Abrir o ficheiro Open file directory Abrir pasta do ficheiro Remote Paused file transfer widget Remoto pausado FilesForm Downloads Recebidos Uploads Enviados Transferred Files "Headline" of the window Transferências de ficheiros FriendListWidget Today Hoje Yesterday Ontem Last 7 days Últimos 7 dias This month Este mês Older than 6 Months Mais de 6 Meses Never Nunca FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Solicitação de contacto Someone wants to make friends with you Alguém quer adicioná-lo como contacto User ID: Identificador do utilizador: Friend request message: Mensagem de pedido de contacto: Accept Accept a friend request Aceitar Reject Reject a friend request Rejeitar FriendWidget Invite to group Menu to invite a friend to a groupchat Convidar para grupo Move to circle... Menu to move a friend into a different circle Mover para círculo... To new circle Para um novo círculo Remove from circle '%1' Remover do círculo "%1" Move to circle "%1" Mover para o círculo "%1" Set alias... Definir o apelido... Auto accept files from this friend context menu entry Aceitar ficheiros deste contacto automaticamente Remove friend Menu to remove the friend from our friendlist Remover contacto Choose an auto accept directory popup title Escolher uma pasta para onde aceitar ficheiros automaticamente New message Nova mensagem Online Conectado Away Ausente Busy Ocupado Offline Desconectado Open chat in new window Abrir conversa numa janela nova Remove chat from this window Retirar conversa desta janela To new group Para um novo grupo Invite to group '%1' Convidar para o grupo '%1' Show details Mostrar detalhes GeneralForm General Geral Choose an auto accept directory popup title Escolher uma pasta para onde aceitar ficheiros automaticamente GeneralSettings General Settings Configurações gerais The translation may not load until qTox restarts. A tradução só pode ser atualizada após reiniciar o qTox. Show system tray icon Mostrar ícone na barra de tarefas Enable light tray icon. toolTip for light icon setting Ativar ícone claro da barra de tarefas. qTox will start minimized in tray. toolTip for Start in tray setting O qTox vai iniciar minimizado na barra de tarefas. Start in tray Inicializar na barra de tarefas After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Após clicar em fechar (X), o qTox será minimizado para a barra de tarefas em vez de fechar. Close to tray Fechar para a barra de tarefas After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Após clicar em minimizar (_) o qTox será minimizado para a barra de tarefas, em vez da barra de tarefas do sistema. Minimize to tray Minimizar para a barra de tarefas Light icon Ícone claro Language: Idioma: Set where files will be saved. Definir onde os ficheiros serão guardados. Your status is changed to Away after set period of inactivity. O seu estado é alterado para Ausente após o período determinado de inatividade. Auto away after (0 to disable): Ausente após (0 para desativar): Show contacts' status changes Mostrar alterações de estado dos contactos Set to 0 to disable Utilizar 0 para desativar You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Pode definir esta configuração por contacto clicando com o botão direito sobre eles. Autoaccept files Aceitar ficheiros automaticamente Autostart Iniciar automaticamente Start qTox on operating system startup (current profile). Iniciar o qTox ao iniciar o sistema operativo (perfil de utilizador atual). Default directory to save files: Pasta padrão para onde guardar ficheiros: Check for updates Verificar se existem atualizações Spell checking Verificação ortográfica Max autoaccept file size (0 to disable): Tamanho máximo do ficheiro a aceitar automaticamente (0 para desativar): MB MB GenericChatForm Send message Enviar mensagem Smileys Emoticons Send file(s) Enviar ficheiros Save chat log Guardar histórico da conversa Send a screenshot Enviar captura de ecrã Clear displayed messages Remover mensagens Cleared Removidas Quote selected text Citar texto selecionado Copy link address Copiar endereço do link Confirmation Confirmação You are sure that you want to clear all displayed messages? Tem a certeza que quer limpar todas as mensagens mostradas? Search in text Procurar no texto Go to current date Load chat history... Carregar histórico de conversas... Export to file Exportar para ficheiro GenericNetCamView Tox video Vídeo Tox Show Messages Mostrar mensagens Hide Messages Ocultar mensagens Full Screen Ecrã inteiro Toggle video preview Ativar/desativar previsão do vídeo Mute audio Sem som Mute microphone Desativar microfone End video call Terminar chamada de vídeo Exit full screen Sair do ecrã inteiro GroupChatForm %1 has set the title to %2 %1 definiu o título como %2 %1 has joined the group %1 juntou-se ao grupo %1 is now known as %2 %1 é conhecido agora como %2 %1 has left the group %1 saiu do grupo %n user(s) in chat Number of users in chat %n utilizador na conversa %n utilizadores na conversa mute sem som unmute com som GroupInviteForm Groups Grupos Create new group Criar um novo grupo Group invites Convites a grupos GroupInviteWidget Invited by %1 on %2 at %3. Convidado por %1 em %2 às %3. Join Entrar na conversa Decline Recusar GroupWidget Set title... Definir o título... Quit group Menu to quit a groupchat Sair do grupo Open chat in new window Abrir conversa noutra janela Remove chat from this window Remover conversa desta janela %n user(s) in chat Number of users in chat %n utilizador na conversa %n utilizadores na conversa New Message Nova mensagem Online Conectado IdentitySettings Public Information Informação pública Tox ID Referência do Tox This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Este conjunto de caracteres diz aos outros clientes Tox como o contactar. Partilhe com seus contactos para comunicar com eles. Your Tox ID (click to copy) A sua Referência do Tox (clique para copiar) This QR code contains your Tox ID. You may share this with your friends as well. Este código QR contém a sua Referência do Tox. Pode partilhá-la com os seus amigos. Save image Guardar imagem Copy image Copiar imagem Profile Perfil Rename profile. tooltip for renaming profile button Alterar nome do perfil. Delete profile. delete profile button tooltip Eliminar perfil. Go back to the login screen tooltip for logout button Voltar para o ecrã de iniciar sessão Logout import profile button Encerrar sessão Remove password Remover palavra-passe Change password Mudar palavra-passe Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Permite exportar o seu perfil Tox para um ficheiro. O perfil não contém o seu histórico. Rename rename profile button Renomear Export export profile button Exportar Delete delete profile button Eliminar Server Servidor Hide my name from the public list Ocultar o meu nome na lista pública Register Registo Your password A sua palavra-passe Update Atualizar Register on ToxMe Registar em ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Nome para o serviço ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Opcional. Algo sobre si ou sobre o seu gato. Optional. Something about you. Or your cat. Tooltip for the Biography field. Opcional. Algo sobre si ou sobre o seu gato. ToxMe service to register on. Serviço ToxMe para registar. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Se não estiver definido, as entradas ToxMe são visíveis publicamente. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Remover a sua palavra-passe e encriptação do seu perfil. Name input Introduzir nome Name visible to contacts Nome visível para os contactos Status message input Introduzir mensagem de estado Status message visible to contacts Mensagem de estado visível para os contactos Your Tox ID A sua Referência do Tox Save QR image as file Guardar imagem QR num ficheiro Copy QR image to clipboard Copiar imagem QR para a área de transferência ToxMe username to be shown on ToxMe Nome de utilizador ToxMe para ser mostrado no ToxMe Optional ToxMe biography to be shown on ToxMe Biografia ToxMe opcional para ser mostrada no ToxMe ToxMe service address Endereço do serviço ToxMe Visibility on the ToxMe service Visibilidade no serviço ToxMe Password Palavra-passe Update ToxMe entry Atualizar entrada ToxMe Rename profile. Alterar nome do perfil. Delete profile. Eliminar perfil. Export profile Exportar perfil Remove password from profile Remover a palavra-passe do perfil Change profile password Alterar a palavra-passe do perfil My name: Meu nome: My status: Meu estado: My username Meu nome de utilizador My biography Minha biografia My profile Meu perfil LoadHistoryDialog Load History Dialog Carregar histórico Load history from to (about 100 messages are loaded) Select Date Dialog Janela de selecionar data Select a date Selecione uma data LoginScreen Username: Utilizador: Password: Palavra-passe: Confirm: Confirmar: Password strength: %p% Segurança da palavra-passe: %p% If the profile does not have a password, qTox can skip the login screen Se o perfil não tiver uma palavra-passe, o qTox não mostra o ecrã de iniciar sessão e autentica automaticamente New Profile Novo perfil Couldn't create a new profile Não foi possível criar um novo perfil The username must not be empty. O nome de utilizador não pode estar vazio. The password must be at least 6 characters long. A palavra-passe deve ter pelo menos 6 caracteres. The passwords you've entered are different. Please make sure to enter same password twice. As palavra-passe digitadas são diferentes. Certifique-se que introduziu a mesma palavra-passe duas vezes. A profile with this name already exists. Já existe um perfil com este nome. Couldn't load this profile Não foi possível carregar o perfil This profile is already in use. Este perfil já está a ser utilizado. Wrong password. Palavra-passe incorreta. Create Profile Criar perfil Load automatically Carregar automaticamente Import Importar Load Carregar Load Profile Carregar perfil Password protected profiles can't be automatically loaded. Os perfis protegidos por palavra-passe não podem ser carregados automaticamente. Couldn't load profile Não foi possível carregar o perfil There is no selected profile. You may want to create one. Não está selecionado nenhum perfil. Talvez queira criar um. Username input field Campo para introduzir o nome de utilizador Password input field, you can leave it empty (no password), or type at least 6 characters Campo de entrada da palavra-passe. Pode deixá-lo vazio (sem palavra-passe) ou digitar pelo menos 6 caracteres Password confirmation field Campo de confirmação da palavra-passe Create a new profile button Botão de criar um novo perfil Profile list Lista de perfis List of profiles Lista de perfis Password input Introduzir palavra-passe Load automatically checkbox Caixa de seleção para carregar automaticamente Import profile Importar perfil Load selected profile button Botão para carregar o perfil selecionado New profile creation page Página de criação de novo perfil Loading existing profile page Carregar página de perfil já existente MainWindow Your name O seu nome Your status O seu estado ... ... Add friends Adicionar contactos Create a group chat Criar uma conversa em grupo View completed file transfers Ver transferências de ficheiros completadas Change your settings Alterar as configurações Close Fechar Open profile Abrir perfil Open profile page when clicked Abrir a página de perfil ao clicar Status message input Introduzir mensagem de estado Set your status message that will be shown to others Defina a sua mensagem de estado que será mostrada aos outros Status Estado Set availability status Definir estado de disponibilidade Contact search Procurar contacto Contact search input for known friends Introduzir o contacto conhecido a procurar Sorting and visibility Ordenação e visibilidade Set friends sorting and visibility Definir ordem e visibilidade dos contactos Open Add friends page Abrir página de adicionar contactos Groupchat Conversa em grupo Open groupchat management page Abrir a página de gestão de conversa em grupo File transfers history Histórico de transferências de ficheiros Open File transfers history Abrir histórico de transferências de ficheiros Settings Configurações Open Settings Abrir configurações Nexus View OS X Menu bar Visualizar Window OS X Menu bar Janela Minimize OS X Menu bar Minimizar Bring All to Front OS X Menu bar Trazer tudo para a frente Exit Fullscreen Sair do ecrã inteiro Enter Fullscreen Mudar para ecrã inteiro NotificationEdgeWidget Unread message(s) Mensagem não lida Mensagens não lidas PasswordEdit CAPS-LOCK ENABLED TECLA DE MAIÚSCULAS ATIVADA PrivacyForm Privacy Privacidade Confirmation Confirmação Do you want to permanently delete all chat history? Quer eliminar de forma permanente todo o histórico de conversas? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Os seus contactos poderão ver quando está a escrever. Send typing notifications Enviar notificação de escrever Keep chat history Guardar histórico de conversas NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam faz parte da sua referência do Tox. Se estiver a receber muitas solicitações indesejadas, deve alterar a configuração do NoSpam. As outras pessoas não poderão adicioná-lo com a sua referência do Tox, mas poderá manter os seus contactos atuais. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam faz parte da sua referência do Tox e pode ser alterado. Se estiver a receber muitas solicitações indesejadas, altere o NoSpam. Generate random NoSpam Gerar NoSpam aleatório Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting O histórico de conversas ainda está a ser desenvolvido. Por isso podem ocorrer alterações no formato do ficheiro guardado, o que pode levar a perda de dados. Privacy Privacidade BlackList Lista negra Filter group message by group member's public key. Put public key here, one per line. Filtrar mensagem de grupo por chave pública do membro do grupo. Introduza a chave pública aqui, uma por linha. Profile Failed to derive key from password, the profile won't use the new password. Falha ao derivar a chave da palavra-passe, o perfil não utilizará a nova palavra-passe. Couldn't change password on the database, it might be corrupted or use the old password. Não foi possível alterar a palavra-passe na base de dados. Esta pode estar corrompida ou pode estar a usar a palavra-passe antiga. Toxing on qTox A toxear no qTox ProfileForm Choose a profile picture Escolha uma imagem para o perfil Error Erro Unable to open this file. Não foi possível abrir este ficheiro. Unable to read this image. Não foi possível ler esta imagem. The supplied image is too large. Please use another image. A imagem é muito grande. Por favor escolha outra. Rename "%1" renaming a profile Alterar o nome de "%1" Couldn't rename the profile to "%1" Não foi possível alterar o nome do perfil para "%1" Location not writable Title of permissions popup Localização sem permissão de escrita You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Não tem permissão de escrita nesta localização. Escolha outro local ou cancele a operação. Failed to copy file Falha ao copiar o ficheiro The file you chose could not be written to. O Não foi possível guardar o ficheiro que escolheu. Really delete profile? deletion confirmation title Quer mesmo eliminar perfil? Are you sure you want to delete this profile? deletion confirmation text Tem a certeza de que quer eliminar este perfil? Save save qr image Guardar Save QrCode (*.png) save dialog filter Guardar código QR (*.png) Nothing to remove Nada para remover Your profile does not have a password! O seu perfil não tem uma palavra-passe! Really delete password? deletion confirmation title Eliminar mesmo a palavra-passe? Please enter a new password. Introduza uma nova palavra-passe. Current profile: Perfil atual: Remove Remover Files could not be deleted! deletion failed title Não foi possível eliminar os ficheiros! Register (processing) Registo (a processar) Update (processing) Atualizar (a processar) Done! Feito! Account %1@%2 updated successfully Conta %1@%2 atualizada com sucesso Successfully added %1@%2 to the database. Save your password %1@%2 foi adicionado com sucesso à base de dados. Guarde a sua palavra-passe Toxme error Erro do Toxme Register Registar Update Atualizar Change password button text Mudar a palavra-passe Set profile password button text Definir a palavra-passe do perfil Current profile location: %1 Localização atual do perfil: %1 Couldn't change password Não foi possível alterar a palavra-passe This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Esse monte de caracteres indica aos outros clientes Tox como podem contactá-lo. Partilhe com os seus contactos para comunicar com eles. Esta referência inclui o código NoSpam (a azul) e a soma de verificação/checksum (a cinza). Empty path is unavaliable O caminho vazio não está disponível Failed to rename Não foi possível alterar o nome Profile already exists O perfil já existe A profile named "%1" already exists. Já existe um perfil com o nome "%1". Empty name Nome vazio Empty name is unavaliable Nome vazio não está disponível Empty path Caminho vazio Couldn't change password on the database, it might be corrupted or use the old password. Não foi possível alterar a palavra-passe na base de dados. Esta pode estar corrompida ou estar a utilizar a palavra-passe antiga. Export profile Exportar perfil Tox save file (*.tox) save dialog filter Guardar ficheiro Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Não foi possível eliminar os seguintes ficheiros: Please manually remove them. deletion failed text part 2 Por favor remova-os maunalmente. Are you sure you want to delete your password? deletion confirmation text Tem certeza que quer eliminar a sua palavra-passe? Images (%1) filetype filter Imagens (%1) ProfileImporter Import profile import dialog title Importar perfil Tox save file (*.tox) import dialog filter Ficheiro no formato Tox (*.tox) Ignoring non-Tox file popup title A ignorar ficheiro não Tox Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Aviso: escolheu um ficheiro que não é um ficheiro do formato Tox; a ignorar. Profile already exists import confirm title O perfil já existe A profile named "%1" already exists. Do you want to erase it? import confirm text Já existe um perfil com o nome "%1". Quer eliminá-lo? File doesn't exist O ficheiro não existe Profile doesn't exist O perfil não existe Profile imported Perfil importado %1.tox was successfully imported %1.tox importado com sucesso QApplication Ok OK Cancel Cancelar Yes Sim No Não LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Da esquerda para a direita QMessageBox Couldn't add friend Não foi possível adicionar o contacto %1 is not a valid Toxme address. %1 não é um endereço Toxme válido. You can't add yourself as a friend! When trying to add your own Tox ID as friend Não pode adicionar-se como seu contacto! QObject Tox URI to parse URI do Tox para processar Starts new instance and loads specified profile. Inicia uma nova instância e carrega o perfil especificado. profile perfil Default Padrão Blue Azul Olive Verde-oliva Red Vermelho Violet Violeta Incoming call... A receber chamada... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Olá, sou %1 ! Queres adicionar-me ao Tox? Server doesn't support Toxme O servidor não suporta Toxme You're making too many requests. Wait an hour and try again Está a fazer muitos pedidos. Aguarde uma hora e tente novamente This name is already in use Este nome já está a ser utilizado This Tox ID is already registered under another name Esta referência do Tox já está registada com outro nome Please don't use a space in your name Não inclua espaços no seu nome Password incorrect Palavra-passe incorreta You can't use this name Não pode usar este nome Name not found Nome não encontrado Tox ID not sent A referência do Tox não foi enviada That user does not exist Esse utilizador não existe Error Erro qTox couldn't open your chat logs, they will be disabled. O qTox não consegui abrir os seus registos de conversas, por isso serão desativados. None No camera device set Nenhum Desktop Desktop as a camera input for screen sharing Área de trabalho Problem with HTTPS connection Problema com a conexão HTTPS Internal ToxMe error Erro interno do Toxme Reformatting text in progress.. A reformatar o texto... Starts new instance and opens the login screen. Inicia uma nova instância e abre o ecrã de início de sessão. Dark Escuro Dark blue Escuro azulado Dark olive Escuro oliva Dark red Escuro avermelhado Dark violet Escuro violeta Failed to load profile automatically. online contact status conectado away contact status ausente busy contact status ocupado offline contact status desconectado blocked contact status bloqueado RemoveFriendDialog Remove friend Remover contacto Also remove chat history Remover também o histórico de conversas Remove Remover Are you sure you want to remove %1 from your contacts list? Tem a certeza que quer remover %1 da sua lista de contactos? Remove all chat history with the friend if set Se definido, remove todo o histórico de conversas com o contacto ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Clique e arraste para selecionar uma região. Pressione %1 para ocultar/mostrar a janela do qTox, ou %2 para cancelar. Space [Space] key on the keyboard Espaço Escape [Escape] key on the keyboard Esc Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Pressione %1 para enviar uma captura de ecrã da seleção, %2 para ocultar/mostrar a janela do qTox ou %3 para cancelar. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. O texto não foi encontrado. Start Iniciar SearchSettingsForm Form Formulário Start search: Iniciar procura: from the end do fim from the beginning do início after date após a data before date antes da data 00.00.0000 00.00.0000 Case sensitive Sensível a maiúsculas Whole words only Apenas palavras completas Use regular expressions Usar expressões regulares SetPasswordDialog Set your password Definir a sua palavra-passe The password is too short Palavra-passe muito curta The password doesn't match. As palavras-passe não são iguais. Confirm: Confirmar: Password: Palavra-passe: Password strength: %p% Segurança da palavra-passe: %p% Confirm password Confirmar palavra-passe Confirm password input Confirmar palavra-passe Password input Introduzir palavra-passe Password input field, minimum 6 characters long Campo de entrada da palavra-passe, mínimo de 6 caracteres Settings Circle #%1 Círculo #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Adicionar um contacto Do you want to add %1 as a friend? Quer adicionar %1 aos seus contactos? User ID: Referência do utilizador: Friend request message: Mensagem de pedido de amizade: Send Send a friend request Enviar Cancel Don't send a friend request Cancelar UserInterfaceForm None Nenhum User Interface Interface de utilizador UserInterfaceSettings Chat Conversas Base font: Fonte de base: px px Size: Tamanho: New text styling preference may not load until qTox restarts. Pode ser necessário reiniciar o qTox para que a preferência do estilo de texto surta efeito. Text Style format: Formato do estilo do texto: Select text styling preference. Selecione a preferência do estilo do texto. Plaintext Texto sem formatação Show formatting characters Mostrar símbolos de formatação Don't show formatting characters Não mostrar os símbolos de formatação New message Nova mensagem Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Abre a janela do qTox quando receber uma nova mensagem caso não esteja nenhuma aberta. Open window Abrir janela Contact list Lista de contactos If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Se ativado, as conversas em grupo serão colocadas no topo da sua lista de contactos. Caso contrário, estarão abaixo dos contactos conectados. Place groupchats at top of friend list Colocar conversas de grupo no topo da lista de contactos Your contact list will be shown in compact mode. toolTip for compact layout setting A sua lista de contactos será mostrada no modo compacto. Compact contact list Lista de contactos compacta Multiple windows mode Modo de janelas múltiplas Open each chat in an individual window Abre cada uma das conversas numa janela individual Emoticons Emoticons Use emoticons Usar emoticons Smiley Pack: Text on smiley pack label Pacote de emoticons: Emoticon size: Tamanho dos emoticons: px px Theme Tema Style: Estilo: Theme color: Cor do tema: Timestamp format: Formato de hora: Date format: Formato de data: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Se ativado, cada contacto sem um avatar terá um gerado com base na sua referência do Tox em vez de uma imagem padrão. É necessário reiniciar para aplicar as alterações. Use identicons instead of empty avatars Usar identicons em vez de avatares em branco Use colored nicknames in chats Usar nomes coloridos nas conversas Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Mostrar uma notificação quando recebe uma mensagem nova e a janela não está selecionada. Notify Notificar Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Apenas notificar sobre novas mensagens quando for mencionado em conversas de grupo. Group chats only notify when mentioned Apenas notificar quando for mencionado em conversas de grupo Play sound Tocar som Play sound while Busy Reproduzir som enquanto estiver Ocupado Notify via desktop notifications Notificar através de notificações da área de trabalho Hide message sender and contents Widget Online Conectados Add new circle... Adicionar círculo novo... By Name Por nome By Activity Por atividade All Todos Offline Desconectados Friends Amigos Groups Grupos Search Contacts Procurar contactos Online Button to set your status to 'Online' Conectado Away Button to set your status to 'Away' Ausente Busy Button to set your status to 'Busy' Ocupado toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text O Toxcore falhou ao inicializar as suas configurações de proxy. O qTox não pode ser executado, por favor altere as suas configurações e reinicie o programa. File Ficheiro Edit Profile Editar perfil Change Status Alterar estado Log out Sair Edit Editar Filter... Filtrar... Contacts Contactos Add Contact... Adicionar contacto... Next Conversation Próxima conversa Previous Conversation Conversa anterior Executable file popup title Ficheiro executável You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Pediu ao qTox para abrir um ficheiro executável. Os executáveis podem potencialmente danificar o seu computador. Tem a certeza que quer abrir este ficheiro? Couldn't request friendship Não foi possível pedir amizade Status Estado Message failed to send O envio da mensagem falhou toxcore failed to start, the application will terminate after you close this message. O toxcore falhou ao iniciar, o programa será encerrado após fechar esta mensagem. Your name O seu nome Groupchat #%1 Conversa em grupo #%1 Create new group... Criar novo grupo... %n New Friend Request(s) %n novo pedido de amizade %n novos pedidos de amizade %n New Group Invite(s) %n convite de novo grupo %n convites de novo grupo Logout Tray action menu to logout user Encerrar sessão Exit Tray action menu to exit tox Sair Show Tray action menu to show qTox window Mostrar Add friend title of the window Adicionar contacto Group invites title of the window Convites a grupos File transfers title of the window Transferências de ficheiros Settings title of the window Configurações My profile title of the window Meu perfil Failed to send file "%1" Falha ao enviar o ficheiro "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/pt_BR.ts000066400000000000000000003322601415623743500161300ustar00rootroot00000000000000 AVForm Audio/Video Áudio/Vídeo Default resolution Resolução padrão Disabled Desabilitado Select region Selecionar região Screen %1 Tela %1 Audio Settings Configurações de Áudio Gain Ganho Playback device Dispositivo de Reprodução Use slider to set volume of your speakers. Deslize para ajustar o volume dos auto-falantes. Capture device Dispositivo de Captura Volume Volume Video Settings Configurações de Vídeo Video device Dispositivo de Vídeo Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Define a resolução da sua câmera. Valores mais altos fornecem uma qualidade melhor. Observe no entanto que uma qualidade de vídeo maior exige uma conexão melhor com a internet. Eventualmente sua conexão pode não ser suficiente para uma qualidade de vídeo maior, que pode acarretar em problemas nas chamadas de vídeo. Resolution Resolução Rescan devices Re-escanear dispositivos Test Sound Testar Som Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Habilita o backend de áudio experimental com suporte a cancelamento de eco, necessita reiniciar o qTox para ser ativado. Enable experimental audio backend Habilita backend de audio experimental Audio quality Qualidade de áudio Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Qualidade de áudio transmitido. Reduza essa configuração se sua largura de banda não é alta o suficiente ou se você deseja reduzir seu uso de Internet. High (64 kbps) Alta (64 kbps) Medium (32 kbps) Média (32 kbps) Low (16 kbps) Baixa (16 kbps) Very low (8 kbps) Muito baixa (8 kbps) Threshold Limite AboutForm About Sobre Original author: %1 Autor original: %1 You are using qTox version %1. Você está usando a versão %1 do qTox. Commit hash: %1 Hash do commit: %1 toxcore version: %1 Versão do toxcore: %1 Qt version: %1 Versão do Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Uma lista de todos os problemas conhecidos pode ser encontrada no nosso % 1 no Github. Se você descobrir um bug ou vulnerabilidade de segurança no qTox, informe-o de acordo com as diretrizes em nosso artigo wiki % 2. Click here to report a bug. Clique aqui para comunicar um bug. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Veja uma lista completa de %1 no Github bug-tracker Replaces `%1` in the `A list of all known…` bug tracker Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Escrevendo Relatórios de Erros Úteis contributors Replaces `%1` in `See a full list of…` contribuidores AboutFriendForm Dialog Diálogo username nome de usuário status message mensagem de status Used aliases: Pseudônimos usados: HISTORY OF ALIASES HISTÓRICO DE PSEUDÔNIMOS Automatically accept files from contact if set Se marcado, aceita automaticamente arquivos do contato Auto accept files Aceitar arquivos automaticamente Default directory to save files: Diretório padrão para salvar arquivos: Auto accept for this contact is disabled Aceitar automaticamente está desabilitado para esse contato Auto accept call: Aceitar chamada automaticamente: Manual Manual Audio Áudio Audio + Video Áudio + Vídeo Automatically accept group chat invitations from this contact if set. Se marcado, aceita automaticamente os convites de bate-papo em grupo desse contato. Auto accept group invites Aceitar automaticamente convites de grupos Remove history (operation can not be undone!) Apagar histórico (operação irreversível!) Notes Notas Input field for notes about the contact Campo de entrada para notas sobre o contato You can save comment about this contact here. Você pode salvar comentários sobre esse contato aqui. History removed Histórico apagado Choose an auto accept directory popup title Escolher um diretório para aceitar arquivos automaticamente <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Essa é a chave pública do seu amigo, use-a para confirmar sua identidade por meio de outro canal. Você não pode enviar isso para outras pessoas para que elas possam adicionar esse contato.</p></body></html> Public key (not ToxID): Chave pública (não o ToxId): Confirmation Confirmar Are you sure to remove %1 chat history? Tem certeza que deseja remover %1 do histórico de conversas? Failed to remove chat history with %1! Falha ao remover o histórico de conversas com % 1! AboutSettings Version Versão License Licença Authors Autores Known Issues Problemas Conhecidos Open update download link Abrir link de download da atualização Update available Atualização disponível qTox is up to date ✓ qTox está atualizado ✓ AddFriendForm Add Friends Adicionar aos Contatos Invalid Tox ID format Formato inválido de ID Tox Send friend request Enviar pedido de amizade Add a friend Adicionar um contato Friend requests Solicitações de amizade Accept Aceitar Reject Rejeitar Couldn't add friend Não foi possível adicionar amigo Tox ID, either 76 hexadecimal characters or name@example.com ID Tox, sejam os 76 caracteres hexadecimais ou nome@exemplo.com Type in Tox ID of your friend Digite o ID Tox do seu amigo Friend request message Mensagem de solicitação de amigo Type message to send with the friend request or leave empty to send a default message Digite a mensagem para enviar com a solicitação de amizade ou deixe vazio para enviar uma mensagem padrão %1 Tox ID is invalid or does not exist Toxme error Tox ID %1 é inválido ou não existe You can't add yourself as a friend! When trying to add your own Tox ID as friend Você não pode adicionar a si mesmo como contato! Open contact list Abrir lista de contatos Couldn't open file Não foi possível abrir o arquivo Couldn't open the contact file Error message when trying to open a contact list file to import Não foi possível abrir o arquivo de contatos Invalid file Arquivo inválido We couldn't find any contacts to import in this file! Não foi possível encontrar nenhum contato para importar nesse arquivo! Tox ID Tox ID of the person you're sending a friend request to ID Tox either 76 hexadecimal characters or name@example.com Tox ID format description ou 76 caracteres hexadecimais ou nome@exemplo.com Message The message you send in friend requests Mensagem Open Button to choose a file with a list of contacts to import Abrir Send friend requests Enviar pedidos de amizade %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Olá, aqui é %1 ! Gostaria de me adicionar no Tox? Import a list of contacts, one Tox ID per line Importar uma lista de contatos, um ID Tox por linha Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Pronto para importar %n contato, clique em enviar para confirmar Pronto para importar %n contatos, clique em enviar para confirmar Import contacts Importar contatos AdvancedForm Advanced Avançado Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. A menos que você %1 saiba o que está fazendo, por favor %2 faça alterações aqui. Mudanças podem levar a problemas com o qTox, e até perda de suas informações, como histórico. really realmente not não IMPORTANT NOTE NOTA IMPORTANTE Reset settings Redefinir configurações All settings will be reset to default. Are you sure? Todas configurações serão redefinidas para o padrão. Deseja prosseguir? Yes Sim No Não Call active popup title Chamada ativa You can't disconnect while a call is active! popup text Você não pode desconectar enquanto uma chamada estiver ativa! Save File Salvar arquivo Logs (*.log) Registros (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Armazena as configurações no diretório atual ao invés do diretório de configurações predefinido Make Tox portable Deixe o Tox portável Reset to default settings Restaurar às configurações padrões Portable Portátil Connection Settings Configurações de Conexão Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Habilitar IPv6 (recomendado) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Desabilitar esta opção permite, por exemplo, utilizar a rede Tor. Ela adiciona mais dados à rede Tor no entanto, portanto desmarque apenas se necessário. Enable UDP (recommended) Text on checkbox to disable UDP Habilitar UDP (recomendado) Proxy type: Tipo de proxy: Address: Text on proxy addr label Endereço: Port: Text on proxy port label Porta: None Nenhum SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Reconectar Debug Depurar Export Debug Log Exportar Registro de Depuração Copy Debug Log Copiar Registro de Depuração Enable LAN discovery Ativar descoberta de LAN ChatForm Send a file Enviar um arquivo qTox wasn't able to open %1 qTox não foi capaz de abrir %1 Unable to open Impossível abrir Bad idea Má idéia %1 calling %1 chamando Calling %1 Chamando %1 Failed to open temporary file Temporary file for screenshot Não foi possível abrir o arquivo temporário qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox não conseguiu salvar a imagem capturada Call with %1 ended. %2 Chamada para %1 terminada. %2 Call duration: Duração da chamada: %1 is typing %1 está digitando Copy Copiar You're trying to send a sequential file, which is not going to work! Você está tentando enviar um arquivo sequencial, o que não vai funcionar! %1 is now %2 e.g. "Dubslow is now online" %1 agora é %2 Call with %1 ended unexpectedly. %2 A chamada com %1 terminou inesperadamente. %2 Filename contained illegal characters O nome do arquivo continha caracteres não autorizados Illegal characters have been changed to _ so you can save the file on windows. Caracteres não autorizados foram alterados para _ para que você possa salvar o arquivo no windows. ChatFormHeader Can't start audio call Não foi possível iniciar a chamada de áudio Start audio call Iniciar chamada de áudio End audio call Terminar chamada de áudio Cancel audio call Cancelar chamada de áudio Accept audio call Aceitar chamada de áudio Can't start video call Não foi possível iniciar a chamada de vídeo Start video call Iniciar chamada de vídeo End video call Terminar chamada de vídeo Cancel video call Cancelar chamada de vídeo Accept video call Aceitar chamada de vídeo Sound can be disabled only during a call O som só pode ser desabilitado durante uma chamada Unmute call Ativar áudio da chamada Mute call Silenciar chamada Microphone can be muted only during a call O microfone só pode ser desativado durante uma chamada Unmute microphone Habilitar microfone Mute microphone Silenciar microfone ChatLog Copy Copiar Select all Selecionar tudo pending pendente ChatTextEdit Type your message here... Digite sua mensagem aqui... CircleWidget Rename circle Menu for renaming a circle Renomear círculo Remove circle Menu for removing a circle Remover círculo Open all in new window Abrir tudo em uma nova janela Core /me offers friendship, "%1" /me oferece contato, "%1" Invalid Tox ID Error while sending friendship request Tox ID inválido You need to write a message with your request Error while sending friendship request Você precisa escrever uma mensagem junto com o seu pedido Your message is too long! Error while sending friendship request Sua mensagem é muito longa! Friend is already added Error while sending friendship request Amigo já adicionado Groupchat %1 Bate-papo em grupo %1 DesktopNotify New message Nova mensagem Incoming file transfer Recebendo transferência de arquivo Friend request received Pedido de amizade recebido New group message Nova mensagem de grupo Group invite received Convite para grupo recebido FileTransferWidget Form Ausgelassen Formulário 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen T:10:10 Filename Ausgelassen Nome do arquivo Waiting to send... file transfer widget Esperando para enviar... Accept to receive this file file transfer widget Aceite recebimento deste arquivo Location not writable Title of permissions popup Impossível salvar aqui You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Você não possui permissão de escrita aqui. Escolha outro local ou cancele a operação. Resuming... file transfer widget Continuando... Cancel transfer Cancelar transferência Pause transfer Pausar transferência Paused file transfer widget Pausado Open file Abrir o arquivo Open file directory Abrir pasta do arquivo Resume transfer Continuar transferência Accept transfer Aceitar transferência Save a file Title of the file saving dialog Salvar um arquivo Remote Paused file transfer widget Pausa no remoto FilesForm Transferred Files "Headline" of the window Transferências Downloads Recebidos Uploads Enviados FriendListWidget Today Hoje Yesterday Ontem Last 7 days Últimos 7 dias This month Este mês Older than 6 Months Mais de 6 Meses Never Nunca FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Solicitação de contato Someone wants to make friends with you Alguém quer adicionar você como contato User ID: ID do usuário: Friend request message: Mensagem de requisição contato: Accept Accept a friend request Aceitar Reject Reject a friend request Rejeitar FriendWidget Invite to group Menu to invite a friend to a groupchat Convidar para grupo Move to circle... Menu to move a friend into a different circle Mover para círculo... To new circle Para um novo círculo Remove from circle '%1' Remover do círculo "%1" Move to circle "%1" Mover para o círculo "%1" Open chat in new window Abrir bate-papo em uma nova janela Remove chat from this window Retirar bate-papo desta janela To new group Para um novo grupo Invite to group '%1' Convidar ao grupo '%1' Set alias... Apelido... Auto accept files from this friend context menu entry Aceitar arquivos automaticamente deste contato Remove friend Menu to remove the friend from our friendlist Remover contato Show details Mostrar detalhes Choose an auto accept directory popup title Escolher um diretório para aceitar arquivos automaticamente New message Nova mensagem Online Conectado Away Ausente Busy Ocupado Offline Ausgelassen Desconectado GeneralForm General Geral Choose an auto accept directory popup title Escolher um diretório para aceitar arquivos automaticamente GeneralSettings General Settings Configurações Gerais The translation may not load until qTox restarts. A tradução pode não ser atualizada antes do qTox ser reinicializado. Language: Idioma: Show system tray icon Mostrar ícone na bandeja Enable light tray icon. toolTip for light icon setting Habilitar ícone claro da bandeja. Light icon Ícone claro qTox will start minimized in tray. toolTip for Start in tray setting O qTox vai iniciar minimizado na bandeja. Start in tray Inicializar na bandeja After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Após clicar em fechar (X), o qTox será minimizado para a bandeja ao em vez de fechar. Close to tray Fechar para a bandeja After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Após clicar em minimizar (_) o qTox será minimizado para a bandeja, ao invés da barra de tarefas. Minimize to tray Minimizar para a bandeja Autostart Iniciar automaticamente Set where files will be saved. Defina onde os arquivos serão salvos. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Você pode definir esta configuração por contato clicando com o botão direito sobre eles. Autoaccept files Aceitar arquivos automaticamente Set to 0 to disable Defina 0 para desativar Your status is changed to Away after set period of inactivity. Seu status é alterado para Ausente após o período de inatividade. Auto away after (0 to disable): Ausente após (0 para desabilitar): Show contacts' status changes Mostrar alterações no status de contatos Start qTox on operating system startup (current profile). Iniciar qTox com o sistema operacional (usando atual perfil). Default directory to save files: Diretório de arquivos salvos padrão: Check for updates Verificar atualizações Spell checking Verificar ortografia Max autoaccept file size (0 to disable): Tamanho máximo do arquivo para recepção automática (0 para desativar): MB MB GenericChatForm Send message Enviar mensagem Smileys Emoticons Send file(s) Enviar arquivos Send a screenshot Enviar captura de tela Save chat log Armazenar histórico do bate-papo Clear displayed messages Remover mensagens Cleared Removidas Quote selected text Citar texto selecionado Copy link address Copiar endereço do link Confirmation Confirmar You are sure that you want to clear all displayed messages? Tem certeza de que deseja limpar todas as mensagens exibidas? Search in text Buscar no texto Go to current date Ir para a data atual Load chat history... Carregar histórico de bate-papo... Export to file Exportar para arquivo GenericNetCamView Tox video Vídeo Tox Show Messages Mostrar mensagens Hide Messages Esconder mensagens Full Screen Tela Cheia Toggle video preview Exibir/ocultar visualização de vídeo Mute audio Tirar som Mute microphone Desativar microfone End video call Terminar chamada de vídeo Exit full screen Sair da tela cheia GroupChatForm %1 has set the title to %2 %1 definiu o título como %2 %1 has joined the group %1 entrou no grupo %1 is now known as %2 %1 agora se chama %2 %1 has left the group %1 saiu do grupo %n user(s) in chat Number of users in chat %n usuário no bate-papo %n usuários no bate-papo mute mudo unmute ativar som GroupInviteForm Groups Grupos Create new group Criar um novo grupo Group invites Convites à grupos GroupInviteWidget Invited by %1 on %2 at %3. Convidado por %1 em %2 às %3. Join Unir-se Decline Recusar GroupWidget Set title... Defina o título... Open chat in new window Abrir bate-papo em outra janela Remove chat from this window Remover bate-papo desta janela Quit group Menu to quit a groupchat Sair do grupo %n user(s) in chat Number of users in chat %n usuário no bate-papo %n usuários no bate-papo New Message Nova Mensagem Online Online IdentitySettings Public Information Informações Públicas Tox ID ID Tox This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Este conjunto de caracteres informa a outros clientes Tox como contactar você. Compartilhe com seus contatos para se comunicar. Your Tox ID (click to copy) Seu ID Tox (clique para copiar) Profile Perfil Rename profile. tooltip for renaming profile button Renomear perfil. Go back to the login screen tooltip for logout button Voltar a tela inicial Logout import profile button Encerrar sessão Remove password Apagar senha Change password Mudar senha This QR code contains your Tox ID. You may share this with your friends as well. Este código QR contém seu ID Tox. Você pode compartilhá-lo com seus amigos. Save image Salvar imagem Copy image Copiar imagem Rename rename profile button Renomear Delete profile. delete profile button tooltip Excluir perfil. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Permite exportar seu perfil Tox para um arquivo. O perfil não contém o seu histórico. Export export profile button Exportar Delete delete profile button Excluir Server Servidor Hide my name from the public list Ocultar meu nome da lista pública Register Registro Your password Sua senha Update Atualizar Register on ToxMe Registrar em ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Nome para o serviço ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Opcional. Algo sobre você ou sobre seu gato. Optional. Something about you. Or your cat. Tooltip for the Biography field. Opcional. Algo sobre você ou sobre seu gato. ToxMe service to register on. Serviço ToxMe para registrar. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Se não estiver marcado, as entradas ToxMe são visíveis publicamente. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Apagar a sua senha e criptografia do seu perfil. Name input Entrada de nome Name visible to contacts Nome visível para os contatos Status message input Entrada de status de mensagem Status message visible to contacts Mensagem de status visível para contatos Your Tox ID Seu ID Tox Save QR image as file Salvar imagem QR como arquivo Copy QR image to clipboard Copiar imagem QR para a área de transferência ToxMe username to be shown on ToxMe Nome de usuário ToxMe para ser exibido no ToxMe Optional ToxMe biography to be shown on ToxMe Biografia ToxMe opcional para ser exibida no ToxMe ToxMe service address Endereço do serviço ToxMe Visibility on the ToxMe service Visibilidade no serviço ToxMe Password Senha Update ToxMe entry Atualizar entrada ToxMe Rename profile. Renomear perfil. Delete profile. Excluir perfil. Export profile Exportar perfil Remove password from profile Apagar a senha do perfil Change profile password Alterar a senha do perfil My name: Meu nome: My status: Meu estado: My username Meu nome de usuário My biography Minha biografia My profile Meu perfil LoadHistoryDialog Load History Dialog Carregar Histórico Load history Carregar histórico from de to para (about 100 messages are loaded) (aproximadamente 100 mensagens estão carregadas) Select Date Dialog Caixa de diálogo Selecionar data Select a date Selecionar uma data LoginScreen Username: Usuário: Password: Senha: Confirm: Confirmar: Password strength: %p% Segurança da senha: %p% Create Profile Criar Perfil If the profile does not have a password, qTox can skip the login screen Se o perfil não tiver uma senha, qTox pode pular a tela de entrada Load automatically Carregar automaticamente Load Carregar Load Profile Carregar Perfil New Profile Novo Perfil Couldn't create a new profile Não foi possível criar um novo perfil The username must not be empty. O nome de usuário não pode ser vazio. The password must be at least 6 characters long. A senha deve ter pelo menos 6 caracteres. The passwords you've entered are different. Please make sure to enter same password twice. As senha digitadas diferem. Certifique-se de que você entrou a mesma senha duas vezes. A profile with this name already exists. Um perfil com este nome já existe. Password protected profiles can't be automatically loaded. Perfis protegidos por senha não podem ser carregados automaticamente. Couldn't load profile Não foi possível carregar o perfil There is no selected profile. You may want to create one. Não há perfil selecionado. Você pode querer criar um. Couldn't load this profile Não foi possível carregar o perfil This profile is already in use. Este perfil já está em uso. Wrong password. Senha incorreta. Import Importar Username input field Campo de entrada do nome de usuário Password input field, you can leave it empty (no password), or type at least 6 characters Campo de entrada de senha, você pode deixá-lo vazio (sem senha) ou digitar pelo menos 6 caracteres Password confirmation field Campo de confirmação de senha Create a new profile button Criar um novo botão de perfil Profile list Lista de perfis List of profiles Lista de perfis Password input Digite sua senha Load automatically checkbox Carregar automaticamente caixa de seleção Import profile Importar perfil Load selected profile button Carregar o botão do perfil selecionado New profile creation page Página de criação de novo perfil Loading existing profile page Carregar página de perfil já existente MainWindow Your name Seu nome Your status Seu status ... Ausgelassen ... Add friends Adicionar contatos Create a group chat Criar um bate-papo de grupo View completed file transfers Ver transferências de arquivos completadas Change your settings Alterar suas configurações Close Fechar Open profile Abrir perfil Open profile page when clicked Abra a página de perfil ao clicar Status message input Entrada de status de mensagem Set your status message that will be shown to others Defina a sua mensagem de status que será exibia aos outros Status Status Set availability status Definir status de disponibilidade Contact search Buscar contatos Contact search input for known friends Entrada de busca de contato para amigos conhecidos Sorting and visibility Classificação e visibilidade Set friends sorting and visibility Definir classificação e visibilidade dos amigos Open Add friends page Abrir página Adicionar Amigos Groupchat Bate-papo em grupo Open groupchat management page Abrir a página de gerenciamento de bate-papo em grupo File transfers history Histórico de transferências de arquivos Open File transfers history Abrir histórico de transferências de arquivos Settings Configurações Open Settings Abrir Configurações Nexus View OS X Menu bar Visualizar Window OS X Menu bar Janela Minimize OS X Menu bar Minimizar Bring All to Front OS X Menu bar Trazer Todos para a Frente Exit Fullscreen Sair da Tela Cheia Enter Fullscreen Entrar em Tela Cheia NotificationEdgeWidget Unread message(s) Mensagem não lida Mensagens não lidas PasswordEdit CAPS-LOCK ENABLED TECLA CAPS-LOCK ATIVADA PrivacyForm Privacy Privacidade Confirmation Confirmação Do you want to permanently delete all chat history? Deseja excluir permanentemente todo o histórico de bate-papo? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Seus contatos poderão ver quando você estiver digitando. Send typing notifications Enviar notificação de digitação Keep chat history Guardar histórico de bate-papo NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam faz parte de seu ID Tox. Se você estiver recebendo muitas solicitações indesejadas, você deve mudar seu NoSpam. Não será possível lhe adicionar com seu ID antigo, mas você manterá sua lista de amigos. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam faz parte de seu ID Tox e pode ser mudado a vontade. Se você estiver recebendo muitas solicitações indesejadas, mude seu NoSpam. Generate random NoSpam Gerar NoSpam aleatório Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting O histórico de bate-papo ainda está em desenvolvimento. Mudanças no arquivo salvo podem ocorrer, isso pode resultar em perda de dados. Privacy Privacidade BlackList Lista negra Filter group message by group member's public key. Put public key here, one per line. Filtrar mensagem de grupo por chave pública do membro do grupo. Insira a chave pública aqui, uma por linha. Profile Failed to derive key from password, the profile won't use the new password. Falha ao derivar a chave da senha, o perfil não usará a nova senha. Couldn't change password on the database, it might be corrupted or use the old password. Não foi possível alterar a senha no banco de dados, ele pode estar corrompido ou usar a senha antiga. Toxing on qTox Toxeando com qTox ProfileForm Choose a profile picture Escolha uma imagem para o perfil Error Erro Rename "%1" renaming a profile Renomear "%1" Unable to open this file. Não é possível abrir esse arquivo. Current profile: Perfil atual: Remove Remover Unable to read this image. Não foi possível ler esta imagem. The supplied image is too large. Please use another image. A imagem é muito grande. Por favor, escolha outra. Couldn't rename the profile to "%1" Não foi possível renomear o perfil para "%1" Location not writable Title of permissions popup Impossível gravar aqui You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Você não possui permissão de escrita aqui. Escolha outro local ou cancele a operação. Failed to copy file Falha ao copiar o arquivo The file you chose could not be written to. O arquivo que você escolheu não pôde ser escrito. Really delete profile? deletion confirmation title Quer mesmo excluir o perfil? Nothing to remove Nada para remover Your profile does not have a password! Seu perfil não possui uma senha! Really delete password? deletion confirmation title Deseja mesmo excluir sua senha? Please enter a new password. Por favor, insira uma nova senha. Are you sure you want to delete this profile? deletion confirmation text Tem certeza de que deseja excluir esse perfil? Save save qr image Salvar Save QrCode (*.png) save dialog filter Salvar código QR (*.png) Files could not be deleted! deletion failed title Não foi possível excluir os arquivos! Register (processing) Registro (processando) Update (processing) Atualizar (processando) Done! Feito! Account %1@%2 updated successfully Conta %1@%2 atualizada com sucesso Successfully added %1@%2 to the database. Save your password Adicionado com êxito %1@%2 para o banco de dados. Salve a sua senha Toxme error Erro do Toxme Register Registro Update Atualizar Change password button text Mudar a senha Set profile password button text Definir senha do perfil Current profile location: %1 Localização atual do perfil: %1 Couldn't change password Não foi possível alterar a senha This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Esse monte de caracteres diz aos outros clientes Tox como entrar em contato com você. Compartilhe com seus amigos para se comunicar. Este ID inclui o código NoSpam (em azul) e a soma de verificação (em cinza). Empty path is unavaliable Caminho em branco não está disponível Failed to rename Não foi possível renomear Profile already exists O perfil já existe A profile named "%1" already exists. Um perfil chamado "%1" já existe. Empty name Nome em branco Empty name is unavaliable Nome em branco não está disponível Empty path Caminho em branco Couldn't change password on the database, it might be corrupted or use the old password. Não foi possível alterar a senha no banco de dados, ele pode estar corrompido ou usar a senha antiga. Export profile Exportar perfil Tox save file (*.tox) save dialog filter Salvar arquivo Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Os seguintes arquivos não puderam ser excluídos: Please manually remove them. deletion failed text part 2 Favor removê-los manualmente. Are you sure you want to delete your password? deletion confirmation text Tem certeza de que deseja excluir sua senha? Images (%1) filetype filter Imagens (%1) ProfileImporter Import profile import dialog title Importar perfil Tox save file (*.tox) import dialog filter Arquivo formato Tox (*.tox) Ignoring non-Tox file popup title Ignorando arquivos não Tox Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Aviso: você escolheu um arquivo que não é um arquivo de formato Tox; ignorando. Profile already exists import confirm title O perfil já existe A profile named "%1" already exists. Do you want to erase it? import confirm text Um perfil chamado "%1" já existe. Deseja sobrescrevê-lo? File doesn't exist O arquivo não existe Profile doesn't exist O perfil não existe Profile imported Perfil importado %1.tox was successfully imported %1.tox importado com sucesso QApplication Ok Ok Cancel Cancelar Yes Sim No Não LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Da esquerda para a direita QMessageBox Couldn't add friend Não foi possível adicionar amigo %1 is not a valid Toxme address. %1 não é um endereço Toxme válido. You can't add yourself as a friend! When trying to add your own Tox ID as friend Você não pode adicionar a si mesmo como contato! QObject Tox URI to parse UTI Tox para interpretar Starts new instance and loads specified profile. Inicia uma nova instância e carrega o perfil especificado. profile perfil Default Padrão Blue Azul Olive Verde-oliva Red Vermelho Violet Violeta Incoming call... Recebendo chamada... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Olá, aqui é %1 ! Gostaria de me adicionar no Tox? None No camera device set Nenhum Desktop Desktop as a camera input for screen sharing Área de trabalho Server doesn't support Toxme Toxme não é suportado pelo servidor You're making too many requests. Wait an hour and try again Você está fazendo muitas solicitações. Aguarde uma hora e tente novamente This name is already in use Este nome já está em uso This Tox ID is already registered under another name Este ID do Tox já está registrado sob outro nome Please don't use a space in your name Favor não incluir espaços no seu nome Password incorrect Senha incorreta You can't use this name Você não pode usar esse nome Name not found Nome não encontrado Tox ID not sent ID Tox não enviado That user does not exist Esse usuário não existe Error Erro qTox couldn't open your chat logs, they will be disabled. O qTox não pôde abrir seus registros de bate-papo, eles serão desativados. Problem with HTTPS connection Problema com a conexão HTTPS Internal ToxMe error Erro interno Toxme Reformatting text in progress.. Reformatação de texto em andamento... Starts new instance and opens the login screen. Inicia nova instância e abre a tela de login. Dark Escuro Dark blue Azul escuro Dark olive Verde oliva Dark red Vermelho escuro Dark violet Roxo Failed to load profile automatically. Falha ao carregar o perfil automaticamente. online contact status conectado away contact status ausente busy contact status ocupado offline contact status desconectado blocked contact status bloqueado RemoveFriendDialog Remove friend Remover contato Also remove chat history Também remover histórico de bate-papo Remove Remover Are you sure you want to remove %1 from your contacts list? Tem certeza de que deseja remover %1 da sua lista de contatos? Remove all chat history with the friend if set Se marcado, remove todo o histórico de bate-papo com o amigo ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Clique e arraste para selecionar uma região. Pressione %1 para ocultar/mostrar a janela do qTox, ou %2 para cancelar. Space [Space] key on the keyboard Espaço Escape [Escape] key on the keyboard Esc Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Pressione %1 para enviar uma captura de tela da seleção, %2 para ocultar/mostrar a janela do qTox ou %3 para cancelar. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Texto não encontrado. Start Iniciar SearchSettingsForm Form Formulário Start search: Iniciar busca: from the end a partir do fim from the beginning a partir do começo after date após a data before date antes da data 00.00.0000 00/00/0000 Case sensitive Observar maiúsculas e minúsculas Whole words only Somente palavras inteiras Use regular expressions Usar expressões comuns SetPasswordDialog Set your password Informe sua senha Confirm: Confirmar: Password: Senha: Password strength: %p% Segurança da senha: %p% The password is too short Senha muito curta The password doesn't match. Senhas não correspondem. Confirm password Confirmar senha Confirm password input Confirmar senha Password input Entrada de senha Password input field, minimum 6 characters long Campo de entrada da senha, mínimo 6 caracteres Settings Circle #%1 Círculo #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Adicionar um contato Do you want to add %1 as a friend? Você deseja adicionar %1 como seu contato? User ID: ID do usuário: Friend request message: Mensagem de requisição contato: Send Send a friend request Enviar Cancel Don't send a friend request Cancelar UserInterfaceForm None Nenhum User Interface Interface de usuário UserInterfaceSettings Chat Bate-papo Base font: Fonte de base: px px Size: Tamanho: New text styling preference may not load until qTox restarts. A preferência de novo estilo de texto pode não ser carregada até o qTox reiniciar. Text Style format: Formatar estilo de texto: Select text styling preference. Selecione a preferência de estilo de texto. Plaintext Texto sem formatação Show formatting characters Mostrar símbolos de formatação Don't show formatting characters Não mostrar os símbolos de formatação New message Nova mensagem Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Abre a janela do qTox quando você receber uma nova mensagem e nenhuma janela estiver ainda aberta. Open window Abrir janela Contact list Lista de contatos If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Se marcada, bate-papo em grupo serão colocados no topo de sua lista de amigos. Caso contrário, estarão abaixo dos amigos conectados. Place groupchats at top of friend list Colocar bate-papo em grupo no topo da lista de amigos Your contact list will be shown in compact mode. toolTip for compact layout setting Sua lista de contatos será exibida em modo compacto. Compact contact list Lista de contatos compacta Multiple windows mode Modo de janelas múltiplas Open each chat in an individual window Abra cada bate-papo em uma janela separada Emoticons Emoticons Use emoticons Usar emoticons Smiley Pack: Text on smiley pack label Pacote de emoticons: Emoticon size: Tamanho dos emoticons: px px Theme Tema Style: Estilo: Theme color: Cor do tema: Timestamp format: Formato de hora: Date format: Formato de data: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Se ativado, cada contato sem um avatar terá um gerado com base em seu Tox ID ao invés de uma imagem padrão. Requer reiniciar para aplicar modificações. Use identicons instead of empty avatars Use identicons em vez de avatares em branco Use colored nicknames in chats Usar nomes coloridos nos bate-papos Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Mostrar uma notificação quando você receber uma nova mensagem e a janela não estiver selecionada. Notify Notificar Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Somente notificar novas mensagens em bate-papos de grupo quando você for mencionado. Group chats only notify when mentioned Somente notificar quando for mencionado em bate-papos de grupo Play sound Tocar som Play sound while Busy Tocar som quando ocupado Notify via desktop notifications Notificar através de notificações da área de trabalho Hide message sender and contents Ocultar remetente e conteúdo da mensagem Widget Online Button to set your status to 'Online' Online Away Button to set your status to 'Away' Ausente Busy Button to set your status to 'Busy' Ocupado toxcore failed to start, the application will terminate after you close this message. O toxcore falhou ao iniciar, o aplicativo será encerrado após você fechar esta mensagem. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text O Toxcore falhou ao inicializar suas configurações de proxy. O qTox não pode ser executado, por favor modifique suas configurações e reinicialize o aplicativo. File Arquivo Edit Profile Editar Perfil Change Status Mudar Status Log out Sair Edit Editar Logout Tray action menu to logout user Encerrar sessão Exit Tray action menu to exit tox Sair Filter... Filtrar... Contacts Contatos Add Contact... Adicionar Contato... Next Conversation Próxima Conversa Previous Conversation Conversa Anterior Executable file popup title Arquivo executável You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Você pediu ao qTox para abrir um arquivo executável. Executáveis podem potencialmente danificar seu computador. Tem certeza de que deseja abrir este arquivo? Couldn't request friendship Não foi possível solicitar amizade Status Status Your name Seu nome Message failed to send Falha no envio da mensagem Create new group... Criar novo grupo... Add new circle... Adicionar novo círculo... %n New Friend Request(s) %n Novo pedido de amizade %n Novos pedidos de amizade %n New Group Invite(s) %n Convite de Novo Grupo %n Convites de Novo Grupo By Name Por Nome By Activity Por Atividade All Todos Online Conectados Offline Ausgelassen Desconectados Friends Amigos Groups Grupos Search Contacts Buscar Contatos Groupchat #%1 Bate-papo em grupo #%1 Show Tray action menu to show qTox window Exibir Add friend title of the window Adicionar contato Group invites title of the window Convites a grupos File transfers title of the window Transferências de arquivos Settings title of the window Configurações My profile title of the window Meu perfil Failed to send file "%1" Falha ao enviar o arquivo "%1" File sent Arquivo enviado sent you a friend request. lhe enviou um pedido de amizade. invites you to join a group. convida você para participar de um grupo. qTox/translations/ro.ts000066400000000000000000003340311415623743500155400ustar00rootroot00000000000000 AVForm Audio/Video Audio/Video Default resolution Rezoluție implicită Disabled Dezactivat Select region Selectați regiunea Screen %1 Ecran %1 Audio Settings Setări audio Gain Câștig Playback device Dispozitiv de redare Use slider to set volume of your speakers. Utilizați cursorul pentru a seta volumul difuzoarelor. Capture device Dispozitiv de captură Volume Volum Video Settings Setări video Video device Dispozitiv video Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Stabiliți rezoluția camerei. Valorile mai mari, o calitate video mai bună pe care prietenii dvs. o pot obține. Rețineți că, cu o calitate video mai bună, este necesară o conexiune la internet mai bună. Uneori, conexiunea dvs. poate să nu fie suficient de bună pentru a gestiona calitatea video superioară, ceea ce poate duce la probleme cu apelurile video. Resolution Rezoluție Rescan devices Rescanare dispozitive Test Sound Testați sunetul Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Permite backend-ul audio experimental cu suport de anulare a ecoului, are nevoie de repornirea qTox pentru a avea efect. Enable experimental audio backend Activați backend-ul audio experimental Audio quality Calitate audio Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Calitate audio transmisă. Reduceți această setare dacă lățimea de bandă nu este suficient de mare sau dacă doriți să reduceți utilizarea internetului. High (64 kbps) Ridicat (64 kbps) Medium (32 kbps) Mediu (32 kbps) Low (16 kbps) Slab (16 kbps) Very low (8 kbps) Foarte slab (8 kbps) Threshold Prag AboutForm About Despre Original author: %1 Autorul original: %1 You are using qTox version %1. Utilizați versiunea qTox %1. Commit hash: %1 Implicați hash: %1 toxcore version: %1 versiune toxcore: %1 Qt version: %1 Versiune Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` O listă a tuturor problemelor cunoscute poate fi găsită la adresa %1 din Github. Dacă descoperiți o eroare sau o vulnerabilitate de securitate în cadrul qTox, raportați-o conform ghidului din articolul nostru wiki %2. Click here to report a bug. Faceți clic aici pentru a raporta o eroare. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Vedeți o listă completă de %1 la Github bug-tracker Replaces `%1` in the `A list of all known…` urmărire probleme Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Se scriu rapoarte utile de erori contributors Replaces `%1` in `See a full list of…` contribuitori AboutFriendForm Dialog Dialog username nume de utilizator status message Mesaj de stare Used aliases: Aliasurile folosite: HISTORY OF ALIASES ISTORIA ALIASELOR Automatically accept files from contact if set Acceptă automat fișiere din contact dacă este setat Auto accept files Acceptare automată de fișiere Default directory to save files: Directorul implicit pentru salvarea fișierelor: Auto accept for this contact is disabled Acceptare automată pentru acest contact dezactivat Auto accept call: Auto-acceptare apel: Manual Manual Audio Audio Audio + Video Audio + Video Automatically accept group chat invitations from this contact if set. Acceptați automat invitațiile discuției de grup de la acest contact dacă este setat. Auto accept group invites Acceptați automat invitații de grup Remove history (operation can not be undone!) Eliminați istoricul (operațiunea nu poate fi anulată!) Notes Note Input field for notes about the contact Câmp de introducere pentru note despre contact You can save comment about this contact here. Puteți să salvați comentariul despre acest contact aici. History removed Istorie eliminată Choose an auto accept directory popup title Alegeți un director de acceptare automată <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Aceasta este cheia publică a prietenului tău, folosește-o pentru a-și verifica identitatea prin alt canal. Nu puteți trimite acest lucru altor persoane pentru a putea adăuga acest contact.</p></body></html> Public key (not ToxID): Cheie publică (nu ToxID): Confirmation Confirmare Are you sure to remove %1 chat history? Sigur eliminați %1 Istoricul conversațiilor? Failed to remove chat history with %1! Nu s-a reușit eliminarea istoricului conversațiilor cu %1! AboutSettings Version Versiune License Licență Authors Autori Known Issues Probleme cunoscute Open update download link Deschideți linkul de descărcare a actualizării Update available Actualizare disponibilă qTox is up to date ✓ qTox este actualizat ✓ AddFriendForm Add Friends Adăugați prieteni Invalid Tox ID format Formatul Tox ID este invalid Send friend request Trimiteți cerere de prietenie Add a friend Adăugați un prieten Friend requests Cereri de prietenie Accept Acceptare Reject Respingere Couldn't add friend Nu s-a putut adăuga un prieten Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, fie 76 caractere hexazecimale ori nume@exemplu.com Type in Tox ID of your friend Introduceți Tox ID-ul prietenului dvs Friend request message Mesaj de solicitare prieten Type message to send with the friend request or leave empty to send a default message Introduceți mesajul pe care doriți să-l trimiteți cu solicitarea prietenului sau lăsați gol pentru a trimite un mesaj implicit %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID este nevalid sau nu există You can't add yourself as a friend! When trying to add your own Tox ID as friend Nu te poți adăuga ca prieten! Open contact list Deschideți lista de contacte Couldn't open file Fișierul nu a putut fi deschis Couldn't open the contact file Error message when trying to open a contact list file to import Nu s-a putut deschide fișierul de contact Invalid file Fișier invalid We couldn't find any contacts to import in this file! Nu s-a putut găsi contacte de importat în acest fișier! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description Fie 76 de caractere hexazecimale ori nume@exemplu.com Message The message you send in friend requests Mesaj Open Button to choose a file with a list of contacts to import Deschide Send friend requests Trimiteți cereri de prietenie %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 aici! Tox cu mine poate? Import a list of contacts, one Tox ID per line Importați o listă de contacte, câte un Tox ID pe linie Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Gata de importare %n contact(e), dați clic de trimitere pentru a confirma Gata de importare %n contacte, dați clic de trimitere pentru a confirma Gata de importare %n contacte, dați clic de trimitere pentru a confirma Import contacts Importați contacte AdvancedForm Advanced Avansat Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Numai dacă %1 știți ce faceți, vă rog %2 schimbați ceva aici. Modificările efectuate aici pot duce la probleme cu qTox, și chiar la pierderea datelor dvs., de ex. istorie. really într-adevăr not nu IMPORTANT NOTE NOTĂ IMPORTANTĂ Reset settings Resetați setările All settings will be reset to default. Are you sure? Toate setările vor fi resetate în mod implicit. Sunteți sigur? Yes Da No Nu Call active popup title Apelați activ You can't disconnect while a call is active! popup text Nu vă puteți deconecta în timp ce un apel este activ! Save File Salvează fișierul Logs (*.log) Log-uri (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Salvați setările în directorul de lucru în locul configurării obișnuite Make Tox portable Faceți Tox portabil Reset to default settings Resetați la setările implicite Portable Portabil Connection Settings Setări de conexiune Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Activați IPv6 (recomandat) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Dezactivarea acestui lucru permite, de exemplu, folosirea tox prin Tor. Se adaugă sarcină rețelei Tox cu toate acestea, astfel debifați numai când este necesar. Enable UDP (recommended) Text on checkbox to disable UDP Activați UDP (recomandat) Proxy type: Tip proxy: Address: Text on proxy addr label Adresă: Port: Text on proxy port label Port: None Nici unul SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Reconectați-vă Debug Debug Export Debug Log Exportați Debug Log Copy Debug Log Copiați Debug Log Enable LAN discovery Activare descoperire LAN ChatForm Send a file Trimiteți un fișier qTox wasn't able to open %1 QTox nu a putut să se deschidă %1 Unable to open Imposibil de deschis Bad idea Rea idee %1 calling %1 apelează Calling %1 Apelare %1 Failed to open temporary file Temporary file for screenshot Eșec la deschiderea fișierului temporar qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox nu a reușit să salveze captura de ecran Call with %1 ended. %2 Apelați cu %1 încheiat. %2 Call duration: Durata apelului: %1 is typing %1 scrie Copy Copiere You're trying to send a sequential file, which is not going to work! Încercați să trimiteți un fișier secvențial, care nu va funcționa! %1 is now %2 e.g. "Dubslow is now online" %1 este acum %2 Call with %1 ended unexpectedly. %2 Apelarea cu %1 sa încheiat neașteptat. %2 Filename contained illegal characters Denumirea fișierului conține caractere neconforme Illegal characters have been changed to _ so you can save the file on windows. Caracterele neconforme au fost schimbate în _ astfel încât să puteți salva fișierul pe Windows. ChatFormHeader Can't start audio call Nu se poate iniția apel audio Start audio call Începeți apel audio End audio call Terminați apelul audio Cancel audio call Anulați apelul audio Accept audio call Acceptați apelul audio Can't start video call Nu se poate iniția apelul video Start video call Începeți apel video End video call Terminați apelul video Cancel video call Anulați apelul video Accept video call Acceptați apelul video Sound can be disabled only during a call Sunetul poate fi dezactivat numai în timpul unui apel Unmute call Apel cu sunet Mute call Apel fără sunet Microphone can be muted only during a call Microfonul poate fi dezactivat numai în timpul unui apel Unmute microphone Microfon cu sunet Mute microphone Microfon fără sunet ChatLog Copy Copiere Select all Selectează tot pending în așteptare ChatTextEdit Type your message here... Scrieți mesajul aici... CircleWidget Rename circle Menu for renaming a circle Redenumiți cercul Remove circle Menu for removing a circle Eliminați cercul Open all in new window Deschideți totul în fereastră nouă Core /me offers friendship, "%1" /Mi-a oferit prietenie, "%1" Invalid Tox ID Error while sending friendship request Tox ID-ul este invalid You need to write a message with your request Error while sending friendship request Trebuie să scrieți un mesaj cu cererea dvs Your message is too long! Error while sending friendship request Mesajul dvs. este prea lung! Friend is already added Error while sending friendship request Prietenul a fost deja adăugat Groupchat %1 DesktopNotify New message Mesaj nou Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen Formă 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen rămas:10:10 Filename Ausgelassen Nume fișier Waiting to send... file transfer widget Așteptați trimiterea... Accept to receive this file file transfer widget Acceptați pentru a primi acest fișier Location not writable Title of permissions popup Locația nu poate fi scrisă You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nu aveți permisiunea de a scrie acea locație. Alegeți altceva, sau anulați dialogul de salvare. Resuming... file transfer widget Reluare... Cancel transfer Anulați transferul Pause transfer Pauză la transfer Paused file transfer widget În pauză Open file Deschideți un fișier Open file directory Deschideți un dosar Resume transfer Reluați transferul Accept transfer Acceptați transferul Save a file Title of the file saving dialog Salvează un fișier Remote Paused file transfer widget Întrerupt la distanță FilesForm Transferred Files "Headline" of the window Fișiere transferate Downloads Descărcări Uploads Încărcări FriendListWidget Today Astăzi Yesterday Ieri Last 7 days Ultimele 7 zile This month Luna aceasta Older than 6 Months Mai vechi de 6 luni Never Niciodată FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Cerere de prietenie Someone wants to make friends with you Cineva vrea să se împrietenească cu tine User ID: Nume utilizator: Friend request message: Mesajul cererii de prietenie: Accept Accept a friend request Acceptare Reject Reject a friend request Respingere FriendWidget Invite to group Menu to invite a friend to a groupchat Invitați la grup Move to circle... Menu to move a friend into a different circle Mutați în cerc... To new circle În cercul nou Remove from circle '%1' Eliminați din cerc '%1' Move to circle "%1" Mută în cerc "%1" Open chat in new window Deschideți chat-ul în fereastră nouă Remove chat from this window Eliminați chat-ul din această fereastră To new group În grupul nou Invite to group '%1' Invitați la grup '%1' Set alias... Setați un alias... Auto accept files from this friend context menu entry Acceptare automată de fișiere de la acest prieten Remove friend Menu to remove the friend from our friendlist Șterge prieten Show details Arata detaliile Choose an auto accept directory popup title Alegeți un director de acceptare automată New message Mesaj nou Online Conectat Away Departe Busy Ocupat Offline Ausgelassen Deconectat GeneralForm General General Choose an auto accept directory popup title Alegeți un director de acceptare automată GeneralSettings General Settings Setări generale The translation may not load until qTox restarts. Traducerea nu poate fi încărcată decât după repornirea qTox. Language: Limbă: Show system tray icon Arată pictograma în zona de notificare Enable light tray icon. toolTip for light icon setting Activează pictograma luminoasă în zona de notificare Light icon Pictogramă luminoasă qTox will start minimized in tray. toolTip for Start in tray setting qTox va porni minimizat în zona de notificare. Start in tray Pornire în zona de notificare After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting După apăsarea butonului de închidere (X) qTox se va minimiza în zona de notificare, în loc să se închidă. Close to tray Închidere în zona de notificare After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting După ce apăsați minimizre (_) qTox se va minimiza în zona notificare, în loc de bara de activități a sistemului. Minimize to tray Minimizare în zona de notificare Autostart Pornire automată Set where files will be saved. Setați unde vor fi salvate fișierele. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Puteți seta asta per-prieten în funcție de drept dând clic pe ele. Autoaccept files Auto-acceptare fișiere Set to 0 to disable Setați la 0 pentru a dezactiva Your status is changed to Away after set period of inactivity. Starea dvs. este schimbat în Departe după perioadă de inactivitate. Auto away after (0 to disable): Auto plecat după (0 pentru a dezactiva): Show contacts' status changes Afișare modificări de stare ale contactelor Start qTox on operating system startup (current profile). Porniți qTox la pornirea sistemului de operare (profilul curent). Default directory to save files: Directorul implicit pentru salvarea fișierelor: Check for updates Căutați actualizări Spell checking Verificarea ortografiei Max autoaccept file size (0 to disable): Dimensiunea maximă a fișierului auto-acceptat (0 pentru a dezactiva): MB MB GenericChatForm Send message Trimiteți mesaj Smileys Zâmbete Send file(s) Trimiteți fișierul (fișierele) Send a screenshot Trimiteți o captură de ecran Save chat log Salvați jurnalul de discuții Clear displayed messages Ștergeți mesajele afișate Cleared Curățat Quote selected text Citați textul selectat Copy link address Copiați adresa de legătură Confirmation Confirmare You are sure that you want to clear all displayed messages? Sunteți sigur că doriți să ștergeți toate mesajele afișate? Search in text Căutați în text Go to current date Load chat history... Încărcați istoricul discuțiilor... Export to file Exportați în fișier GenericNetCamView Tox video Tox video Show Messages Afișare mesaje Hide Messages Ascundere mesaje Full Screen Ecran complet Toggle video preview Comutați previzualizarea video Mute audio Dezactivare sunet Mute microphone Dezactivare microfon End video call Terminați apelul video Exit full screen Ieșire ecran complet GroupChatForm %1 has set the title to %2 %1 a fost setat titlul la %2 %1 has joined the group %1 sa alăturat grupului %1 is now known as %2 %1 este acum cunoscut ca %2 %1 has left the group %1 a părăsit grupul %n user(s) in chat Number of users in chat %n utilizator în conversație %n utilizatori în conversație %n utilizatori în conversație mute mut unmute cu sunet GroupInviteForm Groups Grupuri Create new group Creați un grup nou Group invites Grupul invită GroupInviteWidget Invited by %1 on %2 at %3. Invitat de %1 pe %2 la %3. Join Aderă Decline Refuză GroupWidget Set title... Setați titlul... Open chat in new window Deschideți discuția în fereastră nouă Remove chat from this window Eliminați discuția din această fereastră Quit group Menu to quit a groupchat Închideți grup %n user(s) in chat Number of users in chat %n utilizator(i) în conversație %n utilizatori în conversație %n utilizatori în conversație New Message Mesaj nou Online Conectat IdentitySettings Public Information Informații publice Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Această grămadă de caractere îi spune altor clienți Tox cum să vă contacteze. Împărtășește cu prietenii tăi pentru a comunica. Your Tox ID (click to copy) ID-ul dvs. de Tox (dați clic pentru a copia) Profile Profil Rename profile. tooltip for renaming profile button Redenumiți profilul. Go back to the login screen tooltip for logout button Reveniți la ecranul de conectare Logout import profile button Deconectare Remove password Eliminați parola Change password Schimbați parola This QR code contains your Tox ID. You may share this with your friends as well. Acest cod QR conține ID-ul dvs. de Tox. Puteți împărtăși acest lucru și prietenilor dvs. Save image Salvați imaginea Copy image Copiați imaginea Rename rename profile button Redenumiți Delete profile. delete profile button tooltip Ștergeți profilul. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Vă permite să exportați profilul Tox într-un fișier. Profilul nu conține istoricul dvs. Export export profile button Exportare Delete delete profile button Ștergeți Server Server Hide my name from the public list Ascunde numele meu din lista publică Register Înregistrare Your password Parola dvs Update Actualizare Register on ToxMe Înregistrare pe ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Nume pentru serviciul ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Opțional. Ceva despre tine. Sau pisica ta. Optional. Something about you. Or your cat. Tooltip for the Biography field. Opțional. Ceva despre tine. Sau pisica ta. ToxMe service to register on. Serviciul ToxMe pentru a vă înregistra pe. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Dacă nu este setat, înregistrările ToxMe sunt vizibile public. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Eliminați parola și criptarea din profilul dvs. Name input Introduceți numele Name visible to contacts Nume vizibil pentru contacte Status message input Introduceți mesaj de stare Status message visible to contacts Mesaj de stare vizibil pentru contacte Your Tox ID Tox ID-ul dvs Save QR image as file Salvați imaginea QR ca fișier Copy QR image to clipboard Copiați imaginea QR în clipboard ToxMe username to be shown on ToxMe Numele de utilizator ToxMe care va fi afișat pe ToxMe Optional ToxMe biography to be shown on ToxMe Biografia opțională ToxMe va fi afișată pe ToxMe ToxMe service address Adresa de serviciu ToxMe Visibility on the ToxMe service Vizibilitate pe serviciul ToxMe Password Parolă Update ToxMe entry Actualizați intrarea ToxMe Rename profile. Redenumiți profilul. Delete profile. Ștergeți profilul. Export profile Exportați profilul Remove password from profile Eliminați parola din profil Change profile password Schimbați parola profilului My name: Numele meu: My status: Starea mea: My username Numele meu de utilizator My biography Biografia mea My profile Profilul meu LoadHistoryDialog Load History Dialog Încărcați istoric dialog Load history from to (about 100 messages are loaded) Select Date Dialog Selectare dialog dată Select a date Selectați o dată LoginScreen Username: Nume de utilizator: Password: Parola: Confirm: Confirmare: Password strength: %p% Puterea parolei: %p% Create Profile Creează un profil If the profile does not have a password, qTox can skip the login screen Dacă profilul nu are o parolă, qTox poate sări peste ecranul de conectare Load automatically Încărcare automată Load Încărcați Load Profile Încărcați profil New Profile Profil nou Couldn't create a new profile Nu s-a putut crea un profil nou The username must not be empty. Numele de utilizator nu trebuie să fie gol. The password must be at least 6 characters long. Parola trebuie să aibă cel puțin 6 caractere. The passwords you've entered are different. Please make sure to enter same password twice. Parolele pe care le-ați introdus sunt diferite. Asigurați-vă că introduceți aceeași parolă de două ori. A profile with this name already exists. Un profil cu acest nume există deja. Password protected profiles can't be automatically loaded. Profilurile protejate prin parolă nu pot fi încărcate automat. Couldn't load profile Nu s-a putut încărca profilul There is no selected profile. You may want to create one. Nu există niciun profil selectat. Poate doriți să creați unul. Couldn't load this profile Nu s-a putut încărca acest profil This profile is already in use. Acest profil este deja în uz. Wrong password. Parolă greșită. Import Importați Username input field Câmpul de introducere al numelui de utilizator Password input field, you can leave it empty (no password), or type at least 6 characters Câmpul de introducere a parolei, îl puteți lăsa gol (fără parolă) sau tastați cel puțin 6 caractere Password confirmation field Câmp de confirmare a parolei Create a new profile button Creați un nou buton de profil Profile list Lista de profil List of profiles Lista profilurilor Password input Introducerea parolei Load automatically checkbox Încărcați automat caseta de selectare Import profile Importați profil Load selected profile button Încărcați butonul profil selectat New profile creation page Noua pagină de creare a profilului Loading existing profile page Încărcarea paginii profilului existent MainWindow Your name Numele dvs Your status Statusul dvs ... Ausgelassen ... Add friends Adăugați prieteni Create a group chat Creați un grup de discuții View completed file transfers Vizualizați transferurile complete de fișiere Change your settings Modificați setările Close Închideți Open profile Deschideți profil Open profile page when clicked Deschideți pagina de profil când faceți clic Status message input Introducere mesaj de stare Set your status message that will be shown to others Setați mesajul de stare care va fi afișat altora Status Stare Set availability status Setați starea disponibilității Contact search Căutare contact Contact search input for known friends Căutare contact pentru prietenii cunoscuți Sorting and visibility Sortare și vizibilitate Set friends sorting and visibility Setați sortarea și vizibilitatea prietenilor Open Add friends page Deschideți pagina de prieteni adăugați Groupchat Grup de discuții Open groupchat management page Deschideți pagina de gestionare a discuțiilor de grup File transfers history Fișier de istorie transferuri Open File transfers history Deschideți fișier de istorie transferuri Settings Setări Open Settings Deschideți setările Nexus View OS X Menu bar Vizualizare Window OS X Menu bar Fereastră Minimize OS X Menu bar Minimizare Bring All to Front OS X Menu bar Aduceți tot în față Exit Fullscreen Ieșiți din ecranul complet Enter Fullscreen Intrați în ecranul complet NotificationEdgeWidget Unread message(s) Mesaj necitit Mesaje necitite Mesaje necitite PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK ACTIVAT PrivacyForm Privacy Confidențialitate Confirmation Confirmare Do you want to permanently delete all chat history? Doriți să ștergeți definitiv tot istoricul de discuții? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Prietenii dvs. vor putea vedea când scrieți. Send typing notifications Trimiteți notificările de tastare Keep chat history Păstrați istoricul discuțiilor NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam face parte din Tox ID-ul dvs. Dacă sunteți spamat cu solicitări de prietenie, ar trebui să vă schimbați NoSpam-ul. Oamenii nu vă vor putea adăuga ID-ul vechi, dar vă veți păstra prietenii actuali. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam este o parte a identității dvs. care poate fi schimbată la alegere. Dacă primiți spamuri cu solicitări de prietenie, modificați NoSpam. Generate random NoSpam Generați NoSpam aleatoriu Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Păstrarea de istoricul discuțiilor este încă în dezvoltare. Salvarea modificărilor formatelor este posibilă, ceea ce poate duce la pierderea datelor. Privacy Confidențialitate BlackList Listă neagră Filter group message by group member's public key. Put public key here, one per line. Filtrați mesajul grupului după cheia publică a membrilor grupului. Puneți cheia publică aici, una pe linie. Profile Failed to derive key from password, the profile won't use the new password. Nu s-a putut obține o cheie din parolă, profilul nu va utiliza noua parolă. Couldn't change password on the database, it might be corrupted or use the old password. Nu s-a putut modifica parola în baza de date, s-ar putea să fie deteriorată sau să se folosească parola veche. Toxing on qTox Folosiți Tox în qTox ProfileForm Choose a profile picture Alegeți o imagine de profil Error Eroare Rename "%1" renaming a profile Redenumiți "%1" Unable to open this file. Nu se poate deschide acest fișier. Current profile: Profil curent: Remove Eliminați Unable to read this image. Imposibil de citit această imagine. The supplied image is too large. Please use another image. Imaginea furnizată este prea mare. Utilizați o altă imagine. Couldn't rename the profile to "%1" Nu s-a putut redenumi profilul "%1" Location not writable Title of permissions popup Locația nu poate fi scrisă You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nu aveți permisiunea de a scrie acea locație. Alegeți altceva, sau anulați dialogul de salvare. Failed to copy file Copierea fișierului a eșuat The file you chose could not be written to. Dosarul pe care l-ați ales nu a putut fi scris. Really delete profile? deletion confirmation title Ștergeți într-adevăr profilul? Nothing to remove Nimic de eliminat Your profile does not have a password! Profilul dvs. nu are o parolă! Really delete password? deletion confirmation title Sigur ștergeți parola? Please enter a new password. Introduceți o nouă parolă. Are you sure you want to delete this profile? deletion confirmation text Sigur doriți să ștergeți acest profil? Save save qr image Salvați Save QrCode (*.png) save dialog filter Salvați QrCode (*.png) Files could not be deleted! deletion failed title Fișierele nu au putut fi șterse! Register (processing) Înregistrare (în curs) Update (processing) Actualizare (în curs) Done! Terminat! Account %1@%2 updated successfully Cont %1@%2 actualizat cu succes Successfully added %1@%2 to the database. Save your password Adăugat cu succes %1@%2 la baza de date. Salvați parola Toxme error Eroare toxme Register Înregistrare Update Actualizați Change password button text Schimbați parola Set profile password button text Setați parola de profil Current profile location: %1 Locația profilului curent: %1 Couldn't change password Nu s-a putut modifica parola This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Această grămadă de caractere spune altor clienți Tox cum să vă contacteze. Împărtășiți prietenilor pentru a comunica. Acest ID include codul NoSpam (în albastru) și suma de control (în gri). Empty path is unavaliable Calea goală nu este disponibilă Failed to rename Redenumire eșuată Profile already exists Profilul există deja A profile named "%1" already exists. Un profil numit "%1" există deja. Empty name Nume gol Empty name is unavaliable Numele gol este indisponibil Empty path Cale goală Couldn't change password on the database, it might be corrupted or use the old password. Nu s-a putut modifica parola în baza de date, s-ar putea să fie deteriorată sau să se folosească parola veche. Export profile Exportați profil Tox save file (*.tox) save dialog filter Fișier salvat Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Următoarele fișiere nu au putut fi șterse: Please manually remove them. deletion failed text part 2 Vă rugăm să le eliminați manual. Are you sure you want to delete your password? deletion confirmation text Sigur doriți să vă ștergeți parola? Images (%1) filetype filter Imagini (%1) ProfileImporter Import profile import dialog title Importați profil Tox save file (*.tox) import dialog filter Fișier salvat Tox (*.tox) Ignoring non-Tox file popup title Ignorare fișier non-Tox Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Avertisment: ați ales un fișier care nu este un fișier de salvare Tox; ignorare. Profile already exists import confirm title Profilul există deja A profile named "%1" already exists. Do you want to erase it? import confirm text Un profil numit "%1" deja există. Vreți să ștergeți? File doesn't exist Fișierul nu există Profile doesn't exist Profilul nu există Profile imported Profil importat %1.tox was successfully imported %1.tox a fost importat cu succes QApplication Ok Bine Cancel Anulare Yes Da No Nu LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Nu s-a putut adăuga un prieten %1 is not a valid Toxme address. %1 nu este validă adresa Toxme. You can't add yourself as a friend! When trying to add your own Tox ID as friend Nu te poți adăuga ca prieten! QObject Tox URI to parse Tox URI pentru analiză Starts new instance and loads specified profile. Porniți o instanță nouă și încărcați profilul specificat. profile profil Default Implicit Blue Albastru Olive Măsliniu Red Roșu Violet Violet Incoming call... Sosire apel... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 aici! Tox-ează-mă poate? None No camera device set Nimic Desktop Desktop as a camera input for screen sharing Birou Server doesn't support Toxme Serverul nu acceptă Toxme You're making too many requests. Wait an hour and try again Faceți prea multe cereri. Așteptați o oră și încercați din nou This name is already in use Acest nume este deja folosit This Tox ID is already registered under another name Acest ID Tox este deja înregistrat sub alt nume Please don't use a space in your name Nu utilizați un spațiu în numele dvs Password incorrect Parola incorecta You can't use this name Nu puteți folosi acest nume Name not found Numele nu a fost găsit Tox ID not sent Tox ID netrimis That user does not exist Acest utilizator nu există Error Eroare qTox couldn't open your chat logs, they will be disabled. qTox nu au putut deschide jurnalele de discuții, vor fi dezactivate. Problem with HTTPS connection Problemă cu conexiunea HTTPS Internal ToxMe error Eroare internă ToxMe Reformatting text in progress.. Reformarea textului în desfășurare.. Starts new instance and opens the login screen. Pornește o nouă instanță și deschide ecranul de conectare. Dark Întunecat Dark blue Albastru închis Dark olive Măsliniu întunecat Dark red Roșu închis Dark violet Violet închis Failed to load profile automatically. online contact status conectat away contact status departe busy contact status ocupat offline contact status deconectat blocked contact status blocat RemoveFriendDialog Remove friend Ștergeți prieten Also remove chat history De asemenea, eliminați istoricul discuțiilor Remove Eliminați Are you sure you want to remove %1 from your contacts list? Sunteți sigur că vreți să eliminați %1 Din lista persoanelor de contact? Remove all chat history with the friend if set Eliminați tot istoricul discuțiilor cu prietenul dacă este setat ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Faceți clic și trageți pentru a selecta o regiune. Apăsați %1 Pentru ascundere/afișare fereastra qTox , sau %2 anulare. Space [Space] key on the keyboard Spațiu Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Apăsați %1 pentru a trimite o captură de ecran a selecției, %2 Pentru ascundere/afișare fereastră qTox, sau %3 anulare. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Textul nu a putut fi găsit. Start Începe SearchSettingsForm Form Formă Start search: Începe căutare: from the end de la sfârșit from the beginning de la început after date după dată before date înainte de data 00.00.0000 00.00.0000 Case sensitive Caz sensibil Whole words only Numai cuvinte întregi Use regular expressions Utilizare expresii regulate SetPasswordDialog Set your password Setați-vă parola Confirm: Confirmare: Password: Parolă: Password strength: %p% Puterea parolei: %p% The password is too short Parola este prea scurtă The password doesn't match. Parola nu se potrivește. Confirm password Confirmare parolă Confirm password input Confirmare introducere parolă Password input Introducere parolă Password input field, minimum 6 characters long Câmp de introducere a parolei, cu lungimea minimă de 6 caractere Settings Circle #%1 Cerc #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Adăugați un prieten Do you want to add %1 as a friend? Doriți să adăugați %1 ca un prieten? User ID: Nume utilizator: Friend request message: Mesajul solicitării unui prieten: Send Send a friend request Trimiteți Cancel Don't send a friend request Anulați UserInterfaceForm None Nimic User Interface Interfață utilizator UserInterfaceSettings Chat Discuții Base font: Font de bază: px px Size: Mărime: New text styling preference may not load until qTox restarts. Preferința noului stil de text nu se poate încărca până la repornirea qTox. Text Style format: Formatul stilului de text: Select text styling preference. Selectați preferința stilului de text. Plaintext Text simplu Show formatting characters Afișează formatarea caracterelor Don't show formatting characters Nu afișa formatarea caracterelor New message Mesaj nou Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Se deschide fereastra qTox când primiți un mesaj nou și nu este deschisă o fereastră încă. Open window Deschidere fereastră Contact list Listă de contacte If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Dacă este bifat, discuțiile de grup vor fi plasate în partea de sus a listei de prieteni, altfel vor fi plasate sub prietenii online. Place groupchats at top of friend list Puneți grupurile de discuții deasupra listei de prieteni Your contact list will be shown in compact mode. toolTip for compact layout setting Lista dvs. de contacte va fi afișată în modul compact. Compact contact list Listă de contacte compactă Multiple windows mode Mod de ferestre multiple Open each chat in an individual window Deschideți fiecare discuție într-o fereastră individuală Emoticons Emoticoane Use emoticons Utilizați emoticoane Smiley Pack: Text on smiley pack label Pachet zâmbete: Emoticon size: Dimensiune emoticon: px px Theme Temă Style: Stil: Theme color: Culoarea temei: Timestamp format: Format Marcaj de timp: Date format: Formatul datei: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Dacă este activată orice persoană de contact fără un avatar setat se va genera un avatar pe baza Tox ID-ului în locul unei imagini implicite. Necesită repornire pentru a aplica. Use identicons instead of empty avatars Utilizați icon de identificare în loc de avatare goale Use colored nicknames in chats Utilizați porecle colorate în conversații Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Afișați o notificare atunci când primiți un mesaj nou și fereastra nu este selectată. Notify Notificare Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Notificați numai despre mesajele noi în conversațiile de grup atunci când sunt menționate. Group chats only notify when mentioned Conversațiile de grup se notifică numai atunci când sunt menționate Play sound Redă sunet Play sound while Busy Redă sunet când sunteți ocupat Notify via desktop notifications Notificare prin notificări pe desktop Hide message sender and contents Widget Online Button to set your status to 'Online' Conectat Away Button to set your status to 'Away' Plecat Busy Button to set your status to 'Busy' Ocupat toxcore failed to start, the application will terminate after you close this message. toxcore nu a reușit să pornească, aplicația se va închide după închiderea acestui mesaj. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore nu a reușit să pornească cu setările proxy. qTox nu poate rula; Modificați setările și reporniți. File Fişier Edit Profile Editați profilul Change Status Schimbați starea Log out Deconectați-vă Edit Editați Logout Tray action menu to logout user Deconectați-vă Exit Tray action menu to exit tox Ieșire Filter... Filtru... Contacts Contacte Add Contact... Adaugați contact... Next Conversation Următoarea conversație Previous Conversation Conversație anterioară Executable file popup title Fișier executabil You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Sunteți întrebat de qTox pentru deschiderea unui fișier executabil. Fișierele executabile pot deteriora computerul. Sigur doriți să deschideți acest fișier? Couldn't request friendship Nu se poate cere prietenie Status Stare Your name Numele dvs Message failed to send Mesajul nu a putut fi trimis Create new group... Creați un grup nou... Add new circle... Adăugați un cerc nou... %n New Friend Request(s) %n Cerere nouă de prietenie %n Cereri noi de prietenie %n Cereri noi de prietenie %n New Group Invite(s) %n Invitație nouă în grup %n Invitații noi în grup %n Invitații noi în grup By Name După nume By Activity După activitate All Tot Online Conectat Offline Ausgelassen Deconectat Friends Prieteni Groups Grupuri Search Contacts Căutare contacte Groupchat #%1 Grup discuții #%1 Show Tray action menu to show qTox window Afișare Add friend title of the window Adăugați prieten Group invites title of the window Grupul invită File transfers title of the window Transferuri de fișiere Settings title of the window Setări My profile title of the window Profilul meu Failed to send file "%1" Eșec la trimiterea fișierului "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/ru.ts000066400000000000000000003712731415623743500155570ustar00rootroot00000000000000 AVForm Default resolution Разрешение по умолчанию Audio/Video Аудио/Видео Disabled Отсутствует Select region Выбрать область экрана Screen %1 Экран %1 Audio Settings Настройки звука Gain Усиление Playback device Устройство воспроизведения Use slider to set volume of your speakers. Используйте ползунок для установки громкости динамиков. Capture device Устройство записи Volume Громкость Video Settings Настройки видео Video device Видео устройство Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Установите разрешение своей камеры. Чем больше значение, тем выше качество видео, которое увидят ваши друзья. Заметьте, что чем выше качество видео, тем лучшее подключение к интернету потребуется. Иногда подключение может быть недостаточно хорошим, что бы передать видео высокого качества, что может привести к проблемам при видео звонке. Resolution Разрешение Rescan devices Повторить поиск устройств Test Sound Проверка звука Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Включает экспериментальную звуковую систему с поддержкой эхоподавления, требует перезагрузки qTox. Enable experimental audio backend Экспериментальная звуковая система Audio quality Качество звука Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Качество передаваемого звука. Уменьшите значение, если ваше соединение не достаточно быстрое или если хотите сократить расходы трафика. High (64 kbps) Высокий (64 Кбит/с) Medium (32 kbps) Средний (32 КБит/с) Low (16 kbps) Низкий (16 КБит/с) Very low (8 kbps) Очень низкий (8 КБит/с) Threshold Порог AboutForm About О программе Original author: %1 Исходный автор: %1 You are using qTox version %1. Вы используете qTox версии %1. Commit hash: %1 Хэш коммита: %1 toxcore version: %1 Версия toxcore: %1 Qt version: %1 Версия Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Список всех известных проблем вы можете найти на нашем %1 на Github. Если вы обнаружите ошибку или уязвимость в безопасности qTox, пожалуйста сообщите о них согласно указаниям в нашей статье %2 вики. Click here to report a bug. Нажмите здесь, чтобы сообщить об ошибке. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Полный список %1 доступен на Github bug-tracker Replaces `%1` in the `A list of all known…` баг-трекерe Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Написание полезных отчетов об ошибках contributors Replaces `%1` in `See a full list of…` разработчиков AboutFriendForm Dialog Диалог username имя пользователя status message статус Used aliases: Используемый псевдоним: HISTORY OF ALIASES ИСТОРИЯ ПСЕВДОНИМОВ Automatically accept files from contact if set Автоматически принимать файлы от контактов если установленo Auto accept files Автоматически принимать файлы Default directory to save files: Стандартная папка сохранения файлов: Auto accept for this contact is disabled Автоматический прием файлов от этого контакта отключен Auto accept call: Автоматический прием звонка: Manual Ручной Audio Аудио Audio + Video Аудио + Видео Automatically accept group chat invitations from this contact if set. Автоматически принимать приглашения в групповой чат от этого контакта. Auto accept group invites Автоматически принимать приглашения в группы Remove history (operation can not be undone!) Удалить историю переписки (операцию нельзя отменить) Notes Заметки Input field for notes about the contact Поле ввода для заметок о контакте You can save comment about this contact here. Здесь вы можете сохранить заметки об этом контакте. History removed История переписки удалена Choose an auto accept directory popup title Выбрать папку для автоматического приема <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Это открытый ключ вашего друга, используйте его, чтобы подтвердить свою личность через другой канал. Вы не можете отправить его другим людям для добавления этого контакта.</p></body></html> Public key (not ToxID): Открытый ключ (не ToxID): Confirmation Подтверждение Are you sure to remove %1 chat history? Вы уверены, что хотите удалить %1 историю чата? Failed to remove chat history with %1! Не удалось удалить историю чата с %1! AboutSettings Version Версия License Лицензия Authors Авторы Known Issues Известные проблемы Open update download link Открыть ссылку для скачивания обновления Update available Доступно обновление qTox is up to date ✓ qTox обновлен ✓ AddFriendForm Add Friends Добавить друзей Send friend request Мне не нравится, но другого не придумал, и фейсбук использует это Отправить запрос на добавление в друзья Add a friend Добавить друга Friend requests Запросы на добавление в друзья Accept Принять Reject Отклонить Couldn't add friend Невозможно добавить друга Invalid Tox ID format Неправильный формат Tox ID Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, или 76 шестнадцатиричных символов или name@example.com Type in Tox ID of your friend Введите Tox ID вашего друга Friend request message Запрос в друзья Type message to send with the friend request or leave empty to send a default message Введите сообщение, чтобы отправить с запросом на добавление в друзья или оставьте поле пустым, чтобы отправить сообщение по-умолчанию %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID является недопустимым или не существует You can't add yourself as a friend! When trying to add your own Tox ID as friend Вы не можете добавить себя в друзья! Open contact list Открыть список контактов Couldn't open file Не удается открыть файл Couldn't open the contact file Error message when trying to open a contact list file to import Не удается открыть файл списка контактов Invalid file Недопустимый файл We couldn't find any contacts to import in this file! Не удается найти каких-либо контактов для импорта в этом файле! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description 76-ти значный шестнадцатеричный ключ или name@example.com Message The message you send in friend requests Сообщение Open Button to choose a file with a list of contacts to import Открыть Send friend requests Отправить запрос на добавление %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Привет, это %1! Добавите меня в друзья? Import a list of contacts, one Tox ID per line Импортировать список контактов, один Tox ID в строке Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Можно импортировать %n контакт, нажмите "Отправить" для подтверждения Можно импортировать %n контактов, нажмите "Отправить" для подтверждения Можно импортировать %n контакта, нажмите "Отправить" для подтверждения Import contacts Импорт списка контактов AdvancedForm Advanced Расширенные Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Если вы %1 в том, что вы делаете, пожалуйста ничего %2 меняйте. Изменения могут привести к проблемам в работе qTox и даже потере данных (например, истории сообщений). really не уверены not не IMPORTANT NOTE ОБРАТИТЕ ВНИМАНИЕ Reset settings Сброс настроек All settings will be reset to default. Are you sure? Все настройки будут сброшены в исходное состояние. Вы уверены? Yes Да No Нет Call active popup title Идет звонок You can't disconnect while a call is active! popup text Вы не можете отключиться во время звонка! Save File Сохранить файл Logs (*.log) журнал (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Сохранять настройки в рабочую директорию вместо стандартной папки настроек Make Tox portable Портативный режим Reset to default settings Вернуть стандартные настройки Portable Портативность Connection Settings Настройки соединения Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Включить IPv6 (реком.) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Отключение позволяет, например, использовать Tox поверх Tor. Однако это добавляет нагрузку на сеть Tox, так что отключайте только в случае необходимости. Enable UDP (recommended) Text on checkbox to disable UDP Включить UDP (реком.) Proxy type: Прокси: Address: Text on proxy addr label Адрес: Port: Text on proxy port label Порт: None Отсутствует SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Переподключиться Debug Отладка Export Debug Log Экспорт журнала отладки Copy Debug Log Копировать журнал отладки Enable LAN discovery Включить обнаружение локальной сети (LAN) ChatForm Send a file Отправить файл qTox wasn't able to open %1 Паравозик не смог. Не сможешь и ты! qTox не смог открыть %1 Unable to open Невозможно открыть Bad idea Плохая идея %1 calling Входящий звонок от %1 Calling %1 Вызов %1 Failed to open temporary file Temporary file for screenshot Не удалось открыть временный файл qTox wasn't able to save the screenshot qTox не смог сохранить снимок экрана Call with %1 ended. %2 Разговор с %1 завершился. %2 Call duration: Длительность разговора: %1 is typing %1 набирает сообщение Copy Копировать You're trying to send a sequential file, which is not going to work! Вы пытаетесь отправить последовательный файл, что не сработает! %1 is now %2 e.g. "Dubslow is now online" %1 сейчас %2 Call with %1 ended unexpectedly. %2 Разговор с %1 неожиданно прервался. %2 Filename contained illegal characters Имя файла содержит недопустимые символы Illegal characters have been changed to _ so you can save the file on windows. Некорректные символы были изменены на _ так что вы можете сохранить файл в Windows. ChatFormHeader Can't start audio call Невозможно начать аудиозвонок Start audio call Начать голосовой звонок End audio call Завершить звонок Cancel audio call Прервать звонок Accept audio call Принять аудиозвонок Can't start video call Невозможно начать видеозвонок Start video call Начать видеозвонок End video call Завершить видеозвонок Cancel video call Отменить видеозвонок Accept video call Принять видеозвонок Sound can be disabled only during a call Звук может быть выключен только во время звонка Unmute call Включить звук Mute call Выключить звук Microphone can be muted only during a call Микрофон может быть выключен только во время звонка Unmute microphone Включить микрофон Mute microphone Выключить микрофон ChatLog Copy Копировать Select all Выбрать все pending в ожидании ChatTextEdit Type your message here... Введите ваше сообщение здесь... CircleWidget Rename circle Menu for renaming a circle Переименовать список Remove circle Menu for removing a circle Удалить список Open all in new window Открыть весь список в новом окне Core /me offers friendship, "%1" /me предлагает дружбу, "%1" Invalid Tox ID Error while sending friendship request Некорректный Tox ID You need to write a message with your request Error while sending friendship request Вам нужно написать сообщение с текстом запроса Your message is too long! Error while sending friendship request Ваше сообщение слишком длинное! Friend is already added Error while sending friendship request Друг уже добавлен Groupchat %1 Групповой чат %1 DesktopNotify New message Новое сообщение Incoming file transfer Передача входящего файла Friend request received Получен запрос на дружбу New group message Новое групповое сообщение Group invite received Получено приглашение в группу FileTransferWidget Form От 10Mb 10Mb 0kb/s 0 КБ/c ETA:10:10 Осталось:10:10 Filename Имя файла Waiting to send... file transfer widget Ожидание отправки... Accept to receive this file file transfer widget Разрешить получение этого файла Location not writable Title of permissions popup Невозможно записать в указанный путь You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup У вас нет прав для записи в эту папку. Выберите другую или выйдите из диалога сохранения. Resuming... file transfer widget Возобновление... Cancel transfer Отменить передачу Pause transfer Приостановить передачу Paused file transfer widget Пауза Open file Открыть файл Open file directory Открыть папку с файлом Resume transfer Возобновить передачу Accept transfer Разрешить передачу Save a file Title of the file saving dialog Сохранить файл Remote Paused file transfer widget Удаленная пауза FilesForm Transferred Files "Headline" of the window Переданные файлы Downloads Принятые Uploads Отправленные FriendListWidget Today Сегодня Yesterday Вчера Last 7 days За последние 7 дней This month За этот месяц Older than 6 Months За 6 месяцев Never Никогда FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Мне не нравится, но другого не придумал, и фейсбук использует это Запрос на добавление в друзья Someone wants to make friends with you Кто-то хочет добавить вас в друзья User ID: ID пользователя: Friend request message: Текст запроса: Accept Accept a friend request Принять Reject Reject a friend request Отклонить FriendWidget Auto accept files from this friend context menu entry Автоматически принимать файлы от этого друга Invite to group Menu to invite a friend to a groupchat Пригласить в группу Open chat in new window Перенести разговор в новое окно Remove chat from this window Исключить разговор из этого окна To new group В новую группу Invite to group '%1' Пригласить в группу '%1' Move to circle... Menu to move a friend into a different circle Поместить в список... To new circle В новый список Remove from circle '%1' Переместить из списка '%1' Move to circle "%1" Поместить в список "%1" Set alias... Может "отображаемое имя"?; Можно даже 'элиас' :) Установить псевдоним... Remove friend Menu to remove the friend from our friendlist Удалить друга Show details О контакте Choose an auto accept directory popup title Выбрать папку для автоматического приема New message Новое сообщение Online В сети Away Отошел Busy Занят Offline Не в сети GeneralForm Choose an auto accept directory popup title Выбрать папку для автоматического приема General Общие GeneralSettings General Settings Общие настройки The translation may not load until qTox restarts. Перевод не изменится до перезапуска qTox. Show system tray icon Показывать иконку в трее Close to tray Сворачивать в трей при закрытии Minimize to tray Сворачивать в трей Light icon Светлая иконка Default directory to save files: Стандартная папка сохранения файлов: Set to 0 to disable Укажите 0, чтобы отключить You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Можно настроить отдельно для каждого друга (щелкнув правой кнопкой мыши по другу и выбрав соответствующий пункт меню). Language: Язык: Enable light tray icon. toolTip for light icon setting Включить светлую иконку в трее. qTox will start minimized in tray. toolTip for Start in tray setting qTox будет запускаться свернутым в трей. After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting После закрытия окна (Х) qTox свернется в трей, вместо закрытия. After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting После сворачивания окна (_) qTox свернется в трей, а не в панель задач. Autostart Автозапуск Set where files will be saved. Укажите, где файлы будут сохраняться. Your status is changed to Away after set period of inactivity. Ваш статус изменится на «Отошел» после заданного периода бездействия. Auto away after (0 to disable): Статус «Отошел» после (0 - выключено): Start qTox on operating system startup (current profile). Запускать qTox при загрузке операционной системы (текущий профиль). Autoaccept files Принимать файлы автоматически Start in tray Запускать свернутым в трей Show contacts' status changes Показывать изменения статусов контактов Check for updates Проверить наличие обновлений Spell checking Проверка орфографии Max autoaccept file size (0 to disable): Максимальный размер файла автоприема (0 для отключения): MB МБ GenericChatForm Send message Отправить сообщение Smileys Смайлики Send file(s) Отправить файл(ы) Send a screenshot Отправить снимок экрана Save chat log Сохранить журнал чата Clear displayed messages Очистить показываемые сообщения Quote selected text Цитировать выделенное Cleared Очищено Copy link address Копировать адрес ссылки Confirmation Подтверждение You are sure that you want to clear all displayed messages? Вы уверены, что хотите удалить все отображаемые сообщения? Search in text Поиск в тексте Go to current date Перейти к текущей дате Load chat history... Загрузить историю чата... Export to file Экспорт в файл GenericNetCamView Tox video Видео Show Messages Показать сообщения Hide Messages Скрыть сообщения Full Screen На весь экран Toggle video preview Переключить предварительный просмотр видео Mute audio Выключить звук Mute microphone Выключить микрофон End video call Завершить видеозвонок Exit full screen Выход из полноэкранного режима GroupChatForm %1 has set the title to %2 %1 сменил заголовок на %2 %1 has joined the group %1 присоединился к группе %1 is now known as %2 %1 в настоящее время известен как %2 %1 has left the group %1 покинул группу %n user(s) in chat Number of users in chat %n пользователь в чате %n пользователя в чате %n пользователей в чате mute заглушить unmute включить звук GroupInviteForm Groups Группы Create new group Создать новую группу Group invites Групповые приглашения GroupInviteWidget Invited by %1 on %2 at %3. Приглашен %1 в %2 на %3. Join Присоединиться Decline Отказаться GroupWidget Open chat in new window Перенести разговор в новое окно Remove chat from this window Исключить разговор из этого окна Quit group Menu to quit a groupchat Покинуть группу Set title... Установить заголовок... %n user(s) in chat Number of users in chat %n пользователь в чате %n пользователя в чате %n пользователей в чате New Message Новое сообщение Online В сети IdentitySettings Public Information Публичные данные Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Этот набор символов говорит другим пользователям Tox как связаться с вами. Отправте его своим друзьям для связи. Your Tox ID (click to copy) Ваш Tox ID (нажмите на него, чтобы скопировать) This QR code contains your Tox ID. You may share this with your friends as well. Данный QR-код содержит ваш Tox ID. Им можно поделиться с друзьями. Save image Сохранить изображение Copy image Копировать изображение Profile Профиль Rename profile. tooltip for renaming profile button Переименовать профиль. Delete profile. delete profile button tooltip Удалить профиль. Go back to the login screen tooltip for logout button Вернуться к окну входа Logout import profile button Выйти Remove password Удалить пароль Change password Сменить пароль Rename rename profile button Переименовать Export export profile button Экспортировать Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Экспорт Вашего Tox-профиля в файл. Данный файл-профиля не содержит историю переписки. Delete delete profile button Удалить Server Сервер Hide my name from the public list Не отображать меня в публичном списке Register Регистрация Your password Ваш пароль Update Обновление Register on ToxMe Зарегистрироваться на ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Имя для сервиса ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Необязательно. Что-нибудь о вас. Или о вашей кошке :) Optional. Something about you. Or your cat. Tooltip for the Biography field. Необязательно. Что-нибудь о вас. Или о вашей кошке :) ToxMe service to register on. Сервис ToxMe для регистрации. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Если не установлен, записи ToxMe видны всем. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Удалить ваш пароль и шифрование с вашего профиля. Name input Ввод имени Name visible to contacts Имя видимое для контактов Status message input Ввод сообщения статуса Status message visible to contacts Сообщение статуса видимоее для контактов Your Tox ID Ваш Tox ID Save QR image as file Сохранить QR-изображение как файл Copy QR image to clipboard Скопировать QR-изображение в буфер обмена ToxMe username to be shown on ToxMe Имя пользователя ToxMe для отображения на ToxMe Optional ToxMe biography to be shown on ToxMe Необязательная ToxMe биография для отображения на ToxMe ToxMe service address Адрес сервиса ToxMe Visibility on the ToxMe service Видимость на сервисе ToxMe Password Пароль Update ToxMe entry Обновить запись ToxMe Rename profile. Переименовать профиль. Delete profile. Удалить профиль. Export profile Экспортировать ьрофиль Remove password from profile Удалить пароль из профиля Change profile password Изменить пароль профиля My name: Мое имя: My status: Мой статус: My username Мое имя пользователя My biography Моя биография My profile Мой профиль LoadHistoryDialog Load History Dialog Диалог загрузки истории Load history Загрузить историю from от to кому (about 100 messages are loaded) (загружено около 100 сообщений) Select Date Dialog Диалог выбора даты Select a date Выберите дату LoginScreen Username: Имя пользователя: Password: Пароль: Confirm: Еще раз: Password strength: %p% Сложность пароля: %p% Create Profile Создать профиль If the profile does not have a password, qTox can skip the login screen Если профиль не защищен паролем, qTox может пропустить диалог выбора профиля Load automatically Загрузить автоматически Load Загрузить Load Profile Загрузить профиль New Profile Новый профиль Couldn't create a new profile Невозможно создать новый профиль The username must not be empty. Поле имени пользователя не должно быть пустым. The password must be at least 6 characters long. Пароль должен быть длиной не менее 6 символов. The passwords you've entered are different. Please make sure to enter same password twice. Введенные пароли не совпадают. Пожалуйста, проверьте что введенные пароли идентичны. A profile with this name already exists. Профиль с таким именем уже существует. Couldn't load this profile Невозможно загрузить данный профиль This profile is already in use. Профиль уже используется. Wrong password. Неверный пароль. Couldn't load profile Невозможно загрузить профиль There is no selected profile. You may want to create one. Нет выбранного профиля. Вы можете создать новый. Import Импорт Password protected profiles can't be automatically loaded. Профили защищенные паролем не могут быть загружены автоматически. Username input field Поле ввода имени пользователя Password input field, you can leave it empty (no password), or type at least 6 characters Поле ввода пароля, вы можете оставить его пустым (без пароля), или ввести минимум 6 символов Password confirmation field Поле подтверждения пароля Create a new profile button Кнопка создания нового профиля Profile list Список профилей List of profiles Список профилей Password input Ввод пароля Load automatically checkbox Загружать автоматически Import profile Импорт профиля Load selected profile button Загрузить выбранный профиль New profile creation page Страница создания нового профиля Loading existing profile page Страница загрузки существующего профиля MainWindow ... ... Add friends Добавить друзей Create a group chat Создать групповой чат View completed file transfers Посмотреть завершенные передачи файлов Change your settings Изменить ваши настройки Close Закрыть Your name Ваше имя Your status Ваш статус Open profile Открыть профиль Open profile page when clicked Открыть профиль по клику Status message input Поле ввода сообщения статуса Set your status message that will be shown to others Установить ваше сообщения статуса видимое другим пользователям Status Статус Set availability status Установить статус доступности Contact search Поиск контактов Contact search input for known friends Поле поиска контактов для известных друзей Sorting and visibility Сортировка и видимость Set friends sorting and visibility Установить сортировку друзей и видимость Open Add friends page Открыть страницленияобавить друзей Groupchat Групповой чат Open groupchat management page Открыть страницу настройки группового чата File transfers history История передачи файлов Open File transfers history Открыть историю передачи файлов Settings Настройки Open Settings Открыть настройки Nexus View OS X Menu bar Вид Window OS X Menu bar Окно Minimize OS X Menu bar Свернуть Bring All to Front OS X Menu bar Отобразить все Exit Fullscreen Выйти из полноэкранного режима Enter Fullscreen Войти в полноэкранный режим NotificationEdgeWidget Unread message(s) %n непрочитанное сообщение %n непрочитанных сообщения %n непрочитанных сообщений PasswordEdit CAPS-LOCK ENABLED Включен Caps Lock PrivacyForm Privacy Конфиденциальность Confirmation Подтверждение Do you want to permanently delete all chat history? Вы хотите навсегда удалить всю историю переписки? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Ваши друзья будут видеть, что вы набираете сообщение. Send typing notifications Отправлять информацию о наборе сообщения Keep chat history Хранить историю чата NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam АнтиСпам это часть вашего Tox ID. Если вы часто получаете ошибочные запросы в друзья, вам достаточно изменить АнтиСпам. Пользователи не смогут добавлять вас с вашим старым ID, но вы сохраните ваших текущих друзей. NoSpam АнтиСпам NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. АнтиСпам это изменяемая часть вашего Tox ID. Если к вам стали приходить частые и ненужные запросы в друзья, вам достаточно изменить это значение. Generate random NoSpam Сгенерировать случайное значение АнтиСпам Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Хранение истории чата находится в разработке. Возможно изменение формата сохранения данных, что может привести к потере данных. Privacy Конфиденциальность BlackList Черный список Filter group message by group member's public key. Put public key here, one per line. Фильтровать групповые сообщения по открытому ключу. Укажите публичные ключи, по одному на каждой строке. Profile Failed to derive key from password, the profile won't use the new password. Ошибка извлечения ключа из пароля, новый пароль на профиль не установлен. Couldn't change password on the database, it might be corrupted or use the old password. Невозможно изменить пароль в базе данных, должно быть база повреждена или вы используете старый пароль. Toxing on qTox Общайтесь в qTox ProfileForm Choose a profile picture Выбрать аватар Error Ошибка Rename "%1" renaming a profile Переименовать "%1" Unable to open this file. Невозможно открыть файл. Current profile: Текущий профиль: Remove Удалить Unable to read this image. Невозможно прочитать это изображение. The supplied image is too large. Please use another image. Выбранное изображение слишком большое. Пожалуйста, выберите другое изображение. Couldn't rename the profile to "%1" Не удалось переименовать профиль в "%1" Location not writable Title of permissions popup Путь недоступен для записи You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup У вас нет прав для записи в эту директорию. Выберете другую, или отмените диалог сохранения. Really delete profile? deletion confirmation title Вы действительно хотите удалить профиль? Save save qr image Сохранить Save QrCode (*.png) save dialog filter Сохранить QR-код (*.png) Nothing to remove Нечего удалять Your profile does not have a password! У вашего профиля нет пароля! Really delete password? deletion confirmation title Действительно удалить пароль? Please enter a new password. Пожалуйста, введите новый пароль. Failed to copy file Не удалось скопировать файл The file you chose could not be written to. Запись в выбранный вами файл невозможна. Are you sure you want to delete this profile? deletion confirmation text Вы действительно хотите удалить этот профиль? Files could not be deleted! deletion failed title Файлы не могут быть удалены! Register (processing) Регистрация (в процессе) Update (processing) Обновление (в процессе) Done! Выполнено! Account %1@%2 updated successfully Аккаунт %1@%2 обновлен успешно Successfully added %1@%2 to the database. Save your password %1@%2 успешно внесен в базу данных. Сохраните ваш пароль Toxme error Ошибка Toxme Register Регистрация Update Обновить Change password button text Сменить пароль Set profile password button text Установить пароль для профиля Current profile location: %1 Путь к текущему профилю: %1 Couldn't change password Невозможно изменить пароль This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Этот набор символов позволит другим Tox клиентам связаться с вами. Поделитесь им со своими друзьями, чтобы начать общаться. Этот ID включает NoSpam код (синий), и контрольную сумму (серый). Empty path is unavaliable Пустой путь недопустим Failed to rename Не удалось переименовать Profile already exists Профиль уже существует A profile named "%1" already exists. Профиль с именем "%1" уже существует. Empty name Пустое имя Empty name is unavaliable Пустое имя недопустимо Empty path Путь не задан Couldn't change password on the database, it might be corrupted or use the old password. Невозможно изменить пароль в базе данных, должно быть база повреждена или вы используете старый пароль. Export profile Экспортировать профиль Tox save file (*.tox) save dialog filter Файл профиля Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Следующие файлы не могут быть удалены: Please manually remove them. deletion failed text part 2 Пожалуйста, удалите их вручную. Are you sure you want to delete your password? deletion confirmation text Вы уверены, что хотите удалить свой пароль? Images (%1) filetype filter Изображения (%1) ProfileImporter Import profile import dialog title Импорт профиля Tox save file (*.tox) import dialog filter Файл профиля Tox (*.tox) Ignoring non-Tox file popup title Игнорирование не-Tox файлов Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Предупреждение: Вы выбрали файл, который не является профилем Tox; он будет проигнорирован. Profile already exists import confirm title Профиль уже существует A profile named "%1" already exists. Do you want to erase it? import confirm text Профиль с именем "%1" уже существует. Желаете его стереть? File doesn't exist Файл не существует Profile doesn't exist Профиль не существует Profile imported Профиль импортирован %1.tox was successfully imported %1.tox был успешно импортирован QApplication Ok OK Cancel Отмена Yes Да No Нет LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Письмо справа налево QMessageBox Couldn't add friend Не удалось добавить друга %1 is not a valid Toxme address. %1 - не корректный адрес Toxme. You can't add yourself as a friend! When trying to add your own Tox ID as friend Вы не можете добавить себя в друзья! QObject Tox URI to parse Без перевода, так как весь остальной CLI на английском Tox URI для обработки Starts new instance and loads specified profile. Без перевода, так как весь остальной CLI на английском Запускает новый экземпляр и загружает указанный профиль. profile Без перевода, так как весь остальной CLI на английском профиль Default По-умолчанию Blue Синий Olive Оливковый Red Красный Violet Фиолетовая Incoming call... Входящий вызов... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Привет, это %1! Добавите меня в друзья? None No camera device set Отсутствует Desktop Desktop as a camera input for screen sharing Рабочий стол Server doesn't support Toxme Сервер не поддерживает Toxme You're making too many requests. Wait an hour and try again Вы делаете слишком много запросов. Подождите один час и повторите попытку This name is already in use Это имя уже используется This Tox ID is already registered under another name Этот Tox ID уже зарегистрирован под другим именем Please don't use a space in your name Пожалуйста, не используйте пробелы в вашем имени Password incorrect Неверный пароль You can't use this name Нельзя использовать это имя Name not found Имя не найдено Tox ID not sent Tox ID не отправлен That user does not exist Этот пользователь не существует Error Ошибка qTox couldn't open your chat logs, they will be disabled. qTox не может загрузить историю переписки, она будет отключена. Problem with HTTPS connection Проблема с HTTPS соединением Internal ToxMe error Внутренняя ошибка ToxMe Reformatting text in progress.. Переформатирование текста.. Starts new instance and opens the login screen. Запускает новый экземпляр и открывает экран входа. Dark Темный Dark blue Темно-синий Dark olive Темно-оливковый Dark red Темно-красный Dark violet Темно-фиолетовый Failed to load profile automatically. Не удалось загрузить профиль автоматически. online contact status онлайн away contact status отошел busy contact status занят offline contact status Не в сети blocked contact status заблокирован RemoveFriendDialog Remove friend Удалить друга Also remove chat history Также удалить историю переписки Remove Удалить Are you sure you want to remove %1 from your contacts list? Вы уверены, что хотите удалить %1 из вашего списка контактов? Remove all chat history with the friend if set Удаляет всю историю переписки с другом если установлен ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Обведите область с зажатой левой клавишей. Нажмите %1, чтобы скрыть/показать окно qTox или %2 для отмены. Space [Space] key on the keyboard Пробел Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Нажмите %1, чтобы отправить скриншот выбранной области, %2, чтобы скрыть/показать окно qTox, или клавишу %3 для отмены. Enter [Enter] key on the keyboard Ввод SearchForm The text could not be found. Не удалось найти текст. Start Начало SearchSettingsForm Form Форма Start search: Начать поиск: from the end с конца from the beginning с начала after date после даты before date до даты 00.00.0000 00.00.0000 Case sensitive Учитывать регистр Whole words only Только слова целиком Use regular expressions Использовать регулярные выражения SetPasswordDialog Set your password Установите ваш пароль Confirm: Подтверждение: Password: Пароль: Password strength: %p% Сложность пароля: %p% The password is too short Пароль слишком короткий The password doesn't match. Пароли не совпадают. Confirm password Подтвердите пароль Confirm password input Ввод подтверждения пароля Password input Ввод пароля Password input field, minimum 6 characters long Поле ввода пароля, длиной минимум 6 символов Settings Circle #%1 Список #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Добавить друга Do you want to add %1 as a friend? Хотите добавить %1 в друзья? User ID: ID пользователя: Friend request message: Текст запроса: Send Send a friend request Отправить Cancel Don't send a friend request Отмена UserInterfaceForm None Отсутствует User Interface Внешний вид UserInterfaceSettings Chat Чат Base font: Основной шрифт: px px Size: Размер: New text styling preference may not load until qTox restarts. Для применения нового стиля текстовых сообщений может потребоваться перезагрузка qTox. Text Style format: Стиль текстовых сообщений: Select text styling preference. Выберите стиль текстовых сообщений. Plaintext Обычный текст Show formatting characters Отображать символы форматирования Don't show formatting characters Не отображать символы форматирования New message Новое сообщение Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Открывать окно qTox при получении нового сообщения, если оно еще не было открыто. Open window Открыть окно Contact list Список контактов If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Если выбрано, групповые чаты будут размещаться вверху списка контактов, иначе размещение будет под списком активных контактов. Place groupchats at top of friend list Поместить групповые чаты вверху контакт-листа Your contact list will be shown in compact mode. toolTip for compact layout setting Ваш список контактов будет показан в компактном виде. Compact contact list Компактный список контактов Multiple windows mode Многооконный режим Open each chat in an individual window Открывать каждый чат в отдельном окне Emoticons Смайлики Use emoticons Использовать смайлики Smiley Pack: Text on smiley pack label Набор смайликов: Emoticon size: Размер смайликов: px px Theme Тема Style: Стиль: Theme color: Цветовая схема: Timestamp format: Формат времени: Date format: Формат даты: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Если включено, каждый контакт без аватара будет иметь сгенерированный аватар, основываясь на своем Tox ID, вместо картинки по умолчанию. Требуется перезагрузка для применения. Use identicons instead of empty avatars Использовать картинки вместо пустых аватаров Use colored nicknames in chats Использовать красочные никнеймы в чатах Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Показывать уведомление при получении нового сообщения, когда окно не выбрано. Notify Уведомлять Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Уведомлять о новых сообщениях в групповых чатах только когда вас упоминают. Group chats only notify when mentioned Получать уведомления в групповых чатах только когда вас упоминают Play sound Воспроизводить звуки Play sound while Busy Воспроизводить звук, когда Занят Notify via desktop notifications Уведомлять с помощью уведомлений на рабочем столе Hide message sender and contents Скрыть отправителя сообщения и его содержимое Widget Online В сети Online Button to set your status to 'Online' В сети Away Button to set your status to 'Away' Вероятно, это не столь долгое путешествие Отошел Busy Button to set your status to 'Busy' Занят toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Не удалось запустить toxcore с вашими настройками прокси, qTox не может работать; измените ваши настройки и перезапустите его. Your name Ваше имя Create new group... Создать новую группу... Add new circle... Добавить новый список... %n New Friend Request(s) %n новый запрос в друзья %n новых запроса в друзья %n новых запросов в друзья %n New Group Invite(s) %n новое приглашение в группу %n новых приглашения в группы %n новых приглашений в группы By Name По имени By Activity По активности All Все Offline Не в сети Friends Друзья Groups Группы Search Contacts Поиск контактов File Файл Edit Profile Изменить профиль Change Status Изменить статус Log out Выйти Edit Изменить Logout Tray action menu to logout user Сменить аккаунт Exit Tray action menu to exit tox Выход Filter... Фильтр... Contacts Контакты Add Contact... Добавить контакт... Next Conversation Следующая беседа Previous Conversation Предыдущая беседа toxcore failed to start, the application will terminate after you close this message. Не удалось запустить toxcore, приложение будет завершено после того как вы закроете это сообщение. Executable file popup title Исполняемый файл You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Вы просите qTox открыть исполняемый файл. Исполняемые файлы могут нанести вред вашему компьютеру. Вы уверены, что хотите открыть этот файл? Couldn't request friendship Не удалось запросить добавление в друзья Status Статус Message failed to send Не удалось отправить сообщение Groupchat #%1 Групповой чат #%1 Show Tray action menu to show qTox window Отобразить Add friend title of the window Добавить друга Group invites title of the window Приглашения в группы File transfers title of the window Передача файлов Settings title of the window Настройки My profile title of the window Мой профиль Failed to send file "%1" Не удалось отправить файл "%1" File sent Файл отправлен sent you a friend request. отправил вам запрос на добавление в друзья. invites you to join a group. приглашает вас присоединиться к группе. qTox/translations/sk.ts000066400000000000000000003277441415623743500155520ustar00rootroot00000000000000 AVForm Audio/Video Audio/Video Default resolution Predvolené rozlíšenie Disabled Zakázané Select region Vyberte oblasť Screen %1 Obrazovka %1 Audio Settings Nastavenia zvuku Gain Zosilnenie Playback device Prehrávacie zariadenie Use slider to set volume of your speakers. Pomocou posúvača nastavte hlasitosť reproduktorov. Capture device Vstupné zariadenie Volume Hlasitosť Video Settings Nastavenia videa Video device Video zariadenie Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Nastavte rozlíšenie vašej kamery. Čím vyššia hodnota je nastavená, tým lepšej kvalite vás uvidia ostatní. Čím vyššia kvalita, tým vyššie sú nároky na internetové pripojenie. Rýchlosť vášho připojenia nemusí byť vždy dostačujúca pre vyššiu kvalitu videa, čo môže spôsobiť problémy počas videohovoru. Resolution Rozlíšenie Rescan devices Znova prehľadať zariadenia Test Sound Test zvuku Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Kvalita zvuku Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Kvalita prenosu zvuku. Znížte toto nastavenie ak máte pomalé pripojenie alebo ak chcete šetriť dáta. High (64 kbps) Vysoká (64 kbps) Medium (32 kbps) Stredná (32 kbps) Low (16 kbps) Nízka (16 kbps) Very low (8 kbps) Veľmi nízka (8 kbps) Threshold Prah AboutForm About O programe Original author: %1 Pôvodný autor: %1 You are using qTox version %1. Používate qTox verzie %1. Commit hash: %1 Commit hash: %1 toxcore version: %1 toxcore verzia: %1 Qt version: %1 QT verzia: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. Kliknite sem pre nahlásenie chyby. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Úplný zoznam %1 nájdete na Githube bug-tracker Replaces `%1` in the `A list of all known…` Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Písanie užitočných hlásení o chybách contributors Replaces `%1` in `See a full list of…` prispievateľov AboutFriendForm Dialog username užívateľské meno status message Used aliases: Používané prezývky: HISTORY OF ALIASES HISTÓRIA PREZÝVOK Automatically accept files from contact if set Automaticky prijímať súbory od tohto kontaktu Auto accept files Automaticky prijímať súbory Default directory to save files: Predvolený adresár pre uloženie súborov: Auto accept for this contact is disabled Automatické prijímanie zakázené Auto accept call: Automatické prijímanie hovorov: Manual Ručne Audio Audio hovory Audio + Video Audio + Video hovory Automatically accept group chat invitations from this contact if set. Automatické prijímanie pozvánok do skupín od tohto kontaktu. Auto accept group invites Automaticky prijať pozvánky do skupín Remove history (operation can not be undone!) Odstrániť históriu (operácia sa nedá vrátiť späť!) Notes Poznámky Input field for notes about the contact Pole na poznámky o kontakte You can save comment about this contact here. Sem môžete vložiť poznámky o tomto kontakte. History removed História bola odstránená Choose an auto accept directory popup title Zvoľte adresár pre automatické prijímanie <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Potvrdenie Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Verzia License Licencia Authors Autori Known Issues Známe problémy Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Pridať priateľov Invalid Tox ID format Neplatný formát Tox ID Send friend request Poslať žiadosť o priateľstvo Add a friend Pridať priateľa Friend requests Žiadosti o priateľstvo Accept Prijať Reject Odmietnuť Couldn't add friend Nepodarilo sa pridať priateľa Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, 76 znakov šestnástkovej sústavy alebo meno@priklad.com Type in Tox ID of your friend Zadajte Tox ID vášho priateľa Friend request message Správa ku žiadosti o priateľstvo Type message to send with the friend request or leave empty to send a default message Zadajte správu k žiadosti o priateľstvo, alebo nechajte prázdne, pre odoslanie predvolenej správy %1 Tox ID is invalid or does not exist Toxme error Tox ID %1 je neplatné alebo neexistuje You can't add yourself as a friend! When trying to add your own Tox ID as friend Nemôžete pridať seba ako priateľa! Open contact list Otvoriť zoznam kontaktov Couldn't open file Súbor sa nepodarilo otvoriť Couldn't open the contact file Error message when trying to open a contact list file to import Nepodarilo sa otvoriť súbor s kontaktmi Invalid file Neplatný súbor We couldn't find any contacts to import in this file! Nenašli sa žiadne kontakty na import v tomto súbore! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description 76 šestnástkových znakov alebo meno@priklad.com Message The message you send in friend requests Správa Open Button to choose a file with a list of contacts to import Otvoriť Send friend requests Poslať žiadosti o priateľstvo %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Som %1! Zatoxujeme si? Import a list of contacts, one Tox ID per line Importovať zoznam kontaktov, jedno Tox ID na riadok Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import %n kontaktu je pripravený. Pre potvrdenie, stlačte Odoslať Import %n kontaktov je pripravený. Pre potvrdenie, stlačte Odoslať Import %n kontaktov je pripravený. Pre potvrdenie, stlačte Odoslať Import contacts Importovať kontakty AdvancedForm Advanced Rozšírené Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Pokyaľ si nie ste %1 istý čo robíte, prosím %2mente tu nič. Zmeny môžu viesť k problémom s qToxom, dokonca k strate dát, napríklad histórie. really naozaj not ne IMPORTANT NOTE DÔLEŽITÉ Reset settings Obnoviť pôvodné nastavenia All settings will be reset to default. Are you sure? Všetky nastavenia sa obnovia na predvolené. Ste si istý? Yes Áno No Nie Call active popup title Hovor aktívny You can't disconnect while a call is active! popup text Počas hovoru sa nemôžete odpojiť! Save File Uložiť súbor Logs (*.log) Denníky (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Uloží nastavenia do pracovného adresára namiesto obvyklého konfiguračného adresára Make Tox portable Urobiť Tox prenosný Reset to default settings Obnoviť predvolené nastavenia Portable Prenosný Tox Connection Settings Nastavenia pripojenia Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Povoliť IPv6 (odporúča sa) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Vypnite, pre možnosť toxovania cez Tor. Avšak to zvyšuje záťaž siete Tox, takže vypínajte len v prípade potreby. Enable UDP (recommended) Text on checkbox to disable UDP Povoliť UDP (odporúča sa) Proxy type: Typ proxy: Address: Text on proxy addr label Adresa: Port: Text on proxy port label Port: None Žiadny SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Pripojiť znovu Debug Ladenie Export Debug Log Exportovať denník ladenia Copy Debug Log Kopírovať denník ladenia Enable LAN discovery ChatForm Send a file Poslať súbor qTox wasn't able to open %1 qTox nebol schopný otvoriť %1 Unable to open Nedá sa otvoriť Bad idea Zlý nápad %1 calling %1 volá Calling %1 Volanie %1 Failed to open temporary file Temporary file for screenshot Nepodarilo sa otvoriť dočasný súbor qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox nebol schopný uložiť snímku obrazovky Call with %1 ended. %2 Hovor s %1 skončil. %2 Call duration: Trvanie hovoru: %1 is typing %1 píše Copy Kopírovať You're trying to send a sequential file, which is not going to work! Snažíte sa poslať sekvenčný súbor. To nebude fungovať! %1 is now %2 e.g. "Dubslow is now online" %1 je %2 Call with %1 ended unexpectedly. %2 Hovor s %1 nečakane skončil. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Nemožno začať audio hovor Start audio call Začať audio hovor End audio call Skončiť audio hovor Cancel audio call Zrušiť audio hovor Accept audio call Prijať audio hovor Can't start video call Nemožno začať video hovor Start video call Začať video hovor End video call Skončiť video hovor Cancel video call Zrušiť video hovor Accept video call Prijať video hovor Sound can be disabled only during a call Zvuk možno vypnúť iba počas hovoru Unmute call Zrušiť stlmenie hovoru Mute call Stlmiť hovor Microphone can be muted only during a call Mikrofón môže byť stlmený iba počas hovoru Unmute microphone Zapnúť mikrofón Mute microphone Stlmiť mikrofón ChatLog Copy Kopírovať Select all Vybrať všetko pending prebieha ChatTextEdit Type your message here... Sem napíšte vašu správu... CircleWidget Rename circle Menu for renaming a circle Premenovať kruh Remove circle Menu for removing a circle Odstrániť kruh Open all in new window Otvoriť všetky v novom okne Core /me offers friendship, "%1" /me žiada o priateľstvo, "%1" Invalid Tox ID Error while sending friendship request Neplatné Tox ID You need to write a message with your request Error while sending friendship request Musíte napísať správu ku žiadosti Your message is too long! Error while sending friendship request Vaša správa je príliš dlhá! Friend is already added Error while sending friendship request Priateľ je už pridaný Groupchat %1 DesktopNotify New message Nová správa Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen ETA:10:10 Filename Ausgelassen Názov súboru Waiting to send... file transfer widget Čaká sa na odoslanie... Accept to receive this file file transfer widget Prijať tento súbor Location not writable Title of permissions popup Miesto nie je zapisovateľné You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nemáte povolenie na zápis na určené miesto. Vyberte si iné, alebo zrušte uloženie. Resuming... file transfer widget Obnovenie... Cancel transfer Zrušiť prenos Pause transfer Pozastaviť prenos Paused file transfer widget Pozastavený Open file Otvoriť súbor Open file directory Otvoriť umiestnenie súboru Resume transfer Obnoviť prenos Accept transfer Prijať prenos Save a file Title of the file saving dialog Uložiť súbor Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Prenesené súbory Downloads Stiahnuté Uploads Odoslané FriendListWidget Today Dnes Yesterday Včera Last 7 days Posledných 7 dní This month Tento mesiac Older than 6 Months Staršie ako 6 mesiacov Never Nikdy FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Žiadosť o priateľstvo Someone wants to make friends with you Niekto sa chce stať vašim priateľom User ID: ID používateľa: Friend request message: Správa žiadosti o priateľstvo: Accept Accept a friend request Prijať Reject Reject a friend request Odmietnuť FriendWidget Invite to group Menu to invite a friend to a groupchat Pozvať do skupiny Move to circle... Menu to move a friend into a different circle Presunúť do kruhu... To new circle Do nového kruhu Remove from circle '%1' Odobrať z kruhu "%1" Move to circle "%1" Presunúť do kruhu "%1" Open chat in new window Zobraziť chat v novom okne Remove chat from this window Odstrániť chat z tohto okna To new group Do novej skupiny Invite to group '%1' Pozvať do skupiny '%1' Set alias... Zmeniť meno... Auto accept files from this friend context menu entry Automaticky prijať súbory od tohto priateľa Remove friend Menu to remove the friend from our friendlist Odstrániť priateľa Show details Zobraziť podrobnosti Choose an auto accept directory popup title Zvoľte priečinok pre automatické prijímanie New message Nová správa Online Online Away Preč Busy Zaneprázdnený Offline Ausgelassen Offline GeneralForm General Obecné Choose an auto accept directory popup title Zvoľte priečinok pre automatické prijímanie GeneralSettings General Settings Obecné nastavenia The translation may not load until qTox restarts. Preklad sa nemusí načítať kým sa qTox nereštartuje. Language: Jazyk: Show system tray icon Zobraziť ikonu v systémovej lište Enable light tray icon. toolTip for light icon setting Nastaví svetlú ikonu v systémovej lište. Light icon Svetlá ikona qTox will start minimized in tray. toolTip for Start in tray setting qTox sa spustí minimalizovaný v systémovej lište. Start in tray Spustiť v systémovej lište After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Po zatvorení (X) sa qTox minimalizuje do systémovej lišty, namiesto ukončenia. Close to tray Zavrieť do systémovej lišty After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Po minimalizovaní (_) sa qTox schová do systémovej lišty, namiesto panelu úloh. Minimize to tray Minimalizovať do systémovej lišty Autostart Automatické spustenie Set where files will be saved. Nastavte kam sa majú ukladať súbory. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Je možné automatické prijatie nastaviť zvlášť pre každého priateľa, po kliknutí pravým na priateľa. Autoaccept files Automatické prijatie súborov Set to 0 to disable Nastavte 0 pre vypnutie Your status is changed to Away after set period of inactivity. Váš stav sa pri neaktivite zmení na Preč po uplynutí nastaveného času. Auto away after (0 to disable): Automaticky Preč po (0 pre vypnutie): Show contacts' status changes Upozornenie pri zmene stavu kontaktov Start qTox on operating system startup (current profile). Spustiť qTox pri štarte operačného systému (aktuálny profil). Default directory to save files: Predvolený priečinok pre ukladanie súborov: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Odoslať správu Smileys Emotikony Send file(s) Poslať súbor(y) Send a screenshot Poslať snímku obrazovky Save chat log Uložiť záznam chatu Clear displayed messages Vyčistiť zobrazené správy Cleared Vyčistené Quote selected text Citovať označený text Copy link address Kopírovať adresu odkazu Confirmation Potvrdenie You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Načítať históriu chatu... Export to file Export do súboru GenericNetCamView Tox video Tox video Show Messages Zobraziť správy Hide Messages Skryť Správy Full Screen Toggle video preview Mute audio Mute microphone Stlmiť mikrofón End video call Skončiť video hovor Exit full screen GroupChatForm %1 has set the title to %2 %1 nastavil meno konverzácie na %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Skupiny Create new group Vytvoriť novú skupinu Group invites Pozvánky do skupín GroupInviteWidget Invited by %1 on %2 at %3. %2 o %3 pozval %1. Join Pripojiť sa Decline Odmietnuť GroupWidget Set title... Zmeniť názov... Open chat in new window Zobraziť chat v novom okne Remove chat from this window Odstrániť chat z tohto okna Quit group Menu to quit a groupchat Opustiť skupinu %n user(s) in chat Number of users in chat New Message Online Online IdentitySettings Public Information Verejné informácie Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Táto séria znakov vás umožní kontaktovať pomocou ostatných Tox klientov. Pre komunikáciu ju zdeľte vašim priateľom. Your Tox ID (click to copy) Vaše Tox ID (kliknutím zkopírujete) Profile Profil Rename profile. tooltip for renaming profile button Premenovať profil. Go back to the login screen tooltip for logout button Späť na prihlasovaciu obrazovku Logout import profile button Odhlásiť sa Remove password Odstrániť heslo Change password Zmeniť heslo This QR code contains your Tox ID. You may share this with your friends as well. Tento QR kód obsahuje vaše Tox ID. Aj toto môžete zdieľať s priateľmi. Save image Uložiť obrázok Copy image Kopírovať obrázok Rename rename profile button Premenovať Delete profile. delete profile button tooltip Vymazať Profil. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Umožňuje vám exportovať váš Tox profil do súboru. Profil neobsahuje históriu. Export export profile button Exportovať Delete delete profile button Odstrániť Server Server Hide my name from the public list Nezobrazovať moje meno vo verejnom zozname Register Registrovať Your password Vaše heslo Update Aktualizácia Register on ToxMe Zaregistrovať na ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Meno pre službu ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Voliteľné. Niečo o vás. Alebo o vašej mačke. Optional. Something about you. Or your cat. Tooltip for the Biography field. Voliteľné. Niečo o vás. Alebo o vašej mačke. ToxMe service to register on. Zaregisteovať voči ToxMe službe. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ak nie je zaškrtnuté, položky ToxMe sú verejne dostupné. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Odstráni vaše heslo a šifrovanie vašeho profilu. Name input Name visible to contacts Meno viditeľné pre kontakty Status message input Vstup pre stavovú správu Status message visible to contacts Your Tox ID Vaše Tox ID Save QR image as file Uložiť QR kód ako obrázok Copy QR image to clipboard Kopírovať QR kód do schránky ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Adresa služby ToxMe Visibility on the ToxMe service Viditeľnosť v rámci služby ToxMe Password Heslo Update ToxMe entry Aktualizovať záznam v ToxMe Rename profile. Premenovať profil. Delete profile. Vymazať Profil. Export profile Exportovať profil Remove password from profile Odstrániť heslo z profilu Change profile password Zmeniť heslo profilu My name: Moje meno: My status: My username Moje užívateľské meno My biography O mne My profile Môj profil LoadHistoryDialog Load History Dialog Načítanie histórie Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Užívateľské meno: Password: Heslo: Confirm: Potvrdiť: Password strength: %p% Sila hesla: %p% Create Profile Vytvoriť Profil If the profile does not have a password, qTox can skip the login screen Keď profil nie je chránený heslom, qTox môže preskočiť prihlasovacie okno Load automatically Automaticky načítať Load Načítať Load Profile Načítať profil New Profile Nový profil Couldn't create a new profile Nepodarilo sa vytvoriť nový profil The username must not be empty. Užívateľské meno nesmie byť prázdne. The password must be at least 6 characters long. Heslo musí mať aspoň 6 znakov. The passwords you've entered are different. Please make sure to enter same password twice. Heslá ktoré ste zadali sú rôzne. Uistite sa, že zadávate rovnaké heslo dvakrát. A profile with this name already exists. Profil s týmto menom už existuje. Password protected profiles can't be automatically loaded. Profily chránené heslom nie je možné automaticky načítať. Couldn't load profile Nepodarilo sa načítať profil There is no selected profile. You may want to create one. Couldn't load this profile Nepodarilo sa načítať tento profil This profile is already in use. Tento profil sa už používa. Wrong password. Nesprávne heslo. Import Importovať Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Pole pre heslo môžete nechať prázdne (bez hesla), alebo zadajte aspoň 6 znakov Password confirmation field Pole pre potvrdenie hesla Create a new profile button Tlačidlo Vytvoriť nový profil Profile list Zoznam profilov List of profiles Zoznam profilov Password input Load automatically checkbox Políčko Načítať automaticky Import profile Importovať profil Load selected profile button Tlačidlo Načítať zvolený profil New profile creation page Stránka pre vytvorenie nového profilu Loading existing profile page MainWindow Your name Vaše meno Your status Váš status ... Ausgelassen ... Add friends Pridať priateľov Create a group chat Vytvorenie skupinového chatu View completed file transfers Zobraziť ukončené prenosy súborov Change your settings Zmeniť nastavenia Close Zatvoriť Open profile Otvoriť profil Open profile page when clicked Kliknutím otvoriť stránku s profilom Status message input Vstup pre stavovú správu Set your status message that will be shown to others Status Stav Set availability status Nastavenie dostupnosti Contact search Vyhľadávanie kontaktov Contact search input for known friends Sorting and visibility Triedenie a viditeľnosť Set friends sorting and visibility Nastavenie triedenia priateľov a viditeľnosti Open Add friends page Groupchat Skupinový chat Open groupchat management page File transfers history História prenosov súborov Open File transfers history Otvoriť históriu prenosov súborov Settings Nastavenia Open Settings Otvoriť nastavenia Nexus View OS X Menu bar Zobrazenie Window OS X Menu bar Okno Minimize OS X Menu bar Minimalizovať Bring All to Front OS X Menu bar Presunúť všetko dopredu Exit Fullscreen Opustiť režim celej obrazovky Enter Fullscreen Na celú obrazovku NotificationEdgeWidget Unread message(s) Neprečítaná správa Neprečítané správy Neprečítaných správ PasswordEdit CAPS-LOCK ENABLED ZAPNUTÝ CAPS-LOCK PrivacyForm Privacy Súkromie Confirmation Potvrdenie Do you want to permanently delete all chat history? Chcete natrvalo odstrániť celú históriu chatu? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Vaši priatelia budú vidieť, že píšete. Send typing notifications Odosielať upozornenia o písaní Keep chat history Udržiavať históriu chatu NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam je časť vášho Tox ID. Keď dostávate nežiadúce žiadosti o priateľstvo, zmeňte si NoSpam. Po zmene vám nebudú môcť poslať žiadosť pomocou starého ID, ale zachováte si súčastných priateľov. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam je časť vášho ID, ktorú môžete ľubovoľne meniť. Keď dostávate nežiadúce žiadosti o priateľstvo, zmeňte si NoSpam. Generate random NoSpam Generovať náhodný NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Udržiavanie histórie chatu je stále vo vývoji. Sú možné zmeny formátu, ktoré môžu spôsobiť stratu dát. Privacy Súkromie BlackList Čierna listina Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox Toxujem na qToxu ProfileForm Choose a profile picture Vyberte si profilovú fotku Error Chyba Rename "%1" renaming a profile Premenovať "%1" Unable to open this file. Nepodarilo sa otvoriť tento súbor. Current profile: Aktuálny profil: Remove Odstrániť Unable to read this image. Nepodarilo sa prečítať tento obrázok. The supplied image is too large. Please use another image. Dodaný obrázok je príliš veľký. Prosím, použite iný. Couldn't rename the profile to "%1" Nepodarilo sa premenovať profil na "%1" Location not writable Title of permissions popup Miesto nie je zapisovateľné You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nemáte povolenie na zápis na určené miesto. Vyberte si iné, alebo zrušte uloženie. Failed to copy file Nepodarilo sa skopírovať súbor The file you chose could not be written to. Do súboru, ktorý ste zvolili, sa nedá písať. Really delete profile? deletion confirmation title Naozaj chcete odstrániť profil? Nothing to remove Nič na odstránenie Your profile does not have a password! Váš profil neobsahuje heslo! Really delete password? deletion confirmation title Naozaj chcete odstrániť heslo? Please enter a new password. Prosím zadajte nové heslo. Are you sure you want to delete this profile? deletion confirmation text Naozaj chcete odstrániť tento profil? Save save qr image Uložiť Save QrCode (*.png) save dialog filter Uložiť Qr Kód (*.png) Files could not be deleted! deletion failed title Súbory nie je možné odstrániť! Register (processing) Registrácia (spracovanie) Update (processing) Aktualizácia (spracovanie) Done! Hotovo! Account %1@%2 updated successfully Účet %1@%2 bol úspešne aktualizovaný Successfully added %1@%2 to the database. Save your password Toxme error Toxme chyba Register Registrovať Update Aktualizácia Change password button text Zmeniť heslo Set profile password button text Nastaviť heslo profilu Current profile location: %1 Aktuálne umiestnenie profilu: %1 Couldn't change password Nepodarilo sa zmeniť heslo This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Táto séria znakov vás umožní kontaktovať pomocou ostatných Tox klientov. Pre komunikáciu ju zdeľte vašim priateľom. Toto ID obsahuje NoSpam kód (modrou) a kontrolný súčet (šedou). Empty path is unavaliable Failed to rename Nepodarilo sa premenovať Profile already exists Profil už existuje A profile named "%1" already exists. Profil s názvom "%1" už existuje. Empty name Prázdne meno Empty name is unavaliable Empty path Prázdna cesta Couldn't change password on the database, it might be corrupted or use the old password. Export profile Exportovať profil Tox save file (*.tox) save dialog filter Súbor Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Nebolo možné odstrániť nasledovné súbory: Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Naozaj chcete odstrániť svoje heslo? Images (%1) filetype filter Obrázky (%1) ProfileImporter Import profile import dialog title Import profilu Tox save file (*.tox) import dialog filter Súbor Tox (*.tox) Ignoring non-Tox file popup title Ignorovanie neTox súboru Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title Profil už existuje A profile named "%1" already exists. Do you want to erase it? import confirm text Profil s názvom "%1" už existuje. Chcete ho odstrániť? File doesn't exist Súbor neexistuje Profile doesn't exist Profil neexistuje Profile imported Profil importovaný %1.tox was successfully imported %1.tox bol úspešne importovaný QApplication Ok Ok Cancel Zrušiť Yes Áno No Nie LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout Zľava doprava QMessageBox Couldn't add friend Nepodarilo sa pridať priateľa %1 is not a valid Toxme address. %1 nie je platná Toxme adresa. You can't add yourself as a friend! When trying to add your own Tox ID as friend Nemôžete pridať seba ako priateľa! QObject Tox URI to parse Starts new instance and loads specified profile. Spustí novú inštanciu a načíta zadaný profil. profile profil Default Predvolená Blue Modrá Olive Olivová Red Červená Violet Fialová Incoming call... Prichádzajúci hovor... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! Som %1! Zatoxujeme si? None No camera device set Žiadne Desktop Desktop as a camera input for screen sharing Plocha Server doesn't support Toxme Server nepodporuje Toxme You're making too many requests. Wait an hour and try again Posielate príliš veľa žiadostí. Počkajte hodinu a skúste to znova This name is already in use Toto meno sa už používa This Tox ID is already registered under another name Toto Tox ID je už zaregistrované pod iným menom Please don't use a space in your name Prosím, nepoužívajte medzery vo vašom mene Password incorrect Nesprávne heslo You can't use this name Nemôžete použiť toto meno Name not found Meno nebolo nájdené Tox ID not sent Tox ID nebolo odoslané That user does not exist Tento používateľ neexistuje Error Chyba qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Problém s HTTPS spojením Internal ToxMe error Vnútorná chyba ToxMe Reformatting text in progress.. Prebieha formátovanie textu.. Starts new instance and opens the login screen. Spustí novú inštanciu a otvorí prihlasovacie okno. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status online away contact status preč busy contact status zaneprázdnený offline contact status offline blocked contact status RemoveFriendDialog Remove friend Odstrániť priateľa Also remove chat history Odstrániť aj históriu chatu Remove Odstrániť Are you sure you want to remove %1 from your contacts list? Naozaj chcete odstrániť %1 zo zoznamu kontaktov? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Kliknite, a posunutím vyberte oblasť. Stlačte %1 pre skrytie/zobrazenie okna qTox, alebo %2 pre zrušenie. Space [Space] key on the keyboard Medzerník Escape [Escape] key on the keyboard Esc Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Stlačte %1 pre odoslanie vybranej oblasti, %2 pre skrytie/zobrazenie okna qTox, alebo %3 pre zrušenie. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Nastavte si svoje heslo Confirm: Potvrdiť: Password: Heslo: Password strength: %p% Sila hesla: %p% The password is too short Heslo je príliš krátke The password doesn't match. Heslá sa nezhodujú. Confirm password Potvrďte heslo Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 Kruh #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Pridať priateľa Do you want to add %1 as a friend? Chcete pridať %1 medzi priateľov? User ID: ID užívateľa: Friend request message: Správa žiadosti o priateľstvo: Send Send a friend request Odoslať Cancel Don't send a friend request Zrušiť UserInterfaceForm None Žiadny User Interface Používateľské rozhranie UserInterfaceSettings Chat Chat Base font: Základné písmo: px px Size: Veľkosť: New text styling preference may not load until qTox restarts. Nový štýl textu sa nemusí načítať do reštartu qToxu. Text Style format: Štýl textu: Select text styling preference. Vyberte preferovaný štýl textu. Plaintext Obyčajný text Show formatting characters Zobraziť formátovanie znakov Don't show formatting characters Nezobrazovať formátovanie znakov New message Nová správa Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Zobrazí okno keď dostanete novú správu, a žiadne okno qToxu nie je otvorené. Open window Otvoriť okno Contact list Zoznam kontaktov If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning V prípade označenia sa budú skupinové chaty zobrazovat na začiatku zoznamu priateľov. Inak budú umiestnené za aktívnymi priateľmi. Place groupchats at top of friend list Skupinové chaty zobraziť na začiatku zoznamu priateľov Your contact list will be shown in compact mode. toolTip for compact layout setting Zoznam kontaktov sa zobrazí v kompaktnom režime. Compact contact list Kompaktný zoznam kontaktov Multiple windows mode Režim viacerých okien Open each chat in an individual window Každý chat zobraziť v novom okne Emoticons Emotikony Use emoticons Používať emotikony Smiley Pack: Text on smiley pack label Emotikony: Emoticon size: Veľkosť emotikonov: px px Theme Vzhľad Style: Štýl: Theme color: Farba motívu: Timestamp format: Formát času: Date format: Formát dátumu: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Prehrať zvuk Play sound while Busy Prehrať zvuk aj v stavu Zaneprázdnený Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Online Away Button to set your status to 'Away' Preč Busy Button to set your status to 'Busy' Zaneprázdnený toxcore failed to start, the application will terminate after you close this message. toxcore sa nepodarilo spustiť. Aplikácia sa ukončí po zavretí tejto správy. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore sa nepodarilo spustiť so súčasnými nastaveniami proxy. qTox sa nedá spustiť; Upravte nastavenia a reštartujte. File Súbor Edit Profile Upraviť profil Change Status Log out Odhlásiť sa Edit Upraviť Logout Tray action menu to logout user Odhlásiť sa Exit Tray action menu to exit tox Ukončiť Filter... Filter... Contacts Kontakty Add Contact... Pridať kontakt... Next Conversation Nasledujúca konverzácia Previous Conversation Predchádzajúca konverzácia Executable file popup title Spustiteľný súbor You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Požiadali ste qTox o spustenie spustiteľného súboru. Spustiteľné súbory môžu poškodiť váš počítač. Naozaj chcete tento súbor otvoriť? Couldn't request friendship Nepodarilo sa požiadať o priateľstvo Status Stav Your name Vaše meno Message failed to send Správu sa nepodarilo odoslať Create new group... Vytvoriť novú skupinu... Add new circle... Vytvoriť nový kruh... %n New Friend Request(s) %n nová žiadosť o priateľstvo %n nové žiadosti o priateľstvo %n nových žiadostí o priateľstvo %n New Group Invite(s) %n nová pozvánka do skupiny %n nové pozvánky do skupín %n nových pozvánok do skupín By Name Podľa mena By Activity Podľa aktivity All Všetky Online Offline Ausgelassen Offline Friends Priatelia Groups Skupiny Search Contacts Vyhľadávanie kontaktov Groupchat #%1 Skupinový chat %1 Show Tray action menu to show qTox window Zobraziť Add friend title of the window Pridať priateľa Group invites title of the window Pozvánky do skupín File transfers title of the window Prenosy súborov Settings title of the window Nastavenia My profile title of the window Môj profil Failed to send file "%1" Nepodarilo sa odoslať súbor "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/sl.ts000066400000000000000000003226401415623743500155410ustar00rootroot00000000000000 AVForm Audio/Video Zvok/Video Default resolution Privzeta resolucija Disabled Dehabilitirano Select region Izberite območje Screen %1 Zaslon %1 Audio Settings Nastavitve zvoka Gain Playback device Naprava za predvajanje Use slider to set volume of your speakers. Uporabi drsalo za nastavitev glasnosti zvočnika. Capture device Naprava za zajemanje zvoka Volume Glasnost Video Settings Nastavitve video Video device Video naprave Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Nastavi resolucijo tvoje kamere. Večja vrednost pomeni boljša kvaliteta slike. Vendar je za to potrebna hitrejša internetna povezava. Včasih se lahko zgodi da je tvoj internet prepočasen za visoko kvaliteto videa in zato lahko pride do problemov pri video pogovorih. Resolution Resolucija Rescan devices Preglej znova naprave Test Sound Test zvoka Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Kakovost zvoka Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Visoka (64 kbps) Medium (32 kbps) Srednja (32 kbps) Low (16 kbps) Nizka (16 kbps) Very low (8 kbps) Zelo nizka (8 kbps) Threshold Vhod AboutForm About Več o qTox Original author: %1 Začetni avtor: %1 You are using qTox version %1. Uporabljate qTox %1. Commit hash: %1 toxcore version: %1 Verzija toxcore: %1 Qt version: %1 Različica Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. Kliknite tukaj, če želite prijaviti napako. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Glej celoten seznam %1 na Github bug-tracker Replaces `%1` in the `A list of all known…` bug-tracker Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Pisanje poročil o napakah contributors Replaces `%1` in `See a full list of…` sodelavci AboutFriendForm Dialog Okno sporočila username uporabniško ime status message sporočilo o stanju Used aliases: Uporabljeni vzdevki: HISTORY OF ALIASES ZGODOVINA VZDEVKOV Automatically accept files from contact if set Samodejno sprejemanje datotek iz kontakta (če nastavljen) Auto accept files Samodejno sprejemanje datotek Default directory to save files: Privzeta mapa za shranjevanje datotek: Auto accept for this contact is disabled Auto accept call: Samodejno sprejemanje klica: Manual Ročno Audio Zvok Audio + Video Zvok + Video Automatically accept group chat invitations from this contact if set. Sprejmi samodejno skupinski klepet za ta kontakt (če nastavljen). Auto accept group invites Sprejmi samodejno vabila na skupine Remove history (operation can not be undone!) Izbriši zgodovino (operacije ni mogoče razveljaviti!) Notes Opombe Input field for notes about the contact Polje za opombe o kontaktu You can save comment about this contact here. Lahko shranite komentar o tem kontaktu tukaj. History removed Zgodovina izbrisana Choose an auto accept directory popup title Izberi mapo za avtomatsko sprejemanje datotek <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Verzija License Licenca Authors Avtorji Known Issues Poznane napake Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Dodaj stike Send friend request Dodaj med stike Couldn't add friend Nemogoče dodati kontakta Invalid Tox ID format Format Tox ID ni veljaven Add a friend Dodaj stik Friend requests Prošnje prijateljstva Accept Sprejmi Reject Zavrni Tox ID, either 76 hexadecimal characters or name@example.com Tox ID, 76 šestnajstiških znakov ali name@example.com Type in Tox ID of your friend Vnesite Tox ID vašega prijatelja Friend request message Sporočilo o prošnji prijateljstva Type message to send with the friend request or leave empty to send a default message Vnesite sporočilo, ki želite poslati s prošnjo prijateljstva ali pustite prazno, če želite poslati privzeto sporočilo %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID, je neveljaven ali ne obstaja You can't add yourself as a friend! When trying to add your own Tox ID as friend Samega sebe ne moreš dodati med stike! Open contact list Odpri imenik Couldn't open file Datoteke ni mogoče odpreti Couldn't open the contact file Error message when trying to open a contact list file to import Ni bilo mogoče odpreti datoteke kontakta Invalid file Neveljavna datoteka We couldn't find any contacts to import in this file! Ni nobenega kontakta v tej datoteki! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description 76 šestnajstiških znakov ali name@example.com Message The message you send in friend requests Sporočilo Open Button to choose a file with a list of contacts to import Odpri Send friend requests Pošlji prošnje prijateljstva %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 tukaj. Tox me maybe? Import a list of contacts, one Tox ID per line Vnesite imenik, en Tox ID na linijo Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Pripravljen na vnos %n kontakta(-ov), kliknite pošlji za potrditi Pripravljen na vnos %n kontaktov, kliknite pošlji za potrditi Pripravljen na vnos %n kontaktov, kliknite pošlji za potrditi Pripravljen na vnos %n kontaktov, kliknite pošlji za potrditi Import contacts Vnesi kontakte AdvancedForm Advanced Napredno Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really da not ne IMPORTANT NOTE POMEMBNA OPOMBA Reset settings Ponastavi nastavitve All settings will be reset to default. Are you sure? Vse nastavitve se bodo ponastavile na privzete. Ste prepričani? Yes Da No Ne Call active popup title Klic je aktiven You can't disconnect while a call is active! popup text Ne moreš se odjaviti med klicem! Save File Shrani datoteko Logs (*.log) Dnevniki (* .log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Shrani nastavitve v trenutno mapo namesto običajno konfiguracijsko mapo Make Tox portable Naredi Tox prenosen Reset to default settings Ponastavi na začetne nastavitve Portable Prenosni Connection Settings Nastavitve povezave Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Omogoči IPv6 (priporočeno) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP Omogoči UDP (priporočeno) Proxy type: Vrsta proxy-ja: Address: Text on proxy addr label Naslov: Port: Text on proxy port label Vrata: None Brez SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Ponovno poveži Debug Razhroščevanje Export Debug Log Dnevnik razhroščevanja Copy Debug Log Kopiraj dnevnik razhroščevanja Enable LAN discovery ChatForm Send a file Pošlji datoteko qTox wasn't able to open %1 qTox ni mogel odpreti %1 %1 calling %1 te kliče Call with %1 ended. %2 Klic s osebo %1 je končan. %2 Call duration: Trajanje klica: Unable to open Nemogoče odpreti Bad idea Slaba ideja Calling %1 Kličem %1 Failed to open temporary file Temporary file for screenshot Ni uspelo odpreti začasne datoteke qTox wasn't able to save the screenshot qTox ni uspel shraniti screenshot %1 is typing %1 piše Copy Kopiraj You're trying to send a sequential file, which is not going to work! Skušate poslati zaporedno datoteko, ne bo delalo! %1 is now %2 e.g. "Dubslow is now online" %1 je zdaj %2 Call with %1 ended unexpectedly. %2 Klic z %1 se je končal nepričakovano. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Ni mogoče začeti zvočnega klica Start audio call Začni glasovni pogovor End audio call Končaj glasovni pogovor Cancel audio call Prekini glasovni klic Accept audio call Sprejmi glasovni klic Can't start video call Ni mogoče začeti video klic Start video call Začni video pogovor End video call Končaj video pogovor Cancel video call Prekini video klic Accept video call Sprejmi video klic Sound can be disabled only during a call Unmute call Vklopi zvok Mute call Izklopi zvok Microphone can be muted only during a call Mikrofon se lahko izključi samo med klicem Unmute microphone Vklopi mikrofon Mute microphone Izklopi mikrofon ChatLog Copy Kopiraj Select all Izberi vse pending čakanje ChatTextEdit Type your message here... Vnesi sporočilo tukaj... CircleWidget Rename circle Menu for renaming a circle Preimenuj krog Remove circle Menu for removing a circle Odstrani krog Open all in new window Odpri vse v novem oknu Core /me offers friendship, "%1" /me ponuja prijateljstvo, "%1" Invalid Tox ID Error while sending friendship request Neveljaven ID Tox You need to write a message with your request Error while sending friendship request Morate napisati sporočilo z vašo prošnjo Your message is too long! Error while sending friendship request Vaše sporočilo je predolgo! Friend is already added Error while sending friendship request Stik je že dodan Groupchat %1 DesktopNotify New message Novo sporočilo Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Nastavitve 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 Čas:10:10 Filename Ime datoteke Waiting to send... file transfer widget Čakanje na pošiljanje... Accept to receive this file file transfer widget Sprejmi to datoteko Location not writable Title of permissions popup Lokacija zaščitena You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nimaš dovoljenja za pisanje na to lokacijo. Prosim izberi drugo ali prekliči shranjevanje. Save a file Title of the file saving dialog Shrani datoteko Paused file transfer widget Premor Resuming... file transfer widget Nadaljevanje... Open file Odpri datoteko Open file directory Odprite mapo Pause transfer Ustavite prenos Cancel transfer Prekliči prenos Resume transfer Nadaljuj prenos Accept transfer Sprejmi prenos Remote Paused file transfer widget FilesForm Downloads Prenosi Uploads Poslane datoteke Transferred Files "Headline" of the window Prenesene datoteke FriendListWidget Today Danes Yesterday Včeraj Last 7 days Zadnji 7 dni This month Zadnji mesec Older than 6 Months Starejši kot 6 mesecev Never Nikoli FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Zahteve za stike Someone wants to make friends with you Nekdo te hoče dodati med stike User ID: Uporabniški ID: Friend request message: Sporočilo: Accept Accept a friend request Sprejmi Reject Reject a friend request Zavrni FriendWidget Invite to group Menu to invite a friend to a groupchat Povabi v skupino Set alias... Nastavi vzdevek... Auto accept files from this friend context menu entry Samodejno sprejmi datoteke od te osebe Remove friend Menu to remove the friend from our friendlist Odstrani stik Choose an auto accept directory popup title Izberi mapo za avtomatsko sprejemanje datotek Open chat in new window Odpri klepet v novem oknu Remove chat from this window Zapri klepet v tem oknu To new group V novi skupini Invite to group '%1' Povabi v skupino '%1' Move to circle... Menu to move a friend into a different circle Premakni v krog... To new circle V novi krog Remove from circle '%1' Odstrani iz kroga '%1' Move to circle "%1" Premakni v krog '%1' Show details Pokaži podrobnosti New message Novo sporočilo Online Dosegljiv Away Odsoten Busy Zaseden Offline Nedosegljiv GeneralForm General Splošno Choose an auto accept directory popup title Izberi mapo za avtomatsko sprejemanje datotek GeneralSettings General Settings Splošne nastavitve The translation may not load until qTox restarts. Prevod se morda ne bo naložil dokler se ne bo qTox ponovno naložil. Language: Jezik: Show system tray icon Pokaži ikono v orodni vrstici Enable light tray icon. toolTip for light icon setting Omogoči svetlo ikono. Light icon Svetla ikona qTox will start minimized in tray. toolTip for Start in tray setting qTox se bo zagnal v orodni vrstici. Start in tray Zaženi v orodni vrstici After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Po kliku na izhod (X) se bo qTox pomanjšal v orodno vrstico, namesto da bi se čisto zaprl. Close to tray Preveri Zapri v ozadje After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Po kliku na pomanjšanje (_) se bo qTox pomanjšal v orodno vrstico, namesto da bi ostal med programi. Minimize to tray Pomanjšaj v ozadje Autostart Samodejno zaženi Set where files will be saved. Nastavi kje bodo datoteke shranjene. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Tole lahko nastaviš za vsak stik posebaj, tako da uporabiš desni miškin klik na njih. Autoaccept files Samodejno sprejmi datoteke Set to 0 to disable Izberi 0 za izklop Your status is changed to Away after set period of inactivity. Tvoje stanje bo spremenjeno na Odsotno ob neaktivnosti. Auto away after (0 to disable): Odsoten po X minutah (0 za izklop): Show contacts' status changes Pokaži spremembe statusa stika Start qTox on operating system startup (current profile). Odpri qTox na zagon operacijskega sistema (sedanji profil). Default directory to save files: Privzeta mapa za shranjevanje datotek: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Pošlji sporočilo Smileys Smajliji Send file(s) Pošlji datoteke Save chat log Shrani zgodovino pogovorov Clear displayed messages Skrij prikazana sporočila Cleared Skrito Send a screenshot Pošlji posnetek zaslona Quote selected text Citiraj izbrano besedilo Copy link address Kopiraj naslov povezave Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Naloži zgodovino pogovorov... Export to file Prenesi v datoteko GenericNetCamView Tox video Tox video Show Messages Prikaži sporočila Hide Messages Skrij sporočila Full Screen Toggle video preview Mute audio Mute microphone Izklopi mikrofon End video call Končaj video pogovor Exit full screen GroupChatForm %1 has set the title to %2 %1 je spremenil naslov v %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Skupine Create new group Ustvari novo skupino Group invites Vabila na skupine GroupInviteWidget Invited by %1 on %2 at %3. Povabljen od %1 na %2 ob %3. Join Pridruži se Decline Zavrni GroupWidget Set title... Nastavi naslov... Quit group Menu to quit a groupchat Zapusti skupino Open chat in new window Odpri klepet v novem oknu Remove chat from this window Zapri klepet v tem oknu %n user(s) in chat Number of users in chat New Message Online Dosegljiv IdentitySettings Public Information Javne informacije Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Ta koda pove drugim Tox klientom kako te kontaktirati. Deli jo z ljudmi, ki jih želiš dodati med stike. Your Tox ID (click to copy) Tvoj Tox ID (klikni za kopiranje) Rename rename profile button Preimenuj Export export profile button Shrani Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Omogoči ti da shraniš tvoj Tox profil v datoteko. Profil ne vsebuje tvoje zgodovine pogovorov. Delete delete profile button Izbriši This QR code contains your Tox ID. You may share this with your friends as well. Ta QR koda vsebuje vaš Tox ID. Lahko jo delite s prijatelji namesto kode. Save image Shrani sliko Copy image Kopiraj sliko Server Strežnik Hide my name from the public list Skrij moje ime iz javnega seznama Register Registriraj se Your password Geslo Update Posodobi Profile Profil Rename profile. tooltip for renaming profile button Preimenuj profil. Delete profile. delete profile button tooltip Izbriši profil. Go back to the login screen tooltip for logout button Pojdi nazaj na prijavni zaslon Logout import profile button Odjava Remove password Odstrani geslo Change password Spremeni geslo Register on ToxMe Registrirajte se na ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Ime za ToxMe storitev. Optional. Something about you. Or your cat. Tooltip for the Biography text. Dodatno. Nekaj o vas in o vaši mački. Optional. Something about you. Or your cat. Tooltip for the Biography field. Dodatno. Nekaj o vas in o vaši mački. ToxMe service to register on. ToxMe storitev za registracijo. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Če ni določeno, ToxMe vnosi so javno vidni. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Odstranjevanje gesla in šifriranja iz vašega profila. Name input Vnos imena Name visible to contacts Ime vidno v imenik Status message input Sporočilo o stanju Status message visible to contacts Statusno sporočilo vidno v imenik Your Tox ID Vaš Tox ID Save QR image as file Shranite QR sliko kot datoteko Copy QR image to clipboard Kopiranje QR slike v zapiskih ToxMe username to be shown on ToxMe ToxMe uporabniško ime, ki bo prikazano na ToxMe Optional ToxMe biography to be shown on ToxMe Dodatni ToxMe življenjepis, ki bo prikazan na ToxMe ToxMe service address ToxMe naslov Visibility on the ToxMe service Prepoznavnost na ToxMe storitev Password Geslo Update ToxMe entry Posodobitev podatkov ToxMe Rename profile. Preimenuj profil. Delete profile. Izbriši profil. Export profile Shrani profil Remove password from profile Odstrani geslo iz profila Change profile password Spremenite geslo profila My name: Moje ime: My status: Moje stanje: My username Moje uporabniško ime My biography Moj življenjepis My profile Moj profil LoadHistoryDialog Load History Dialog Naloži zgodovino pogovorov Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Uporabniško ime: Password: Geslo: Confirm: Potrdi: Password strength: %p% Moč gesla: %p% Create Profile Ustvarite Profil If the profile does not have a password, qTox can skip the login screen Če profil nima gesla, qTox preskoči prijavni zaslon Load automatically Naloži samodejno Import Vstavi Load Potrdi izbiro New Profile Nov profil Load Profile Nalaganje profila Couldn't create a new profile Nemogoče ustvariti nov profil The username must not be empty. Uporabniško ime ne sme biti prazno. The password must be at least 6 characters long. Geslo mora vsebovati vsaj 6 znakov. The passwords you've entered are different. Please make sure to enter same password twice. Gesla, ki ste vnesli, sta različna. Prosimo, da vnesete dvakrat pravilno geslo. A profile with this name already exists. Profil s tem imenom že obstaja. Password protected profiles can't be automatically loaded. Z geslom zaščitenih profilov ni mogoče samodejno naložiti. Couldn't load profile Ni mogoče naložiti profil There is no selected profile. You may want to create one. Ni nobenega izbranega profila. Lahko ustvarite enega. Couldn't load this profile Ni bilo mogoče naložiti ta profil This profile is already in use. Ta profil je že v uporabi. Wrong password. Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Naloži profil Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Tvoje ime Your status Tvoje stanje Add friends Dodaj stike Create a group chat Ustvari skupen pogovor View completed file transfers Pokaži končane prenose datotek Change your settings Spremeni nastavitve Close Zapri ... Open profile Open profile page when clicked Status message input Sporočilo o stanju Set your status message that will be shown to others Status Stanje Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings Nastavitve Open Settings Nexus View OS X Menu bar Window OS X Menu bar Minimize OS X Menu bar Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Zasebnost Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Tvoji stiki bodo lahko videl kdaj tipkaš. Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Zgodovina sporočil je še vedno v izdelavi. Vrsta datotek za shranjevanje se lahko spremeni in podatki se izgubijo. Send typing notifications Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Privacy Zasebnost BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox Toxanje na qToxu ProfileForm Current profile: Remove Choose a profile picture Izberi sliko profila Error Napaka Unable to open this file. Unable to read this image. The supplied image is too large. Please use another image. Rename "%1" renaming a profile Preimenuj "%1" Couldn't rename the profile to "%1" Location not writable Title of permissions popup Lokacija zaščitena You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nimaš dovoljenja za pisanje na to lokacijo. Prosim izberi drugo ali prekliči shranjevanje. Failed to copy file Napaka pri kopiranju datoteke The file you chose could not be written to. Zapis v to datoteko ni mogoč. Really delete profile? deletion confirmation title Are you sure you want to delete this profile? deletion confirmation text Files could not be deleted! deletion failed title Save save qr image Save QrCode (*.png) save dialog filter Nothing to remove Your profile does not have a password! Really delete password? deletion confirmation title Please enter a new password. Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Registriraj se Update Posodobi Change password button text Spremeni geslo Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists Profil že obstaja A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Shrani profil Tox save file (*.tox) save dialog filter Tox datoteka (*.tox) The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter Slike (%1) ProfileImporter Import profile import dialog title Naloži profil Tox save file (*.tox) import dialog filter Tox datoteka (*.tox) Ignoring non-Tox file popup title Ignoriraj ne-Tox datoteke Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title Profil že obstaja A profile named "%1" already exists. Do you want to erase it? import confirm text Profil z imenom "%1" že obstaja. Ga želiš izbrisati? File doesn't exist Profile doesn't exist Profile imported Profil dodan %1.tox was successfully imported %1.tox je bila uspešno dodana QApplication Ok Cancel Prekliči Yes Da No Ne LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend Nemogoče dodati kontakta %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend Samega sebe ne moreš dodati med stike! QObject Tox URI to parse Preveri Tox URI za interpretirati Starts new instance and loads specified profile. Odpre novo okno z določenim profilom. profile profil Default Privzeto Blue Modro Olive Olivno Red Rdeče Violet Violično Incoming call... Prihajujoči klic... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 tukaj. Tox me maybe? Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error Napaka qTox couldn't open your chat logs, they will be disabled. None No camera device set Brez Desktop Desktop as a camera input for screen sharing Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status dosegljiv away contact status odsoten busy contact status zaseden offline contact status nedosegljiv blocked contact status RemoveFriendDialog Remove friend Odstrani stik Also remove chat history Remove Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Nastavitve Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Nastavi geslo Confirm: Potrdi: Password: Geslo: Password strength: %p% Moč gesla: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Dodaj stik Do you want to add %1 as a friend? Želiš dodati %1 med stike? User ID: Uporabniški ID: Friend request message: Sporočilo: Send Send a friend request Pošlji Cancel Don't send a friend request Prekliči UserInterfaceForm None Brez User Interface UserInterfaceSettings Chat Pogovor Base font: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message Novo sporočilo Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Tvoj seznam stiko bo prikazan zgoščeno. Compact contact list Zgoščen prikaz stikov Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Uporabi smajlije Smiley Pack: Text on smiley pack label Smajli paket: Emoticon size: Velikost smajlijev: px px Theme Tema Style: Stil: Theme color: Barva teme: Timestamp format: Oblika časa: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Predvajaj zvok Play sound while Busy Predvajanje zvoka, ko si zaseden Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Dosegljiv Away Button to set your status to 'Away' Odsoten Busy Button to set your status to 'Busy' Zaseden toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Toxcore se ni uspešno zagnal s tvojimi proxy nastavitvami. qTox ne more delovati, prosim spremeni nastavitve in ponovno zaženi. Executable file popup title Zagonska datoteka You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Želiš odpreti zagonsko datoteko. Te datoteke so lahko nevarne in škodijo računalniku. Želiš vseeno odpreti? Couldn't request friendship Zahteva za stik ni bila uspešna Message failed to send Sporočilo ni bilo poslano Status Stanje toxcore failed to start, the application will terminate after you close this message. Your name Tvoje ime Groupchat #%1 Create new group... Add new circle... %n New Friend Request(s) %n New Group Invite(s) By Name By Activity All Online Dosegljiv Offline Nedosegljiv Friends Groups Skupine Search Contacts Logout Tray action menu to logout user Odjava Exit Tray action menu to exit tox Filter... File Edit Contacts Change Status Edit Profile Log out Add Contact... Next Conversation Previous Conversation Show Tray action menu to show qTox window Add friend title of the window Dodaj stik Group invites title of the window Vabila na skupine File transfers title of the window Prenosti datotek Settings title of the window Nastavitve My profile title of the window Moj profil Failed to send file "%1" Pošiljanje datoteke "%1" ni uspelo File sent sent you a friend request. invites you to join a group. qTox/translations/sr.ts000066400000000000000000003553001415623743500155460ustar00rootroot00000000000000 AVForm Audio/Video Звук/Видео Default resolution подразумевана резолуција Disabled Онемогућено Select region Изаберите област Screen %1 Екран %1 Audio Settings Подешавање звука Gain Појачање Playback device Уређај за репродукцију Use slider to set volume of your speakers. Помоћу клизача подесите зачину звучника. Capture device Уређај за хватање Volume Јачина Video Settings Подешавање видеа Video device Видео уређај Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Подесите резолуцију камере. Што је вредност већа, бољи је квалитет видеа који ваши пријатељи примају. Имајте у виду да већи квалитет захтева и бољу везу са интернетом. Некад ваша веза може бити недовољна да подржи већи квалитет видеа, што може узроковати проблеме са видео позивима. Resolution Резолуција Rescan devices Поново скенирај уређаје Test Sound Проба звука Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Омогућава експериментално звучно прочеље са потискивањем еха, захтева да поново покренете qTox. Enable experimental audio backend Омогући експериментално звучно прочеље Audio quality Квалитет звука Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Емитовани квалитет звука. Умањите поставке уколико ваша веза није довољно јака или желите да смањите употребу интернета. High (64 kbps) висок (64 kbps) Medium (32 kbps) средњи (32 kbps) Low (16 kbps) низак (16 kbps) Very low (8 kbps) врло низак (8 kbps) Threshold AboutForm About О програму Original author: %1 Изворни аутор: %1 You are using qTox version %1. Користите qTox верзије %1. Commit hash: %1 Хеш предаје: %1 toxcore version: %1 верзија toxcore-а: %1 Qt version: %1 верзија Qt-а: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Листа ознатих проблема се може наћи на нашем Github %1. Уколико откријете грешку или безбеносну рањивост у qTox-у, молимо пријавите је у складу са смерницама на нашем вики чланку %2 . Click here to report a bug. Кликните овде за пријаву грешке. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Погледајте целокупан списак %1 на Гитхабу bug-tracker Replaces `%1` in the `A list of all known…` буболовцу Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` „Писање корисних пријава грешака“ contributors Replaces `%1` in `See a full list of…` доприносиоца AboutFriendForm Dialog Дијалог username орисничко име status message статусна порука Used aliases: Коришћени алијаси: HISTORY OF ALIASES ИСТОРИЈАТ АЛИЈАСА Automatically accept files from contact if set Уколико је постављено аутоматски прихвата фајлове од контаката Auto accept files Аутоматски примај фајлове Default directory to save files: Подразумевана фасцикла за чување фајлова: Auto accept for this contact is disabled Аутоматско прихватање фајлова од овог контакта је онемогућено Auto accept call: Аутоматсски прихвати позив: Manual Приручник Audio Звук Audio + Video Звук и видео Automatically accept group chat invitations from this contact if set. Уколико је задано, аутоматски прихавти позиве за групно ћаскање од овог контакта. Auto accept group invites Аутоматски прихвати позиве у групу Remove history (operation can not be undone!) Уклони историјат (тадња не може бити опозвана!) Notes Забелешке Input field for notes about the contact Поље уноса за белешке о контакту You can save comment about this contact here. Овде можете сачувати коментаре о контакту. History removed Историјат је уклоњен Choose an auto accept directory popup title Изаберите фасциклу за аутоматски пријем <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Потврда Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Верзија License Лиценца Authors Аутори Known Issues Познати проблеми Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Додај пријатеље Invalid Tox ID format Неисправан формат ToxID Send friend request Пошаљи захтев за пријатељство Add a friend Додај пријатеља Friend requests Захтеви за пријатељство Accept Прихвати Reject Одбиј Couldn't add friend Не могу да нађем пријатеља Tox ID, either 76 hexadecimal characters or name@example.com ToxID, или 76 хексадецималних знакова, или име@example.com Type in Tox ID of your friend Унесите ToxID вашег пријатеља Friend request message Порука уз захтев за прјатељство Type message to send with the friend request or leave empty to send a default message Унесите поруку за послати уз захтев за пријатељство или оставите празно како би послали основну поруку %1 Tox ID is invalid or does not exist Toxme error %1 ToxID не постоји You can't add yourself as a friend! When trying to add your own Tox ID as friend Не можете додати себе као пријатеља! Open contact list Отвори списак контаката Couldn't open file Не могу да отворим фајл Couldn't open the contact file Error message when trying to open a contact list file to import Не могу да отворим фајл са контактима Invalid file Неиспрраван фајл We couldn't find any contacts to import in this file! Не нађох нијеан контакт за увести у овом фајлу! Tox ID Tox ID of the person you're sending a friend request to ToxID either 76 hexadecimal characters or name@example.com Tox ID format description или 76 хексадецималних знакова, или име@example.com Message The message you send in friend requests Порука Open Button to choose a file with a list of contacts to import Отвори Send friend requests Пошаљи захтеве за пријатељство %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 овде! Tox-уј са мном? Import a list of contacts, one Tox ID per line Увезите списак контаката, један Tox ID по линији Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Спреман да увезем %n контакт, кликните на слање за потврду Спреман да увезем %n контакта, кликните на слање за потврду Спреман да увезем %n контакта, кликните на слање за потврду Import contacts Увоз контаката AdvancedForm Advanced Напредно Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Уколико %1 шта радите, молимо %2 мењајте ништа овде. Начињене измене могу довести до проблема у qTox-у, чак и до губитка података попут историјата. really нисте сигурни not не IMPORTANT NOTE ВАЖНА НАПОМЕНА Reset settings Ресетуј поставке All settings will be reset to default. Are you sure? Све поставке ће бити враћене на подразумеване. Да ли сте сигурни? Yes Да No Не Call active popup title Позив у току You can't disconnect while a call is active! popup text Не можете се развезати током трајања позива! Save File Сними фајл Logs (*.log) Дневници (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Сачувај поставке у радну фасциклу уместо уобичајене фасцикле поставки Make Tox portable Учини Tox преносивим Reset to default settings Врати на основне поставке Portable Преносивост Connection Settings Подешавање везе Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Омогући IPv6 (препоручено) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Онемогућавање овога дозвољава да , нпр., tox-ујете преко Тора. Међутим, ово оптерећује Tox мрежу, тако да онемогућите само кад је неопходно. Enable UDP (recommended) Text on checkbox to disable UDP Омогући UДP (препоручено) Proxy type: Врста проксија: Address: Text on proxy addr label Адреса: Port: Text on proxy port label Порт: None ниједан SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Поново се повежи Debug Исправљање грешака Export Debug Log Извези дневник исправљања грешака Copy Debug Log Уможи дневник исправљања грешака Enable LAN discovery ChatForm Send a file Пошаљи фајл qTox wasn't able to open %1 qTox не може да отвори %1 Unable to open Не могу да отворим Bad idea Лоша идеја %1 calling %1 зове Calling %1 Позивам %1 Failed to open temporary file Temporary file for screenshot Неуспело отварање привременог фајла qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox не успе да сачува снимак Call with %1 ended. %2 Звање %1 заврши. %2 Call duration: Трајање позива: %1 is typing %1 куца Copy Уможи You're trying to send a sequential file, which is not going to work! Покушавате да пошаљете фајл у деловима што неће радити! %1 is now %2 e.g. "Dubslow is now online" %1 је сад %2 Call with %1 ended unexpectedly. %2 Звање %1 неочекивано заврши. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Не могу да започнем аудио позив Start audio call Започни аудио позив End audio call Заврши аудио позив Cancel audio call Откажи аудио позив Accept audio call Прихвати аудио позив Can't start video call Не могу да започнем видео позив Start video call Започни видео позив End video call Заврши видео позив Cancel video call Откажи видео позив Accept video call Прихвати видео позив Sound can be disabled only during a call Звук се може искључити само током позива Unmute call Mute call Утишај позив Microphone can be muted only during a call Микрофон се може утишати само током позива Unmute microphone Mute microphone Утишај микрофон ChatLog Copy Умножи Select all Изабери све pending на чекању ChatTextEdit Type your message here... Овде унесите вашу поруку... CircleWidget Rename circle Menu for renaming a circle Преименуј круг Remove circle Menu for removing a circle Укони круг Open all in new window Отвори ссве у новом прозору Core /me offers friendship, "%1" /me нуди пријатељство, „%1“ Invalid Tox ID Error while sending friendship request Неиsправан Tox ID You need to write a message with your request Error while sending friendship request Морате написати поруку уз ваш захтев за пријатељство Your message is too long! Error while sending friendship request Ваша порука је предугачка! Friend is already added Error while sending friendship request Пријатељ је већ додат Groupchat %1 DesktopNotify New message Нова порука Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen Образац 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen ETA:10:10 Filename Ausgelassen Име фајла Waiting to send... file transfer widget Чекам на слање... Accept to receive this file file transfer widget Прихати пријем овог фајла Location not writable Title of permissions popup Локација није уписива You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Немате дозволе за писање на овој локацији. Изаберите другу, или откажите у дијалогу снимања. Resuming... file transfer widget Настављам... Cancel transfer Откажи пренос Pause transfer Паузирај пренос Paused file transfer widget Паузиран Open file Отвори фајл Open file directory Отвори фасциклу фајла Resume transfer Настави пренос Accept transfer Прихвати пренос Save a file Title of the file saving dialog Сачувај фајл Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Пребачени фајлови Downloads Пријеми Uploads Слања FriendListWidget Today Данас Yesterday Јуче Last 7 days Протеклих 7 дана This month Овог месеца Older than 6 Months Старије од 6 месеци Never Никада FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Захтев за пријатељство Someone wants to make friends with you Неко жели да вам буде пријатељ User ID: Кориснички ID: Friend request message: Порука захтева за пријатељство: Accept Accept a friend request Прихвати Reject Reject a friend request Одбиј FriendWidget Invite to group Menu to invite a friend to a groupchat Позови у групу Move to circle... Menu to move a friend into a different circle Помери у круг... To new circle У нови круг Remove from circle '%1' Уклони из круга „%1 “ Move to circle "%1" Помери у круг „%1“ Open chat in new window Отвори ћакање у новом прозору Remove chat from this window Уклони ћаскање из овог прозора To new group У нову групу Invite to group '%1' Позови у групу „%1“ Set alias... Постави алијас... Auto accept files from this friend context menu entry Аутоматски прихвати фајлове од овог пријатеља Remove friend Menu to remove the friend from our friendlist Уклони пријатеља Show details Прикажи детаље Choose an auto accept directory popup title Изаберте фасциклу за аутоматски пријем New message Нова порука Online На вези Away Одсутан Busy Заузет Offline Ausgelassen Ван везе GeneralForm General Опште Choose an auto accept directory popup title Изаберите фасциклу за аутоматски пријем GeneralSettings General Settings Опште поставке The translation may not load until qTox restarts. Превод се можда неће учитати пре поновног покретања qTox-а. Language: Језик: Show system tray icon Приказуј икону системске касете Enable light tray icon. toolTip for light icon setting Светла икона касете. Light icon Светла икона qTox will start minimized in tray. toolTip for Start in tray setting qTox ће се покретати смањен у касету. Start in tray Покрени у касети After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting По клику на затвори (X) qTox ће се минимизовати у касету уместо да се затвори. Close to tray Затвори у касету After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting По клику на минимизуј (_) qTox ће се минимизовати у касету уместо системске траке задатака. Minimize to tray Минимизуј у касету Autostart Аутоматско покретање Set where files will be saved. Изаберите где чувати фајлове. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Ово можете подесити посебно за сваког пријатеља десним кликом. Autoaccept files Аутоматски пријем фајлова Set to 0 to disable Поставите на 0 да онемогућите Your status is changed to Away after set period of inactivity. Ваш статус се мења на Одсутан након задатог периода неактивности. Auto away after (0 to disable): Аутоматска одсутност након (0 да онемогућите): Show contacts' status changes Прикажи промене сатуса контаката Start qTox on operating system startup (current profile). Покрени qTox са оперативним системом (текући профил). Default directory to save files: Позразумевана фасцикла за чување фајлова: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Пошаљи поруку Smileys Емотикони Send file(s) Пошаљи фајл(ове) Send a screenshot Пошаљи снимак екрана Save chat log Сачувај дневник ћаскања Clear displayed messages Очисти приказане поруке Cleared Очишћено Quote selected text Цитирај изабрани текст Copy link address Копирај адресу везе Confirmation Потврда You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Учитај историјат ћаскања... Export to file Извези у фајл GenericNetCamView Tox video Токс видео Show Messages Прикажи поруке Hide Messages Сакриј поруке Full Screen Toggle video preview Mute audio Mute microphone Утишај микрофон End video call Заврши видео позив Exit full screen GroupChatForm %1 has set the title to %2 %1 постави наслов на %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Групе Create new group Направи нову групу Group invites Позивнице у групу GroupInviteWidget Invited by %1 on %2 at %3. Позва %1 %2 у %3. Join Придружи се Decline Одбиј GroupWidget Set title... Постави наслов... Open chat in new window Отвори ћаскање у новом прозору Remove chat from this window Уклони ћаскање из овог прозора Quit group Menu to quit a groupchat Напусти групу %n user(s) in chat Number of users in chat New Message Online На вези IdentitySettings Public Information Јавне информације Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Ова хрпа знакова говори другим Tox клијентима како да вас контактирају. Поделите је са пријатељима да би комуницирали. Your Tox ID (click to copy) Ваш Tox ID (кликните да копирате) Profile Профил Rename profile. tooltip for renaming profile button Преименуј профил. Go back to the login screen tooltip for logout button Врати се на екран за пријаву Logout import profile button Одјава Remove password Уклони лозинку Change password Измени лозинку This QR code contains your Tox ID. You may share this with your friends as well. Овај QR код садржи ваш Tox ID. Можете га делити и вашим пријатељима. Save image Сачувај слику Copy image Копирај слику Rename rename profile button Преименуј Delete profile. delete profile button tooltip Обриши профил. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Омогућава вам да извезете ваш Tox профил у фајл. Профил не садржи ваш историјат. Export export profile button Извези Delete delete profile button Обриши Server Сервер Hide my name from the public list Сакриј моје име са јавне листе Register Региструј се Your password Ваша лозинка Update Ажурирај Register on ToxMe Региструј се на ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Име на сервису ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Опционо. Нешто о вама. Или вашој мачки. Optional. Something about you. Or your cat. Tooltip for the Biography field. Опционо. Нешто о вама. Или вашој мачки. ToxMe service to register on. ToxMe сервис на који се треба регистровати. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ако није означено, ToxMe уноси су јавни. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Уклања лозинку и шифровање из вашег профила. Name input Унос имена Name visible to contacts Име видљиво контактима Status message input Унос статусне поруке Status message visible to contacts Статусна порука је видљива контактима Your Tox ID Ваш Tox ID Save QR image as file Сачувај QR слику као фајл Copy QR image to clipboard Умножи QR слику у оставу ToxMe username to be shown on ToxMe ToxMe корисничко име које се приказује на ToxMe Optional ToxMe biography to be shown on ToxMe Опциона ToxMe биографија која се приказује на ToxMe ToxMe service address Адреса ToxMe сервиса Visibility on the ToxMe service Видљивост на ToxMe сервису Password Лозинка Update ToxMe entry Ажурирај ToxMe унос Rename profile. Преименуј профил. Delete profile. Обриши профил. Export profile Извези профил Remove password from profile Уколони лозинку профила Change profile password Промени лозинку профила My name: Моје име: My status: Мој статус: My username Моје корисничко име My biography Моја биографија My profile Мој профил LoadHistoryDialog Load History Dialog Дијалог учитавања историјата Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Корисничко име: Password: Лозинка: Confirm: Потврда: Password strength: %p% Јачина лозинке: %p% Create Profile Направи профил If the profile does not have a password, qTox can skip the login screen Уколико профил нема лозинку, qTox ће прескочити екран за пријаву Load automatically Аутоматски учитај Load Учитај Load Profile Учитај профил New Profile Нови профил Couldn't create a new profile Не могу да направим нови профил The username must not be empty. Корисничко име не сме бити празно. The password must be at least 6 characters long. Лозинка мора имати најмање 6 знакова. The passwords you've entered are different. Please make sure to enter same password twice. Лозинке које сте унели се разликују. Постарајте се да унесете исту лозинку двапут. A profile with this name already exists. Профил са овим именом већ постоји. Password protected profiles can't be automatically loaded. Профили заштићени лозинком се не могу учитати аутоматски. Couldn't load profile Не могу да учитам профил There is no selected profile. You may want to create one. Нема изабраног профила. Вероватно желите да направите један. Couldn't load this profile Не могу да учитам овај профил This profile is already in use. Овај профил је већ у употреби. Wrong password. Погрешна лозинка. Import Увоз Username input field Поље уноса корисничког имена Password input field, you can leave it empty (no password), or type at least 6 characters Поље уноса лозинке, можете га оставити празним (без лозинке), или унесите најмање 6 знакова Password confirmation field Поље потврде лозинке Create a new profile button Дугме за прављење новог профила Profile list Листа профила List of profiles Листа профила Password input Унос лозинке Load automatically checkbox Кућица за аутоматско учитавање Import profile Увоз профила Load selected profile button Дугме увоза изабраног профила New profile creation page Страница прављења новог профила Loading existing profile page Страница учитавања постојећег профила MainWindow Your name Ваше име Your status Ваш статус ... Ausgelassen ... Add friends Додај пријатеље Create a group chat Направи групно ћаскање View completed file transfers Прикажи завршене преносе фајлова Change your settings Измените ваше поставке Close Затвори Open profile Отвори профил Open profile page when clicked Отвори профил по клику Status message input Унос статусне поруке Set your status message that will be shown to others Поставите статусну поруку видљиву другима Status статус Set availability status Поставите доступност Contact search Претрага контаката Contact search input for known friends Унос претраге контаката за познате пријатеље Sorting and visibility Ређање и видљивост Set friends sorting and visibility Постави ређање пријатеља и видљивост Open Add friends page Отвори страницу Додај пријатеље Groupchat Групно ћаскање Open groupchat management page Отвори страницу управљања групним ћаскањем File transfers history Историјат преноса фајлова Open File transfers history Отвара историјат преноса фајлова Settings Подешавање Open Settings Отвори поставке Nexus View OS X Menu bar Приказ Window OS X Menu bar Прозор Minimize OS X Menu bar Минимизуј Bring All to Front OS X Menu bar Стави све испред Exit Fullscreen Напусти цео екран Enter Fullscreen Пређи на цео екран NotificationEdgeWidget Unread message(s) %n непрочитана порука %n непрочитане поруке %n непрочитане поруке PasswordEdit CAPS-LOCK ENABLED УКЉУЧЕН CAPS-LOCK PrivacyForm Privacy Приватност Confirmation Потврда Do you want to permanently delete all chat history? Желите ли да трајно обришете историјат ћаскања? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Ваши пријатељи ће видети када куцате. Send typing notifications Шаљи обавештења о куцању Keep chat history Задржи историју ћаскања NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam је део вашег Tox ID. Уколико вас спамују захтевима за пријатељство, требало би да измените ваш NoSpam. Други неће моћи да вас додају путем старог ID, али ћете задржати све тренутне пријатеље. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam је део вашег Tox ID. Уколико вас спамују захтевима за пријатељство, измените ваш NoSpam. Generate random NoSpam Направи произвољан NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Задржавање историјата ћаскања је још у развоју. Промене у формату снимања су могуће, што може довести до губитка података. Privacy Приватност BlackList Црна листа Filter group message by group member's public key. Put public key here, one per line. Филтер групних порука према јавном кључу чланова групе. Јавне кључеве унесите овде, један по линији. Profile Failed to derive key from password, the profile won't use the new password. Неуспело извођење кључа из лозинке, профил неће користити нову лозинку. Couldn't change password on the database, it might be corrupted or use the old password. Не могу да променим лозинку базе, можда је оштећена или користите стару лозинку. Toxing on qTox Tox-ује путем qTox-а ProfileForm Choose a profile picture Изаберите слику профила Error Грешка Rename "%1" renaming a profile Преименовање „%1“ Unable to open this file. Не могу да отворим овај фајл. Current profile: Текући профил: Remove Уклони Unable to read this image. Не могу да читам ову слику. The supplied image is too large. Please use another image. Достављена слика је превелика. Молимо користите другу. Couldn't rename the profile to "%1" Не могу да преименујем профил у „%1“ Location not writable Title of permissions popup Локација није уписива You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Немате дозволу уписа на овој локацији. Изаберите другу, или откажите снимање у дијалогу. Failed to copy file Неуспело копирање фајла The file you chose could not be written to. Не може се писати у изабрани фајл. Really delete profile? deletion confirmation title Заиста обрисати профил? Nothing to remove Нема се шта уклонити Your profile does not have a password! Ваш профил нема лозинку! Really delete password? deletion confirmation title Заиста обрисати лозинку? Please enter a new password. Унесите нову лозинку. Are you sure you want to delete this profile? deletion confirmation text Стварно сте сигурни да желите да уклоните овај профил? Save save qr image Чување Save QrCode (*.png) save dialog filter Чување QrCode-a (*.png) Files could not be deleted! deletion failed title Фајлови се не могу обрисати! Register (processing) Регистрација (у току) Update (processing) Ажурирање (у току) Done! Готово! Account %1@%2 updated successfully Налог %1@%2 је успешно ажуриран Successfully added %1@%2 to the database. Save your password Успешно додах %1@%2 у базу. Саачувајте вашу лозинку Toxme error Грешка на ToxMe Register Региструј се Update Ажурирај Change password button text Измени лозинку Set profile password button text Постави лозинку профила Current profile location: %1 Локација текућег профила: %1 Couldn't change password Не могу да променим лозинку This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Ова хрпа знакова говори другим Tox клијентима како да вас контактирају. Поделите је са пријатељима да би комуницирали. Овај ID саржи NoSpam код (плав) и суме за проверу (сиве). Empty path is unavaliable Празна путања није могућа Failed to rename Неуспело преименовање Profile already exists Профил већ постоји A profile named "%1" already exists. Профил имена „%1“ већ постоји. Empty name Празно име Empty name is unavaliable Празно име није могуће Empty path Празна путања Couldn't change password on the database, it might be corrupted or use the old password. Не могу да променим лозинку базе, можда је оштећена или користите стару лозинку. Export profile Извези профил Tox save file (*.tox) save dialog filter Tox-ов фајл снимка (*.tox) The following files could not be deleted: deletion failed text part 1 Следећи фајлови се не могу обрисати: Please manually remove them. deletion failed text part 2 Молимо да их ручно уклоните. Are you sure you want to delete your password? deletion confirmation text Стварно сте сигурни да желите да уклоните лозинку? Images (%1) filetype filter Слике (%1) ProfileImporter Import profile import dialog title Увоз профила Tox save file (*.tox) import dialog filter Tox-ов фајл снимка (*.tox) Ignoring non-Tox file popup title Игоришем не-Tox фајл Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Упозорење: изабрали сте фајл који није Tox-ов фајл снимка; игноришем. Profile already exists import confirm title Профил већ постоји A profile named "%1" already exists. Do you want to erase it? import confirm text Профил имена „%1“ већ постоји. Желите ли да га обришете? File doesn't exist Фајл не постоји Profile doesn't exist Профил не постоји Profile imported Профил је увезен %1.tox was successfully imported %1.tox је успешно увезен QApplication Ok У реду Cancel Откажи Yes Да No Не LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Не могу да додам пријатеља %1 is not a valid Toxme address. %1 није исправна ToxMe адреса. You can't add yourself as a friend! When trying to add your own Tox ID as friend Не можете додати себе као пријатеља! QObject Tox URI to parse Tox URI за рашчланити Starts new instance and loads specified profile. Покреће нови примерак и учитава наведени профил. profile профил Default подразумевана Blue плава Olive маслинаста Red црвена Violet ружичаста Incoming call... Долазни позив... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 овде! Tox-уј са мном? None No camera device set ниједан Desktop Desktop as a camera input for screen sharing десктоп Server doesn't support Toxme Сервер не подржава ToxMe You're making too many requests. Wait an hour and try again Правите исувише захтева. Сачекајте час и пробајте поново This name is already in use Ово име се већ користи This Tox ID is already registered under another name Овај Tox ID је већ регистрован под другим именом Please don't use a space in your name Молимо не стављајте размаке у ваше име Password incorrect Погрешна лозинка You can't use this name Не можете користити ово име Name not found Име није нађено Tox ID not sent Tox ID није послат That user does not exist Тај корисник не постоји Error Грешка qTox couldn't open your chat logs, they will be disabled. qTox не може да отвори ваше дневнике ћаскања, биће онемогућени. Problem with HTTPS connection Проблем са HTTPS везом Internal ToxMe error Унутрашња грешка на ToxMe Reformatting text in progress.. У току је поновно форматирање текста.. Starts new instance and opens the login screen. Покреће нови примерак и отвара екран за пријаву. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status на вези away contact status одсутан busy contact status зазузет offline contact status ван везе blocked contact status RemoveFriendDialog Remove friend Уклони пријатеља Also remove chat history Такође уклони историјат ћаскања Remove Уклони Are you sure you want to remove %1 from your contacts list? Сигурно желите а уклоните „%1“ из списка контаката? Remove all chat history with the friend if set Ако је задато, уклања цео историјат ћаскања заједно са пријатељем ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Кликните и вуците да би изабрали област. Притисните %1 да сакријете или прикажете прозор qTox-а, или %2 да откажете. Space [Space] key on the keyboard Размак Escape [Escape] key on the keyboard Ескејп Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Притисните %1 да пошаљете снимак избора, %2 да сакријете или прикажете прозор qTox-а, или %3 да откажете. Enter [Enter] key on the keyboard Ентер SearchForm The text could not be found. Start SearchSettingsForm Form Образац Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Поставите лозинку Confirm: Потврдите: Password: Лозинка: Password strength: %p% Јачина лозинке: %p% The password is too short Лозинка је прекратка The password doesn't match. Лозинке се не поклапају. Confirm password Потврда лозинке Confirm password input Унос потврде лозинке Password input Унос лозинке Password input field, minimum 6 characters long Поље уноса лозинке, минимална дужина је 6 знакова Settings Circle #%1 Круг #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Додавање пријатеља Do you want to add %1 as a friend? Желите ли да додате %1 у пријатеље? User ID: ID корисника: Friend request message: Порука захева за пријатељство: Send Send a friend request Пошаљи Cancel Don't send a friend request Откажи UserInterfaceForm None Ништа User Interface Корисничко сучеље UserInterfaceSettings Chat Ћаскање Base font: Основни фонт: px тач Size: Величина: New text styling preference may not load until qTox restarts. Нова поставка стилизовања текста се можда неће учитати пре поновног покретања qTox-а. Text Style format: Формат стила текста: Select text styling preference. Изаберите поставку стилизовања текста. Plaintext обичан текст Show formatting characters прикажи знакове за форматирање Don't show formatting characters не приказуј знакове за форматирање New message Нова порука Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Отвори прозор qTox-а по пријему нове поруке ако није већ отворен. Open window Отвори прозор Contact list Списак контаката If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Уколико је означено, групна ћаскања ће бити постављена на врх списка пријатеља, у супротном ће бити постављена испод пријатеља на вези. Place groupchats at top of friend list Постави групна ћаскања на врх списка пријатеља Your contact list will be shown in compact mode. toolTip for compact layout setting Ваш списак контаката се приказује у компактном режиму. Compact contact list Компактна листа контаката Multiple windows mode Режим вишеструких прозора Open each chat in an individual window Отвори свако ћаскање у засебном прозору Emoticons Емотикони Use emoticons Користи емотиконе Smiley Pack: Text on smiley pack label Тема емотикона: Emoticon size: Величина емотикона: px тач Theme Тема Style: Стил: Theme color: Боја теме: Timestamp format: Формат временске ознаке: Date format: Формат датума: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Пусти звук Play sound while Busy Пусти звук током зузећа Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' На вези Away Button to set your status to 'Away' Одсутан Busy Button to set your status to 'Busy' Заузет toxcore failed to start, the application will terminate after you close this message. Неуспело покретање toxcore, програм ће се угасити по затварању ове поруке. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Неуспело покретање toxcore са вашим поставкама проксија. qTox не може да се покрене; измените поставке и поново га покрените. File Фајл Edit Profile Уреди профил Change Status Промени статус Log out Одјава Edit Уреди Logout Tray action menu to logout user Одјави се Exit Tray action menu to exit tox Напусти Filter... Филтер... Contacts Контакти Add Contact... Додај контакт... Next Conversation Следећи разговор Previous Conversation Претходни разговор Executable file popup title Извршни фајл You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Тражили сте qTox-у да отвори извршни фајл. Извршни фајлови могу потенцијално нанети штету вашем рачунару. Да ли сте сигурни да желите да отворите овај фајл? Couldn't request friendship Не могу да захтевам пријатељство Status Статус Your name Ваше име Message failed to send Неуспело слање поруке Create new group... Направи нову групу... Add new circle... Додај нови круг... %n New Friend Request(s) %n нови захтев за пријатељством %n нова захтева за пријатељством %n нових захтева за пријатељством %n New Group Invite(s) %n нови позив у групу %n нова позива у групу %n нових позива у групу By Name По имену By Activity По активности All Сви Online На вези Offline Ausgelassen Ван везе Friends Пријатељи Groups Групе Search Contacts Претражи контакте Groupchat #%1 Групно ћаскање #%1 Show Tray action menu to show qTox window Прикажи Add friend title of the window Додавање пријатеља Group invites title of the window Позивнице у групу File transfers title of the window Преноси фајлова Settings title of the window Подешвање My profile title of the window Мој профил Failed to send file "%1" Неуспело слање фајла „%1“ File sent sent you a friend request. invites you to join a group. qTox/translations/sr_Latn.ts000066400000000000000000003261211415623743500165230ustar00rootroot00000000000000 AVForm Audio/Video Zvuk/Video Default resolution Podrazumevana rezolucija Disabled Onemogućeno Select region Izaberite oblast Screen %1 Ekran %1 Audio Settings Podešavanje zvuka Gain Pojačanje Playback device Uređaj za reprodukciju Use slider to set volume of your speakers. Pomoću klizača podesite začinu zvučnika. Capture device Uređaj za hvatanje Volume Jačina Video Settings Podešavanje videa Video device Video uređaj Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Podesite rezoluciju kamere. Što je vrednost veća, bolji je kvalitet videa koji vaši prijatelji primaju. Imajte u vidu da veći kvalitet zahteva i bolju vezu sa internetom. Nekad vaša veza može biti nedovoljna da podrži veći kvalitet videa, što može uzrokovati probleme sa video pozivima. Resolution Rezolucija Rescan devices Ponovo skeniraj uređaje Test Sound Proba zvuka Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Omogućava eksperimentalno zvučno pročelje sa potiskivanjem eha, zahteva da ponovo pokrenete qTox. Enable experimental audio backend Omogući eksperimentalno zvučno pročelje Audio quality Kvalitet zvuka Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Emitovani kvalitet zvuka. Umanjite postavke ukoliko vaša veza nije dovoljno jaka ili želite da smanjite upotrebu interneta. High (64 kbps) visok (64 kbps) Medium (32 kbps) srednji (32 kbps) Low (16 kbps) nizak (16 kbps) Very low (8 kbps) vrlo nizak (8 kbps) Threshold AboutForm About O programu Original author: %1 Izvorni autor: %1 You are using qTox version %1. Koristite qTox verzije %1. Commit hash: %1 Heš predaje: %1 toxcore version: %1 verzija toxcore-a: %1 Qt version: %1 verzija Qt-a: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Lista oznatih problema se može naći na našem Github %1. Ukoliko otkrijete grešku ili bezbenosnu ranjivost u qTox-u, molimo prijavite je u skladu sa smernicama na našem viki članku %2 . Click here to report a bug. Kliknite ovde za prijavu greške. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Pogledajte celokupan spisak %1 na Githabu bug-tracker Replaces `%1` in the `A list of all known…` bubolovcu Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` „Pisanje korisnih prijava grešaka“ contributors Replaces `%1` in `See a full list of…` doprinosioca AboutFriendForm Dialog Dijalog username orisničko ime status message statusna poruka Used aliases: Korišćeni alijasi: HISTORY OF ALIASES ISTORIJAT ALIJASA Automatically accept files from contact if set Ukoliko je postavljeno automatski prihvata fajlove od kontakata Auto accept files Automatski primaj fajlove Default directory to save files: Podrazumevana fascikla za čuvanje fajlova: Auto accept for this contact is disabled Automatsko prihvatanje fajlova od ovog kontakta je onemogućeno Auto accept call: Automatsski prihvati poziv: Manual Priručnik Audio Zvuk Audio + Video Zvuk i video Automatically accept group chat invitations from this contact if set. Ukoliko je zadano, automatski prihavti pozive za grupno ćaskanje od ovog kontakta. Auto accept group invites Automatski prihvati pozive u grupu Remove history (operation can not be undone!) Ukloni istorijat (tadnja ne može biti opozvana!) Notes Zabeleške Input field for notes about the contact Polje unosa za beleške o kontaktu You can save comment about this contact here. Ovde možete sačuvati komentare o kontaktu. History removed Istorijat je uklonjen Choose an auto accept directory popup title <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Potvrda Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Verzija License Licenca Authors Autori Known Issues Poznati problemi Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Dodaj prijatelje Invalid Tox ID format Neispravan format ToxID Send friend request Pošalji zahtev za prijateljstvo Add a friend Dodaj prijatelja Friend requests Zahtevi za prijateljstvo Accept Prihvati Reject Odbij Couldn't add friend Ne mogu da nađem prijatelja Tox ID, either 76 hexadecimal characters or name@example.com ToxID, ili 76 heksadecimalnih znakova, ili ime@example.com Type in Tox ID of your friend Unesite ToxID vašeg prijatelja Friend request message Poruka uz zahtev za prjateljstvo Type message to send with the friend request or leave empty to send a default message Unesite poruku za poslati uz zahtev za prijateljstvo ili ostavite prazno kako bi poslali osnovnu poruku %1 Tox ID is invalid or does not exist Toxme error %1 ToxID ne postoji You can't add yourself as a friend! When trying to add your own Tox ID as friend Ne možete dodati sebe kao prijatelja! Open contact list Otvori spisak kontakata Couldn't open file Ne mogu da otvorim fajl Couldn't open the contact file Error message when trying to open a contact list file to import Ne mogu da otvorim fajl sa kontaktima Invalid file Neisprravan fajl We couldn't find any contacts to import in this file! Ne nađoh nijean kontakt za uvesti u ovom fajlu! Tox ID Tox ID of the person you're sending a friend request to ToxID either 76 hexadecimal characters or name@example.com Tox ID format description ili 76 heksadecimalnih znakova, ili ime@example.com Message The message you send in friend requests Poruka Open Button to choose a file with a list of contacts to import Otvori Send friend requests Pošalji zahteve za prijateljstvo %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 ovde! Tox-uj sa mnom? Import a list of contacts, one Tox ID per line Uvezite spisak kontakata, jedan Tox ID po liniji Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Spreman da uvezem %n kontakt, kliknite na slanje za potvrdu Spreman da uvezem %n kontakta, kliknite na slanje za potvrdu Spreman da uvezem %n kontakta, kliknite na slanje za potvrdu Import contacts Uvoz kontakata AdvancedForm Advanced Napredno Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Ukoliko %1 šta radite, molimo %2 menjajte ništa ovde. Načinjene izmene mogu dovesti do problema u qTox-u, čak i do gubitka podataka poput istorijata. really niste sigurni not ne IMPORTANT NOTE VAŽNA NAPOMENA Reset settings Resetuj postavke All settings will be reset to default. Are you sure? Sve postavke će biti vraćene na podrazumevane. Da li ste sigurni? Yes Da No Ne Call active popup title Poziv u toku You can't disconnect while a call is active! popup text Ne možete se razvezati tokom trajanja poziva! Save File Snimi fajl Logs (*.log) Dnevnici (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Sačuvaj postavke u radnu fasciklu umesto uobičajene fascikle postavki Make Tox portable Učini Tox prenosivim Reset to default settings Vrati na osnovne postavke Portable Prenosivost Connection Settings Podešavanje veze Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Omogući IPv6 (preporučeno) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Onemogućavanje ovoga dozvoljava da , npr., tox-ujete preko Tora. Međutim, ovo opterećuje Tox mrežu, tako da onemogućite samo kad je neophodno. Enable UDP (recommended) Text on checkbox to disable UDP Omogući UDP (preporučeno) Proxy type: Vrsta proksija: Address: Text on proxy addr label Adresa: Port: Text on proxy port label Port: None nijedan SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Ponovo se poveži Debug Ispravljanje grešaka Export Debug Log Izvezi dnevnik ispravljanja grešaka Copy Debug Log Umoži dnevnik ispravljanja grešaka Enable LAN discovery ChatForm Send a file Pošalji fajl qTox wasn't able to open %1 qTox ne može da otvori %1 Unable to open Ne mogu da otvorim Bad idea Loša ideja %1 calling %1 zove Calling %1 Pozivam %1 Failed to open temporary file Temporary file for screenshot Neuspelo otvaranje privremenog fajla qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox ne uspe da sačuva snimak Call with %1 ended. %2 Zvanje %1 završi. %2 Call duration: Trajanje poziva: %1 is typing %1 kuca Copy Umoži You're trying to send a sequential file, which is not going to work! Pokušavate da pošaljete fajl u delovima što neće raditi! %1 is now %2 e.g. "Dubslow is now online" %1 je sad %2 Call with %1 ended unexpectedly. %2 Zvanje %1 neočekivano završi. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Ne mogu da započnem audio poziv Start audio call Započni audio poziv End audio call Cancel audio call Otkaži audio poziv Accept audio call Prihvati audio poziv Can't start video call Ne mogu da započnem vido poziv Start video call Započni video poziv End video call Cancel video call Ozkaži video poziv Accept video call Prihvati video poziv Sound can be disabled only during a call Zvuk se može isključiti samo tokom poziva Unmute call Mute call Utišaj poziv Microphone can be muted only during a call Mikrofon se može utišati samo tokom poziva Unmute microphone Mute microphone Utišaj mikrofon ChatLog Copy Umnoži Select all Izaberi sve pending na čekanju ChatTextEdit Type your message here... Ovde unesite vašu poruku... CircleWidget Rename circle Menu for renaming a circle Preimenuj krug Remove circle Menu for removing a circle Ukoni krug Open all in new window Otvori ssve u novom prozoru Core /me offers friendship, "%1" /me nudi prijateljstvo, „%1“ Invalid Tox ID Error while sending friendship request Neispravan Tox ID You need to write a message with your request Error while sending friendship request Morate napisati poruku uz vaš zahtev za prijateljstvo Your message is too long! Error while sending friendship request Vaša poruka je predugačka! Friend is already added Error while sending friendship request Prijatelj je već dodat Groupchat %1 DesktopNotify New message Nova poruka Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen Obrazac 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen ETA:10:10 Filename Ausgelassen Ime fajla Waiting to send... file transfer widget Čekam na slanje... Accept to receive this file file transfer widget Prihati prijem ovog fajla Location not writable Title of permissions popup Lokacija nije upisiva You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nemate dozvole za pisanje na ovoj lokaciji. Izaberite drugu, ili otkažite u dijalogu snimanja. Resuming... file transfer widget Nastavljam... Cancel transfer Otkaži prenos Pause transfer Pauziraj prenos Paused file transfer widget Pauziran Open file Otvori fajl Open file directory Otvori fasciklu fajla Resume transfer Nastavi prenos Accept transfer Prihvati prenos Save a file Title of the file saving dialog Sačuvaj fajl Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Prebačeni fajlovi Downloads Prijemi Uploads Slanja FriendListWidget Today Danas Yesterday Juče Last 7 days Proteklih 7 dana This month Ovog meseca Older than 6 Months Starije od 6 meseci Never Nikada FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Zahtev za prijateljstvo Someone wants to make friends with you Neko želi da vam bude prijatelj User ID: Korisnički ID: Friend request message: Poruka zahteva za prijateljstvo: Accept Accept a friend request Prihvati Reject Reject a friend request Odbij FriendWidget Invite to group Menu to invite a friend to a groupchat Pozovi u grupu Move to circle... Menu to move a friend into a different circle Pomeri u krug... To new circle U novi krug Remove from circle '%1' Ukloni iz kruga „%1 “ Move to circle "%1" Pomeri u krug „%1“ Open chat in new window Otvori ćakanje u novom prozoru Remove chat from this window Ukloni ćaskanje iz ovog prozora To new group U novu grupu Invite to group '%1' Pozovi u grupu „%1“ Set alias... Postavi alijas... Auto accept files from this friend context menu entry Automatski prihvati fajlove od ovog prijatelja Remove friend Menu to remove the friend from our friendlist Ukloni prijatelja Show details Prikaži detalje Choose an auto accept directory popup title Izaberte fasciklu za automatski prijem New message Nova poruka Online Na vezi Away Odsutan Busy Zauzet Offline Ausgelassen Van veze GeneralForm General Opšte Choose an auto accept directory popup title Izaberite fasciklu za automatski prijem GeneralSettings General Settings Opšte postavke The translation may not load until qTox restarts. Prevod se možda neće učitati pre ponovnog pokretanja qTox-a. Language: Jezik: Show system tray icon Prikazuj ikonu sistemske kasete Enable light tray icon. toolTip for light icon setting Svetla ikona kasete. Light icon Svetla ikona qTox will start minimized in tray. toolTip for Start in tray setting qTox će se pokretati smanjen u kasetu. Start in tray Pokreni u kaseti After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Po kliku na zatvori (X) qTox će se minimizovati u kasetu umesto da se zatvori. Close to tray Zatvori u kasetu After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Po kliku na minimizuj (_) qTox će se minimizovati u kasetu umesto sistemske trake zadataka. Minimize to tray Minimizuj u kasetu Autostart Automatsko pokretanje Set where files will be saved. Izaberite gde čuvati fajlove. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Ovo možete podesiti posebno za svakog prijatelja desnim klikom. Autoaccept files Automatski prijem fajlova Set to 0 to disable Postavite na 0 da onemogućite Your status is changed to Away after set period of inactivity. Vaš status se menja na Odsutan nakon zadatog perioda neaktivnosti. Auto away after (0 to disable): Automatska odsutnost nakon (0 da onemogućite): Show contacts' status changes Prikaži promene satusa kontakata Start qTox on operating system startup (current profile). Pokreni qTox sa operativnim sistemom (tekući profil). Default directory to save files: Podrazumevana fascikla za čuvanje fajlova: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Pošalji poruku Smileys Emotikoni Send file(s) Pošalji fajl(ove) Send a screenshot Pošalji snimak ekrana Save chat log Sačuvaj dnevnik ćaskanja Clear displayed messages Očisti prikazane poruke Cleared Očišćeno Quote selected text Citiraj izabrani tekst Copy link address Kopiraj adresu veze Confirmation Potvrda You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Učitaj istorijat ćaskanja... Export to file Izvezi u fajl GenericNetCamView Tox video Toks video Show Messages Prikaži poruke Hide Messages Sakrij poruke Full Screen Toggle video preview Mute audio Mute microphone Utišaj mikrofon End video call Exit full screen GroupChatForm %1 has set the title to %2 %1 postavi naslov na %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Grupe Create new group Napravi novu grupu Group invites Pozivnice u grupu GroupInviteWidget Invited by %1 on %2 at %3. Pozva %1 %2 u %3. Join Pridruži se Decline Odbij GroupWidget Set title... Postavi naslov... Open chat in new window Otvori ćaskanje u novom prozoru Remove chat from this window Ukloni ćaskanje iz ovog prozora Quit group Menu to quit a groupchat Napusti grupu %n user(s) in chat Number of users in chat New Message Online Na vezi IdentitySettings Public Information Javne informacije Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Ova hrpa znakova govori drugim Tox klijentima kako da vas kontaktiraju. Podelite je sa prijateljima da bi komunicirali. Your Tox ID (click to copy) Vaš Tox ID (kliknite da kopirate) Profile Profil Rename profile. tooltip for renaming profile button Preimenuj profil. Go back to the login screen tooltip for logout button Vrati se na ekran za prijavu Logout import profile button Odjava Remove password Ukloni lozinku Change password Izmeni lozinku This QR code contains your Tox ID. You may share this with your friends as well. Ovaj QR kod sadrži vaš Tox ID. Možete ga deliti i vašim prijateljima. Save image Sačuvaj sliku Copy image Kopiraj sliku Rename rename profile button Preimenuj Delete profile. delete profile button tooltip Obriši profil. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Omogućava vam da izvezete vaš Tox profil u fajl. Profil ne sadrži vaš istorijat. Export export profile button Izvezi Delete delete profile button Obriši Server Server Hide my name from the public list Sakrij moje ime sa javne liste Register Registruj se Your password Vaša lozinka Update Ažuriraj Register on ToxMe Registruj se na ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Ime na servisu ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Opciono. Nešto o vama. Ili vašoj mački. Optional. Something about you. Or your cat. Tooltip for the Biography field. Opciono. Nešto o vama. Ili vašoj mački. ToxMe service to register on. ToxMe servis na koji se treba registrovati. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ako nije označeno, ToxMe unosi su javni. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Uklanja lozinku i šifrovanje iz vašeg profila. Name input Unos imena Name visible to contacts Ime vidljivo kontaktima Status message input Unos statusne poruke Status message visible to contacts Statusna poruka je vidljiva kontaktima Your Tox ID Vaš Tox ID Save QR image as file Sačuvaj QR sliku kao fajl Copy QR image to clipboard Umnoži QR sliku u ostavu ToxMe username to be shown on ToxMe ToxMe korisničko ime koje se prikazuje na ToxMe Optional ToxMe biography to be shown on ToxMe Opciona ToxMe biografija koja se prikazuje na ToxMe ToxMe service address Adresa ToxMe servisa Visibility on the ToxMe service Vidljivost na ToxMe servisu Password Lozinka Update ToxMe entry Ažuriraj ToxMe unos Rename profile. Preimenuj profil. Delete profile. Obriši profil. Export profile Izvezi profil Remove password from profile Ukoloni lozinku profila Change profile password Promeni lozinku profila My name: Moje ime: My status: Moj status: My username Moje korisničko ime My biography Moja biografija My profile Moj profil LoadHistoryDialog Load History Dialog Dijalog učitavanja istorijata Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Korisničko ime: Password: Lozinka: Confirm: Potvrda: Password strength: %p% Jačina lozinke: %p% Create Profile Napravi profil If the profile does not have a password, qTox can skip the login screen Ukoliko profil nema lozinku, qTox će preskočiti ekran za prijavu Load automatically Automatski učitaj Load Učitaj Load Profile Učitaj profil New Profile Novi profil Couldn't create a new profile Ne mogu da napravim novi profil The username must not be empty. Korisničko ime ne sme biti prazno. The password must be at least 6 characters long. Lozinka mora imati najmanje 6 znakova. The passwords you've entered are different. Please make sure to enter same password twice. Lozinke koje ste uneli se razlikuju. Postarajte se da unesete istu lozinku dvaput. A profile with this name already exists. Profil sa ovim imenom već postoji. Password protected profiles can't be automatically loaded. Profili zaštićeni lozinkom se ne mogu učitati automatski. Couldn't load profile Ne mogu da učitam profil There is no selected profile. You may want to create one. Nema izabranog profila. Verovatno želite da napravite jedan. Couldn't load this profile Ne mogu da učitam ovaj profil This profile is already in use. Ovaj profil je već u upotrebi. Wrong password. Pogrešna lozinka. Import Uvoz Username input field Polje unosa korisničkog imena Password input field, you can leave it empty (no password), or type at least 6 characters Polje unosa lozinke, možete ga ostaviti praznim (bez lozinke), ili unesite najmanje 6 znakova Password confirmation field Polje potvrde lozinke Create a new profile button Dugme za pravljenje novog profila Profile list Lista profila List of profiles Lista profila Password input Unos lozinke Load automatically checkbox Kućica za automatsko učitavanje Import profile Uvoz profila Load selected profile button Dugme uvoza izabranog profila New profile creation page Stranica pravljenja novog profila Loading existing profile page Stranica učitavanja postojećeg profila MainWindow Your name Vaše ime Your status Vaš status ... Ausgelassen ... Add friends Dodaj prijatelje Create a group chat Napravi grupno ćaskanje View completed file transfers Prikaži završene prenose fajlova Change your settings Izmenite vaše postavke Close Zatvori Open profile Otvori profil Open profile page when clicked Otvori profil po kliku Status message input Unos statusne poruke Set your status message that will be shown to others Postavite statusnu poruku vidljivu drugima Status status Set availability status Postavite dostupnost Contact search Pretraga kontakata Contact search input for known friends Unos pretrage kontakata za poznate prijatelje Sorting and visibility Ređanje i vidljivost Set friends sorting and visibility Postavi ređanje prijatelja i vidljivost Open Add friends page Otvori stranicu Dodaj prijatelje Groupchat Grupno ćaskanje Open groupchat management page Otvori stranicu upravljanja grupnim ćaskanjem File transfers history Istorijat prenosa fajlova Open File transfers history Otvara istorijat prenosa fajlova Settings Podešavanje Open Settings Otvori postavke Nexus View OS X Menu bar Prikaz Window OS X Menu bar Prozor Minimize OS X Menu bar Minimizuj Bring All to Front OS X Menu bar Stavi sve ispred Exit Fullscreen Napusti ceo ekran Enter Fullscreen Pređi na ceo ekran NotificationEdgeWidget Unread message(s) %n nepročitana poruka %n nepročitane poruke %n nepročitane poruke PasswordEdit CAPS-LOCK ENABLED UKLjUČEN CAPS-LOCK PrivacyForm Privacy Privatnost Confirmation Potvrda Do you want to permanently delete all chat history? Želite li da trajno obrišete istorijat ćaskanja? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Vaši prijatelji će videti kada kucate. Send typing notifications Šalji obaveštenja o kucanju Keep chat history Zadrži istoriju ćaskanja NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam je deo vašeg Tox ID. Ukoliko vas spamuju zahtevima za prijateljstvo, trebalo bi da izmenite vaš NoSpam. Drugi neće moći da vas dodaju putem starog ID, ali ćete zadržati sve trenutne prijatelje. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam je deo vašeg Tox ID. Ukoliko vas spamuju zahtevima za prijateljstvo, izmenite vaš NoSpam. Generate random NoSpam Napravi proizvoljan NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Zadržavanje istorijata ćaskanja je još u razvoju. Promene u formatu snimanja su moguće, što može dovesti do gubitka podataka. Privacy Privatnost BlackList Crna lista Filter group message by group member's public key. Put public key here, one per line. Filter grupnih poruka prema javnom ključu članova grupe. Javne ključeve unesite ovde, jedan po liniji. Profile Failed to derive key from password, the profile won't use the new password. Neuspelo izvođenje ključa iz lozinke, profil neće koristiti novu lozinku. Couldn't change password on the database, it might be corrupted or use the old password. Ne mogu da promenim lozinku baze, možda je oštećena ili koristite staru lozinku. Toxing on qTox Tox-uje putem qTox-a ProfileForm Choose a profile picture Izaberite sliku profila Error Greška Rename "%1" renaming a profile Preimenovanje „%1“ Unable to open this file. Ne mogu da otvorim ovaj fajl. Current profile: Tekući profil: Remove Ukloni Unable to read this image. Ne mogu da čitam ovu sliku. The supplied image is too large. Please use another image. Dostavljena slika je prevelika. Molimo koristite drugu. Couldn't rename the profile to "%1" Ne mogu da preimenujem profil u „%1“ Location not writable Title of permissions popup Lokacija nije upisiva You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nemate dozvolu upisa na ovoj lokaciji. Izaberite drugu, ili otkažite snimanje u dijalogu. Failed to copy file Neuspelo kopiranje fajla The file you chose could not be written to. Ne može se pisati u izabrani fajl. Really delete profile? deletion confirmation title Zaista obrisati profil? Nothing to remove Nema se šta ukloniti Your profile does not have a password! Vaš profil nema lozinku! Really delete password? deletion confirmation title Zaista obrisati lozinku? Please enter a new password. Unesite novu lozinku. Are you sure you want to delete this profile? deletion confirmation text Stvarno ste sigurni da želite da uklonite ovaj profil? Save save qr image Čuvanje Save QrCode (*.png) save dialog filter Čuvanje QrCode-a (*.png) Files could not be deleted! deletion failed title Fajlovi se ne mogu obrisati! Register (processing) Registracija (u toku) Update (processing) Ažuriranje (u toku) Done! Gotovo! Account %1@%2 updated successfully Nalog %1@%2 je uspešno ažuriran Successfully added %1@%2 to the database. Save your password Uspešno dodah %1@%2 u bazu. Saačuvajte vašu lozinku Toxme error Greška na ToxMe Register Registruj se Update Ažuriraj Change password button text Izmeni lozinku Set profile password button text Postavi lozinku profila Current profile location: %1 Lokacija tekućeg profila: %1 Couldn't change password Ne mogu da promenim lozinku This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Ova hrpa znakova govori drugim Tox klijentima kako da vas kontaktiraju. Podelite je sa prijateljima da bi komunicirali. Ovaj ID sarži NoSpam kod (plav) i sume za proveru (sive). Empty path is unavaliable Prazna putanja nije dostupna Failed to rename Neuspelo preimenovanje Profile already exists Profil već postoji A profile named "%1" already exists. Profil imena „%1“ već postoji. Empty name Prazno ime Empty name is unavaliable Prazno ime nije dostupno Empty path Prazna putanja Couldn't change password on the database, it might be corrupted or use the old password. Ne mogu da promenim lozinku baze, možda je oštećena ili koristite staru lozinku. Export profile Izvezi profil Tox save file (*.tox) save dialog filter Tox-ov fajl snimka (*.tox) The following files could not be deleted: deletion failed text part 1 Sledeći fajlovi se ne mogu obrisati: Please manually remove them. deletion failed text part 2 Molimo da ih ručno uklonite. Are you sure you want to delete your password? deletion confirmation text Stvarno ste sigurni da želite da uklonite lozinku? Images (%1) filetype filter Slike (%1) ProfileImporter Import profile import dialog title Uvoz profila Tox save file (*.tox) import dialog filter Tox-ov fajl snimka (*.tox) Ignoring non-Tox file popup title Igorišem ne-Tox fajl Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Upozorenje: izabrali ste fajl koji nije Tox-ov fajl snimka; ignorišem. Profile already exists import confirm title Profil već postoji A profile named "%1" already exists. Do you want to erase it? import confirm text Profil imena „%1“ već postoji. Želite li da ga obrišete? File doesn't exist Fajl ne postoji Profile doesn't exist Profil ne postoji Profile imported Profil je uvezen %1.tox was successfully imported %1.tox je uspešno uvezen QApplication Ok U redu Cancel Otkaži Yes Da No Ne LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Ne mogu da dodam prijatelja %1 is not a valid Toxme address. %1 nije ispravna ToxMe adresa. You can't add yourself as a friend! When trying to add your own Tox ID as friend Ne možete dodati sebe kao prijatelja! QObject Tox URI to parse Tox URI za raščlaniti Starts new instance and loads specified profile. Pokreće novi primerak i učitava navedeni profil. profile profil Default podrazumevana Blue plava Olive maslinasta Red crvena Violet ružičasta Incoming call... Dolazni poziv... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 ovde! Tox-uj sa mnom? None No camera device set nijedan Desktop Desktop as a camera input for screen sharing desktop Server doesn't support Toxme Server ne podržava ToxMe You're making too many requests. Wait an hour and try again Pravite isuviše zahteva. Sačekajte čas i probajte ponovo This name is already in use Ovo ime se već koristi This Tox ID is already registered under another name Ovaj Tox ID je već registrovan pod drugim imenom Please don't use a space in your name Molimo ne stavljajte razmake u vaše ime Password incorrect Pogrešna lozinka You can't use this name Ne možete koristiti ovo ime Name not found Ime nije nađeno Tox ID not sent Tox ID nije poslat That user does not exist Taj korisnik ne postoji Error Greška qTox couldn't open your chat logs, they will be disabled. qTox ne može da otvori vaše dnevnike ćaskanja, biće onemogućeni. Problem with HTTPS connection Problem sa HTTPS vezom Internal ToxMe error Unutrašnja greška na ToxMe Reformatting text in progress.. U toku je ponovno formatiranje teksta.. Starts new instance and opens the login screen. Pokreće novi primerak i otvara ekran za prijavu. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status na vezi away contact status odsutan busy contact status zazuzet offline contact status van veze blocked contact status RemoveFriendDialog Remove friend Ukloni prijatelja Also remove chat history Takođe ukloni istorijat ćaskanja Remove Ukloni Are you sure you want to remove %1 from your contacts list? Sigurno želite a uklonite „%1“ iz spiska kontakata? Remove all chat history with the friend if set Ako je zadato, uklanja ceo istorijat ćaskanja zajedno sa prijateljem ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Kliknite i vucite da bi izabrali oblast. Pritisnite %1 da sakrijete ili prikažete prozor qTox-a, ili %2 da otkažete. Space [Space] key on the keyboard Razmak Escape [Escape] key on the keyboard Eskejp Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Pritisnite %1 da pošaljete snimak izbora, %2 da sakrijete ili prikažete prozor qTox-a, ili %3 da otkažete. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form Obrazac Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Postavite lozinku Confirm: Potvrdite: Password: Lozinka: Password strength: %p% Jačina lozinke: %p% The password is too short Lozinka je prekratka The password doesn't match. Lozinke se ne poklapaju. Confirm password Potvrda lozinke Confirm password input Unos potvrde lozinke Password input Unos lozinke Password input field, minimum 6 characters long Polje unosa lozinke, minimalna dužina je 6 znakova Settings Circle #%1 Krug #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Dodavanje prijatelja Do you want to add %1 as a friend? Želite li da dodate %1 u prijatelje? User ID: ID korisnika: Friend request message: Poruka zaheva za prijateljstvo: Send Send a friend request Pošalji Cancel Don't send a friend request Otkaži UserInterfaceForm None Ništa User Interface Korisničko sučelje UserInterfaceSettings Chat Ćaskanje Base font: Osnovni font: px tač Size: Veličina: New text styling preference may not load until qTox restarts. Nova postavka stilizovanja teksta se možda neće učitati pre ponovnog pokretanja qTox-a. Text Style format: Format stila teksta: Select text styling preference. Izaberite postavku stilizovanja teksta. Plaintext običan tekst Show formatting characters prikaži znakove za formatiranje Don't show formatting characters ne prikazuj znakove za formatiranje New message Nova poruka Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Otvori prozor qTox-a po prijemu nove poruke ako nije već otvoren. Open window Otvori prozor Contact list Spisak kontakata If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Ukoliko je označeno, grupna ćaskanja će biti postavljena na vrh spiska prijatelja, u suprotnom će biti postavljena ispod prijatelja na vezi. Place groupchats at top of friend list Postavi grupna ćaskanja na vrh spiska prijatelja Your contact list will be shown in compact mode. toolTip for compact layout setting Vaš spisak kontakata se prikazuje u kompaktnom režimu. Compact contact list Kompaktna lista kontakata Multiple windows mode Režim višestrukih prozora Open each chat in an individual window Otvori svako ćaskanje u zasebnom prozoru Emoticons Emotikoni Use emoticons Koristi emotikone Smiley Pack: Text on smiley pack label Tema emotikona: Emoticon size: Veličina emotikona: px tač Theme Tema Style: Stil: Theme color: Boja teme: Timestamp format: Format vremenske oznake: Date format: Format datuma: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Pusti zvuk Play sound while Busy Pusti zvuk tokom zuzeća Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Na vezi Away Button to set your status to 'Away' Odsutan Busy Button to set your status to 'Busy' Zauzet toxcore failed to start, the application will terminate after you close this message. Neuspelo pokretanje toxcore, program će se ugasiti po zatvaranju ove poruke. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Neuspelo pokretanje toxcore sa vašim postavkama proksija. qTox ne može da se pokrene; izmenite postavke i ponovo ga pokrenite. File Fajl Edit Profile Uredi profil Change Status Promeni status Log out Odjava Edit Uredi Logout Tray action menu to logout user Odjavi se Exit Tray action menu to exit tox Napusti Filter... Filter... Contacts Kontakti Add Contact... Dodaj kontakt... Next Conversation Sledeći razgovor Previous Conversation Prethodni razgovor Executable file popup title Izvršni fajl You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Tražili ste qTox-u da otvori izvršni fajl. Izvršni fajlovi mogu potencijalno naneti štetu vašem računaru. Da li ste sigurni da želite da otvorite ovaj fajl? Couldn't request friendship Ne mogu da zahtevam prijateljstvo Status Status Your name Vaše ime Message failed to send Neuspelo slanje poruke Create new group... Napravi novu grupu... Add new circle... Dodaj novi krug... %n New Friend Request(s) %n novi zahtev za prijateljstvom %n nova zahteva za prijateljstvom %n novih zahteva za prijateljstvom %n New Group Invite(s) %n novi poziv u grupu %n nova poziva u grupu %n novih poziva u grupu By Name Po imenu By Activity Po aktivnosti All Svi Online Na vezi Offline Ausgelassen Van veze Friends Prijatelji Groups Grupe Search Contacts Pretraži kontakte Groupchat #%1 Grupno ćaskanje #%1 Show Tray action menu to show qTox window Prikaži Add friend title of the window Dodavanje prijatelja Group invites title of the window Pozivnice u grupu File transfers title of the window Prenosi fajlova Settings title of the window Podešvanje My profile title of the window Moj profil Failed to send file "%1" Neuspelo slanje fajla „%1“ File sent sent you a friend request. invites you to join a group. qTox/translations/sv.ts000066400000000000000000003263241415623743500155560ustar00rootroot00000000000000 AVForm Audio/Video Ljud/video Default resolution Standardupplösning Disabled Inaktiverad Select region Välj region Screen %1 Skärm %1 Audio Settings Ljudinställningar Gain Förstärkning Playback device Uppspelningsenhet Use slider to set volume of your speakers. Använder skjutreglaget för att ställa in volym på dina högtalare. Capture device Inspelningsenhet Volume Volym Video Settings Videoinställningar Video device Video-enhet Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Ange upplösning för din kamera. Ju högre värden, desto bättre bildkvalitet kan dina vänner få. Tänk dock på att med bättre bildkvalitet behövs bättre internetanslutning. Ibland kanske din anslutning inte är bra nog för att hantera högre videokvalitet, vilket kan leda till problem med videosamtal. Resolution Upplösning Rescan devices Skanna om enheter Test Sound Prova ljud Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Aktiverar experimentell ljud-backend med ekoborttagningsstöd, kräver att qTox startas om för att träda i kraft. Enable experimental audio backend Aktivera experimentell ljud-backend Audio quality Ljudkvalitet Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Skickad ljudkvalitet. Välj en lägre inställning om din bandbredd är för låg eller om du vill minska på dataanvändningen. High (64 kbps) Hög (64 kbps) Medium (32 kbps) Medium (32 kbps) Low (16 kbps) Låg (16 kbps) Very low (8 kbps) Väldigt låg (8 kbps) Threshold Tröskelvärde AboutForm About Om Original author: %1 Ursprunglig författare: %1 You are using qTox version %1. Du använder qTox version %1. Commit hash: %1 Inchecknings-hash: %1 toxcore version: %1 toxcore-version: %1 Qt version: %1 Qt-version: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` En lista över alla kända problem kan hittas på vår %1 på Github. Om du upptäcker ett fel eller säkerhetsproblem inom qTox, vänligen rapportera det i enlighet med riktlinjerna i vår wikiartikel %2. Click here to report a bug. Klicka här för att rapportera en bugg. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Se en fullständig lista över %1 på Github bug-tracker Replaces `%1` in the `A list of all known…` felbevakare Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Skriva användbara felrapporter contributors Replaces `%1` in `See a full list of…` bidragsgivare AboutFriendForm Dialog Dialogruta username användarnamn status message statusmeddelande Used aliases: Använda alias: HISTORY OF ALIASES HISTORIK AV ALIAS Automatically accept files from contact if set Acceptera filer automatiskt från kontakt om angivet Auto accept files Acceptera filer automatiskt Default directory to save files: Standardkatalog för att spara filer: Auto accept for this contact is disabled Acceptera automatiskt för den här kontakten är inaktiverad Auto accept call: Acceptera samtal automatiskt: Manual Handbok Audio Ljud Audio + Video Ljud + video Automatically accept group chat invitations from this contact if set. Acceptera gruppchattsinbjudningar automatiskt från denna kontakt om angivet. Auto accept group invites Acceptera gruppinbjudningar automatiskt Remove history (operation can not be undone!) Ta bort historik (operation kan inte ångras!) Notes Anteckningar Input field for notes about the contact Inmatningsfält för anteckningar om kontakten You can save comment about this contact here. Du kan spara kommentar om denna kontakt här. History removed Historik borttagen Choose an auto accept directory popup title Välj en mapp för acceptera-automatiskt <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Detta är din väns offentliga nyckel, använd den för att verifiera dennes identitet via annan kanal. Du kan inte skicka detta till andra människor, så att de kan lägga till denna kontakt.</p></body></html> Public key (not ToxID): Offentlig nyckel (Inte ToxID): Confirmation Bekräftelse Are you sure to remove %1 chat history? Vill du verkligen ta bort %1 chatthistorik? Failed to remove chat history with %1! Kunde inte ta bort chatthistoriken med %1! AboutSettings Version Version License Licens Authors Författare Known Issues Kända problem Open update download link Öppna nerladdningslänk för uppdatering Update available Uppdatering tillgänglig qTox is up to date ✓ qTox är uppdaterad ✓ AddFriendForm Add Friends Lägg till vänner Send friend request Skicka vänförfrågning Couldn't add friend Kunde inte lägga till vän Invalid Tox ID format Ogiltigt format på Tox-ID Add a friend Lägg till en vän Friend requests Vänförfrågningar Accept Acceptera Reject Avvisa Tox ID, either 76 hexadecimal characters or name@example.com Tox-ID, antingen 76 hexadecimala tecken eller name@example.com Type in Tox ID of your friend Ange Tox-ID för din vän Friend request message Vänförfrågningsmeddelande Type message to send with the friend request or leave empty to send a default message Skriv meddelande att skicka med vänförfrågningen eller lämna tomt för att skicka ett standardmeddelande %1 Tox ID is invalid or does not exist Toxme error %1 Tox-ID är felaktigt eller existerar inte You can't add yourself as a friend! When trying to add your own Tox ID as friend Du kan inte lägga till dig själv som vän! Open contact list Öppna kontaktlista Couldn't open file Kunde inte öppna filen Couldn't open the contact file Error message when trying to open a contact list file to import Kunde inte öppna filen med kontakter Invalid file Felaktig fil We couldn't find any contacts to import in this file! Kunde inte hitta några kontakter att importera från denna fil! Tox ID Tox ID of the person you're sending a friend request to Tox-ID either 76 hexadecimal characters or name@example.com Tox ID format description antingen 76 hexadecimala tecken eller namn@exempel.se Message The message you send in friend requests Meddelande Open Button to choose a file with a list of contacts to import Öppna Send friend requests Skicka vänförfrågan %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 här! Toxa mig kanske? Import a list of contacts, one Tox ID per line Importera en lista med kontakter, ett Tox-ID per rad Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Klar för att importera %n kontakt(er), klicka på skicka för att bekräfta Klar för att importera %n kontakter, klicka på skicka för att bekräfta Import contacts Importera kontakter AdvancedForm Advanced Avancerad Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Om du inte verkligen vet vad du gör, gör inga ändringar här. Ändringar som görs här kan leda till problem med qTox, och även till förlust av data, t.ex. historik. really verkligen not inte IMPORTANT NOTE VIKTIG NOTERING Reset settings Återställ inställningar All settings will be reset to default. Are you sure? Alla inställningar återställs till standard. Är du säker? Yes Ja No Nej Call active popup title Samtal aktivt You can't disconnect while a call is active! popup text Du kan inte koppla bort under ett aktivt samtal! Save File Spara fil Logs (*.log) Loggar (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Spara inställningar till arbetsmappen istället för den vanliga konfigurationsmappen Make Tox portable Gör Tox portabel Reset to default settings Återställ till standardinställningar Portable Bärbar Connection Settings Anslutningsinställningar Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Aktivera IPv6 (rekommenderat) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Avaktivering av detta tillåter exempelvis toxande över Tor. Det lägger extra belastning på Tox-nätverket, så avmarkera endast när det är nödvändigt. Enable UDP (recommended) Text on checkbox to disable UDP Aktivera UDP (rekommenderat) Proxy type: Proxytyp: Address: Text on proxy addr label Adress: Port: Text on proxy port label Port: None Ingen SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Återanslut Debug Felsök Export Debug Log Exportera felsökningslogg Copy Debug Log Kopiera felsökningslogg Enable LAN discovery Aktivera LAN-identifiering ChatForm Send a file Skicka en fil qTox wasn't able to open %1 qTox kunde inte öppna %1 %1 calling %1 ringer Call with %1 ended. %2 Samtal med %1 avslutades. %2 Call duration: Samtalslängd: Unable to open Det går inte att öppna Bad idea Dålig idé Calling %1 Ringer %1 Failed to open temporary file Temporary file for screenshot Misslyckades öppna temporär fil qTox wasn't able to save the screenshot qTox kunde inte spara skärmdumpen %1 is typing %1 skriver Copy Kopiera You're trying to send a sequential file, which is not going to work! Du försöker skicka en sekventiell fil, som inte kommer att fungera! %1 is now %2 e.g. "Dubslow is now online" %1 är nu %2 Call with %1 ended unexpectedly. %2 Samtalet med %1 avbröts av okänd anledning. %2 Filename contained illegal characters Filnamnet innehåller förbjudna tecken Illegal characters have been changed to _ so you can save the file on windows. Förbjudet tecken har ändrats till _ så att du kan spara filen på Windows. ChatFormHeader Can't start audio call Kan inte påbörja röstsamtal Start audio call Påbörja röstsamtal End audio call Avsluta ljudsamtal Cancel audio call Avbryt ljudsamtal Accept audio call Acceptera ljudsamtal Can't start video call Kan inte påbörja videosamtal Start video call Påbörja videosamtal End video call Avsluta videosamtal Cancel video call Avbryt videosamtal Accept video call Acceptera videosamtal Sound can be disabled only during a call Ljud kan endast inaktiveras under ett samtal Unmute call Slå på mikrofon Mute call Tysta samtal Microphone can be muted only during a call Mikrofon kan endast tystas under ett samtal Unmute microphone Aktivera mikrofon Mute microphone Stäng av mikrofon ChatLog Copy Kopiera Select all Markera alla pending avvaktar ChatTextEdit Type your message here... Skriv ditt meddelande här... CircleWidget Rename circle Menu for renaming a circle Byt namn på cirkel Remove circle Menu for removing a circle Ta bort cirkel Open all in new window Öppna alla i nytt fönster Core /me offers friendship, "%1" /me erbjuder vänskap, "%1" Invalid Tox ID Error while sending friendship request Ogiltigt Tox-ID You need to write a message with your request Error while sending friendship request Du måste skriva ett meddelande med din förfrågan Your message is too long! Error while sending friendship request Ditt meddelande är för långt! Friend is already added Error while sending friendship request Vän är redan tillagd Groupchat %1 Gruppchatt %1 DesktopNotify New message Nytt meddelande Incoming file transfer Inkommande filöverföring Friend request received Vänförfrågan mottagen New group message Nytt gruppmeddelande Group invite received Gruppinbjudan mottagen FileTransferWidget Form Formulär 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 ETA:10:10 Filename Filnamn Waiting to send... file transfer widget Väntar på att skicka... Accept to receive this file file transfer widget Acceptera för att ta emot den här filen Location not writable Title of permissions popup Plats ej skrivbar You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Du har inte tillåtelse att skriva till platsen. Välj en annan, eller avbryt spara-dialogen. Save a file Title of the file saving dialog Spara en fil Paused file transfer widget Pausad Resuming... file transfer widget Återuppta... Open file Öppna fil Open file directory Öppna filkatalog Pause transfer Pausa överföring Cancel transfer Avbryt överföring Resume transfer Återuppta överföring Accept transfer Acceptera överföring Remote Paused file transfer widget Fjärröverföring pausad FilesForm Downloads Nedladdningar Uploads Uppladdningar Transferred Files "Headline" of the window Överförda filer FriendListWidget Today Idag Yesterday Igår Last 7 days Senaste 7 dagarna This month Denna månad Older than 6 Months Äldre än 6 månader Never Aldrig FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Vänförfrågning Someone wants to make friends with you Någon vill bli vän med dig User ID: Användar-ID: Friend request message: Better translation? Vänförfrågningsmeddelande: Accept Accept a friend request Acceptera Reject Reject a friend request Avvisa FriendWidget Set alias... Ange alias... Auto accept files from this friend context menu entry Acceptera automatiskt filer från denna vän Invite to group Menu to invite a friend to a groupchat Bjud in till grupp Remove friend Menu to remove the friend from our friendlist Ta bort vän Choose an auto accept directory popup title Hmm, hard one. Got any better? Välj en acceptera-automatiskt-katalog Open chat in new window Öppna chatt i nytt fönster Remove chat from this window Radera chatt från detta fönster To new group Till ny grupp Invite to group '%1' Bjud in till grupp '%1' Move to circle... Menu to move a friend into a different circle Flytta till cirkel... To new circle Till ny cirkel Remove from circle '%1' Ta bort från cirkel '%1' Move to circle "%1" Flytta till cirkel "%1" Show details Visa detaljer New message Nytt meddelande Online Tillgänglig Away Borta Busy Upptagen Offline Otillgänglig GeneralForm General Allmänt Choose an auto accept directory popup title Välj en acceptera-automatiskt-katalog GeneralSettings General Settings Allmänna inställningar The translation may not load until qTox restarts. Översättning läses inte in innan qTox startas om. Language: Språk: Show system tray icon Visa ikon i systemfältet Enable light tray icon. toolTip for light icon setting Aktivera Ljus-ikon i aktivitetsfältet. Light icon Ljus-ikon qTox will start minimized in tray. toolTip for Start in tray setting qTox kommer att starta minimerad i verktygsfältet. Start in tray Starta i bakgrunden After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Efter att du tryckt på stäng (X) kommer qTox att minimeras till systemfältet, istället för att stängas. Close to tray Stäng till bakgrund After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Efter att du tryckt på minimera (_) kommer qTox att minimeras till systemfältet, istället för aktivitetsfältet för systemet. Minimize to tray Minimera till bakgrund Autostart Automatisk uppstart Set where files will be saved. Ange var filer ska sparas. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Du kan ställa in detta för varje enskild vän genom att högerklicka på dem. Your status is changed to Away after set period of inactivity. Din status ändras till Borta efter inställd tid av inaktivitet. Auto away after (0 to disable): Automatisk borta efter (0 för att avaktivera): Show contacts' status changes Visa kontakters statusändringar Set to 0 to disable Sätt 0 för att avaktivera Autoaccept files Acceptera filer automatiskt Start qTox on operating system startup (current profile). Starta qTox vid operativsystemets uppstart (nuvarande profil). Default directory to save files: Standardkatalog för att spara filer: Check for updates Sök efter uppdateringar Spell checking Stavningskontroll Max autoaccept file size (0 to disable): Max filstorlek att ta emot automatiskt (0 för att inaktivera): MB MB GenericChatForm Send message Skicka meddelande Smileys Humörsymboler Send file(s) Skicka fil(er) Save chat log Spara chattlogg Clear displayed messages Ta bort visade meddelanden Cleared Borttaget Send a screenshot Skicka en skärmdump Quote selected text Citera markerad text Copy link address Kopiera länkadress Confirmation Bekräftelse You are sure that you want to clear all displayed messages? Vill du verkligen ta bort alla visade meddelanden? Search in text Sök i text Go to current date Gå till aktuellt datum Load chat history... Läser in chatthistorik... Export to file Exportera till fil GenericNetCamView Tox video Tox-video Show Messages Visa meddelanden Hide Messages Göm meddelanden Full Screen Helskärm Toggle video preview Videoförhandsgranskning på/av Mute audio Stäng av ljudet Mute microphone Stäng av mikrofon End video call Avsluta videosamtal Exit full screen Avsluta helskärmsläge GroupChatForm %1 has set the title to %2 %1 har angett titeln som %2 %1 has joined the group %1 har anslutit till gruppen %1 is now known as %2 %1 är nu känd som %2 %1 has left the group %1 har lämnat gruppen %n user(s) in chat Number of users in chat mute tysta unmute slå på ljud GroupInviteForm Groups Grupper Create new group Skapa ny grupp Group invites Gruppinbjudningar GroupInviteWidget Invited by %1 on %2 at %3. Inbjuden av %1 den %2 kl. %3. Join Gå med Decline Avböj GroupWidget Set title... Ange titel... Quit group Menu to quit a groupchat Lämna grupp Open chat in new window Öppna chatt i nytt fönster Remove chat from this window Ta bort chatt från detta fönster %n user(s) in chat Number of users in chat New Message Nytt meddelande Online Tillgänglig IdentitySettings Public Information Publik information Tox ID Tox-ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Detta gäng tecken berättar andra Tox-klienter din kontaktinformation. Dela den med dina vänner för att kommunicera. Your Tox ID (click to copy) Ditt Tox-ID (klicka för att kopiera) Rename rename profile button Byt namn Export export profile button Exportera Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Tillåter dig att exportera din Tox-profil till en fil. Profilen innehåller inte din historik. Delete delete profile button Radera This QR code contains your Tox ID. You may share this with your friends as well. Denna QR-kod innehåller ditt Tox-ID. Du kan dela det med dina vänner. Save image Spara bild Copy image Kopiera bild Server Server Hide my name from the public list Dölj mitt namn från den offentliga listan Register Registrera Your password Ditt lösenord Update Uppdatera Profile Profil Rename profile. tooltip for renaming profile button Byt namn på profil. Delete profile. delete profile button tooltip Radera profil. Go back to the login screen tooltip for logout button Gå tillbaka till inloggningsskärmen Logout import profile button Logga ut Remove password Ta bort lösenord Change password Ändra lösenord Register on ToxMe Registrera på ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Namn för tjänsten ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Valfritt. Något om dig. Eller din katt. Optional. Something about you. Or your cat. Tooltip for the Biography field. Valfritt. Något om dig. Eller din katt. ToxMe service to register on. ToxMe-tjänst att registrera sig på. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Om ej inställt, visas ToxMe-poster offentligt. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Ta bort ditt lösenord och kryptering från din profil. Name input Namn inmatning Name visible to contacts Namn synligt för kontakter Status message input Statusmeddelande-inmatning Status message visible to contacts Statusmeddelande synligt för kontakter Your Tox ID Ditt Tox-ID Save QR image as file Spara QR-bild som fil Copy QR image to clipboard Kopiera QR-bild till urklipp ToxMe username to be shown on ToxMe ToxMe-användarnamn att visas på ToxMe Optional ToxMe biography to be shown on ToxMe Valfri ToxMe-biografi som visas på ToxMe ToxMe service address ToxMe-tjänstadress Visibility on the ToxMe service Synlighet på tjänsten ToxMe Password Lösenord Update ToxMe entry Uppdatera ToxMe-inlägg Rename profile. Byt namn på profil. Delete profile. Ta bort profil. Export profile Exportera profil Remove password from profile Ta bort lösenord från profil Change profile password Ändra profillösenord My name: Mitt namn: My status: Min status: My username Mitt användarnamn My biography Min biografi My profile Min profil LoadHistoryDialog Load History Dialog Läs in historik Load history Läs in historik from från to till (about 100 messages are loaded) (omkring 100 meddelanden är inlästa) Select Date Dialog Välj datum Select a date Välj ett datum LoginScreen Username: Användarnamn: Password: Lösenord: Confirm: Bekräfta: Password strength: %p% Lösenordets styrka: %p% Create Profile Skapa profil If the profile does not have a password, qTox can skip the login screen Om profilen inte har ett lösenord, kan qTox hoppa över inloggningsskärmen Load automatically Läs in automatiskt Import Importera Load Läs in New Profile Ny profil Load Profile Läs in profil Couldn't create a new profile Kunde inte skapa en ny profil The username must not be empty. Användarnamnet får inte vara tomt. The password must be at least 6 characters long. Lösenordet måste vara minst 6 tecken långt. The passwords you've entered are different. Please make sure to enter same password twice. Lösenorden du angav är olika. Var noga med att ange samma lösenord två gånger. A profile with this name already exists. En profil med detta namn finns redan. Password protected profiles can't be automatically loaded. Lösenordsskyddade profiler kan inte laddas automatiskt. Couldn't load profile Kunde inte läsa in profil There is no selected profile. You may want to create one. Det finns ingen vald profil. Du kanske vill skapa en. Couldn't load this profile Kunde inte läsa in denna profil This profile is already in use. Denna profil är redan i bruk. Wrong password. Fel lösenord. Username input field Inmatningsfält för användarnamn Password input field, you can leave it empty (no password), or type at least 6 characters Lösenordets inmatningsfält, du kan lämna det tomt (inget lösenord), eller ange minst 6 tecken Password confirmation field Fält för lösenordsbekräftelse Create a new profile button Skapa en ny profil-knapp Profile list Profillista List of profiles Lista över profiler Password input Lösenordsinmatning Load automatically checkbox Läs in automatiskt Import profile Importera profil Load selected profile button Läs in vald profil New profile creation page Sidan för att skapa en ny profil Loading existing profile page Laddar befintlig profilsida MainWindow Your name Ditt namn Your status Din status Add friends Lägg till vänner Create a group chat Skapa en chattgrupp View completed file transfers Se färdiga filöverföringar Change your settings translated as "change settings"; seems to be simpler this way Ändra dina inställningar Close Stäng ... ... Open profile Öppen profil Open profile page when clicked Öppna profilsida när du klickar Status message input Statusmeddelande-inmatning Set your status message that will be shown to others Ange ditt statusmeddelande som visas för andra Status Status Set availability status Ange tillgänglighetsstatus Contact search Kontaktsökning Contact search input for known friends Kontaktsökningsinmatning för kända vänner Sorting and visibility Sortering och synlighet Set friends sorting and visibility Ställa in vänsortering och synlighet Open Add friends page Öppna sidan Lägg till vänner Groupchat Gruppchatt Open groupchat management page Öppna gruppchatt-hanteringssidan File transfers history Filöverföringshistorik Open File transfers history Öppna filöverföringshistorik Settings Inställningar Open Settings Öppna inställningar Nexus View OS X Menu bar Visa Window OS X Menu bar Fönster Minimize OS X Menu bar Minimera Bring All to Front OS X Menu bar Flytta längst fram Exit Fullscreen Avsluta helskärmsläge Enter Fullscreen Använd helskärm NotificationEdgeWidget Unread message(s) Oläst meddelande Olästa meddelanden PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK AKTIVERAD PrivacyForm Privacy Integritet Confirmation Bekräftelse Do you want to permanently delete all chat history? Vill du permanent ta bort all chatthistorik? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Dina vänner kommer att kunna se när du skriver. Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Chatthistorik fortfarande under utveckling. Ändringar i sparningsformatet är möjliga, vilket kan resultera i dataförlust. Send typing notifications Skicka skrivaviseringar Keep chat history Bevara chatthistorik NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam är en del av ditt Tox-ID. Om du blir spammad med vänförfrågningar, bör du ändra din NoSpam. Människor kommer att kunna lägga till dig med ditt gamla ID, men du behåller dina nuvarande vänner. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam är en del av ditt ID som kan ändras efter behag. Om du blir spammad med vänförfrågningar, ändra NoSpam. Generate random NoSpam Generera slumpmässiga NoSpam Privacy Integritet BlackList Blocklista Filter group message by group member's public key. Put public key here, one per line. Filtrera gruppmeddelande genom gruppmedlems allmänna nyckel. Ange den offentliga nyckeln här, en per rad. Profile Failed to derive key from password, the profile won't use the new password. Misslyckades att härleda nyckel från lösenord, profilen kommer inte använda det nya lösenordet. Couldn't change password on the database, it might be corrupted or use the old password. Kunde inte byta lösenord på databasen, den kan vara trasig eller använda det gamla lösenordet. Toxing on qTox Toxar på qTox ProfileForm Current profile: Aktuell profil: Remove Ta bort Choose a profile picture Välj en profilbild Error Fel Unable to open this file. Det gick inte att öppna filen. Unable to read this image. Det gick inte att läsa denna bild. The supplied image is too large. Please use another image. Medföljande bilden är för stor. Använd en annan bild. Rename "%1" renaming a profile Byt namn på "%1" Couldn't rename the profile to "%1" Kunde inte byta namn på profilen till "%1" Location not writable Title of permissions popup Plats ej skrivbar You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Du har inte tillåtelse att skriva till platsen. Välj en annan, eller avbryt spara-dialogen. Failed to copy file Det gick inte att kopiera filen The file you chose could not be written to. Filen du valde kunde inte skrivas till. Really delete profile? deletion confirmation title Vill du verkligen ta bort profil? Are you sure you want to delete this profile? deletion confirmation text Är du säker på att du vill ta bort denna profil? Files could not be deleted! deletion failed title Filer kunde inte tas bort! Save save qr image Spara Save QrCode (*.png) save dialog filter Spara QrCode (*.png) Nothing to remove Ingenting att ta bort Your profile does not have a password! Din profil har inte ett lösenord! Really delete password? deletion confirmation title Vill du verkligen ta bort lösenord? Please enter a new password. Vänligen ange ett nytt lösenord. Register (processing) Registrera (bearbetning) Update (processing) Uppdatering (bearbetning) Done! Klart! Account %1@%2 updated successfully Konto %1@%2 uppdaterats Successfully added %1@%2 to the database. Save your password Lade framgångsrikt %1@%2 till databasen. Spara ditt lösenord Toxme error Toxme-fel Register Registrera dig Update Uppdatering Change password button text Ändra lösenord Set profile password button text Ange profillösenord Current profile location: %1 Aktuell profilplats: %1 Couldn't change password Kunde inte ändra lösenord This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Denna grupp tecken berättar för andra Tox-klienter hur man kontaktar dig. Dela den med dina vänner för att kommunicera. Detta ID inkluderar NoSpam-koden (i blått) och checksum (i grått). Empty path is unavaliable Tom sökväg är inte tillgänglig Failed to rename Det gick inte att byta namn Profile already exists Profilen finns redan A profile named "%1" already exists. En profil med namnet "%1" finns redan. Empty name Inget namn Empty name is unavaliable Tomt namn är inte tillgängligt Empty path Tom sökväg Couldn't change password on the database, it might be corrupted or use the old password. Det gick inte att byta lösenord på databasen, den kan vara trasig eller använda det gamla lösenordet. Export profile Exportera profil Tox save file (*.tox) save dialog filter Tox-fil (*.tox) The following files could not be deleted: deletion failed text part 1 Följande filer kunde inte tas bort: Please manually remove them. deletion failed text part 2 Ta bort dem manuellt. Are you sure you want to delete your password? deletion confirmation text Är du säker på att du vill ta bort ditt lösenord? Images (%1) filetype filter Bilder (%1) ProfileImporter Import profile import dialog title Importera profil Tox save file (*.tox) import dialog filter Tox-sparningsfil (*.tox) Ignoring non-Tox file popup title Ignorerar icke-Toxfil Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Varning: Du har valt en fil som inte är en Tox-sparafil; Ignorerar. Profile already exists import confirm title Profil finns redan A profile named "%1" already exists. Do you want to erase it? import confirm text En profil med namnet "%1" finns redan. Vill du ta bort det? File doesn't exist Fil finns inte Profile doesn't exist Profil finns inte Profile imported Profilen har importerats %1.tox was successfully imported %1.tox har importerats QApplication Ok OK Cancel Avbryt Yes Ja No Nej LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Kunde inte lägga till vän %1 is not a valid Toxme address. %1 är inte en giltig Toxme-adress. You can't add yourself as a friend! When trying to add your own Tox ID as friend Du kan inte lägga till dig själv som vän! QObject Tox URI to parse Tox-URI för att tolka Starts new instance and loads specified profile. Startar ny instans och laddar angiven profil. profile profil Default Standard Blue Blå Olive Olivgrönt Red Röd Violet Violett Incoming call... Inkommande samtal... Server doesn't support Toxme Server stöder inte Toxme You're making too many requests. Wait an hour and try again Du gör alltför många förfrågningar. Vänta en timme och försök igen This name is already in use Detta namn används redan This Tox ID is already registered under another name Detta Tox-ID är redan registrerat under ett annat namn Please don't use a space in your name Använd inte ett mellanrum i ditt namn Password incorrect Felaktigt lösenord You can't use this name Du kan inte använda detta namn Name not found Namn hittades inte Tox ID not sent Tox-ID skickades inte That user does not exist Användaren finns inte %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 här! Toxa mig kanske? Error Fel qTox couldn't open your chat logs, they will be disabled. qTox kunde inte öppna dina chattloggar, de kommer att avaktiveras. None No camera device set Ingen Desktop Desktop as a camera input for screen sharing Skrivbord Problem with HTTPS connection Problem med HTTPS-anslutning Internal ToxMe error Internt ToxMe-fel Reformatting text in progress.. Formaterar om text... Starts new instance and opens the login screen. Startar ny instans och öppnar loginskärmen. Dark Mörk Dark blue Mörkblå Dark olive Mörk oliv Dark red Mörkröd Dark violet Mörk lila Failed to load profile automatically. Kunde inte läsa in profil automatiskt. online contact status tillgänglig away contact status borta busy contact status upptagen offline contact status frånkopplad blocked contact status blockerad RemoveFriendDialog Remove friend Ta bort vän Also remove chat history Ta också bort chatthistorik Remove Ta bort Are you sure you want to remove %1 from your contacts list? Är du säker du vill ta bort %1 från kontaktlistan? Remove all chat history with the friend if set Ta bort all chatthistorik med vännen om den är inställd ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Klicka och dra för att markera en region. Tryck på %1 för att dölja/visa qTox-fönster, eller %2 för att avbryta. Space [Space] key on the keyboard Mellanrum Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Tryck på %1 för att skicka en skärmdump av urvalet, %2 för att visa/dölja qTox-fönster eller %3 för att avbryta. Enter [Enter] key on the keyboard Enter-tangent SearchForm The text could not be found. Texten kunde inte hittas. Start Starta SearchSettingsForm Form Formulär Start search: Börja sök: from the end från slutet from the beginning från början after date efter datum before date före datum 00.00.0000 00.00.0000 Case sensitive Skiftlägeskänslig Whole words only Endast hela ord Use regular expressions Använd Regular Expression SetPasswordDialog Set your password Ange ditt lösenord Confirm: Bekräfta: Password: Lösenord: Password strength: %p% Lösenordets styrka: %p% The password is too short Lösenordet är för kort The password doesn't match. Lösenordet matchar inte. Confirm password Bekräfta lösenord Confirm password input Bekräfta lösenordsinmatning Password input Lösenordsinmatning Password input field, minimum 6 characters long Lösenordsinmatningsfält, minst 6 tecken långt Settings Circle #%1 Cirkel #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Lägga till en vän Do you want to add %1 as a friend? Vill du lägga till %1 som en vän? User ID: Användar-ID: Friend request message: Vänförfrågningsmeddelande: Send Send a friend request Skicka Cancel Don't send a friend request Avbryt UserInterfaceForm None Inget User Interface Användargränssnitt UserInterfaceSettings Chat Chatt Base font: Bas-typsnitt: px px Size: Storlek: New text styling preference may not load until qTox restarts. Ny textstilsinställning kanske inte läses in förrän qTox startas om. Text Style format: Textstilsformat: Select text styling preference. Välj textstilsinställning. Plaintext Klartext Show formatting characters Visa formateringstecken Don't show formatting characters Visa inte formateringstecken New message Nytt meddelande Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Öppna qTox fönster när du får ett nytt meddelande och inga fönster är öppna ännu. Open window Öppna fönster Contact list Kontaktlista If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Om ifylld, kommer gruppchattar att placeras överst i vänlistan, annars kommer de att placeras nedanför uppkopplade vänner. Place groupchats at top of friend list Placera gruppchattar högst upp i vänlistan Your contact list will be shown in compact mode. toolTip for compact layout setting Din kontaktlista kommer att visas i kompaktläge. Compact contact list Kompakt kontaktlista Multiple windows mode Flerfönsterläge Open each chat in an individual window Öppna varje chatt i enskilt fönster Emoticons Humörsymboler Use emoticons Använd humörsymboler Smiley Pack: Text on smiley pack label Humörsymbol-paket: Emoticon size: Humörsymbolstorlek: px px Theme Tema Style: Stil: Theme color: Temafärg: Timestamp format: Tidsstämpelformat: Date format: Datumformat: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Om den är aktiverad kommer varje kontakt utan en avatar att ha en genererad avatar baserat på deras Tox-ID istället för en standardbild. Kräver omstart att tillämpa. Use identicons instead of empty avatars Använd identicons istället för tomma avatarer Use colored nicknames in chats Använd färgade användarnamn i chattar Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Visar avisering när du får ett nytt meddelande och fönstret inte är öppet. Notify Avisera Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Avisera nya meddelanden i gruppchatt endast vid omnämnande. Group chats only notify when mentioned Gruppchattar aviserar endast vid omnämnande Play sound Spela upp ljud Play sound while Busy Spela upp ljud medan du är upptagen Notify via desktop notifications Avisera via skrivbordsavisering Hide message sender and contents Dölj meddelandes avsändare och innehåll Widget Online Button to set your status to 'Online' Tillgänglig Away Button to set your status to 'Away' Borta Busy Button to set your status to 'Busy' Upptagen toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore misslyckades att starta med dina proxy-inställningar. qTox kan inte köras; ändra dina inställningar och starta om. Couldn't request friendship Kunde inte begära vänskap Message failed to send Misslyckades att skicka meddelande Status Status toxcore failed to start, the application will terminate after you close this message. toxcore kunde inte startas, programmet kommer att avslutas efter att du stängt det här meddelandet. Executable file popup title Körbar fil You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Du har bett qTox att öppna en körbar fil. Körbara filer kan potentiellt skada din dator. Är du säker vill öppna den här filen? Your name Ditt namn Groupchat #%1 Gruppchatt #%1 Create new group... Skapa ny grupp... Add new circle... Lägg till ny cirkel... %n New Friend Request(s) %n ny vänförfrågning %n nya vänförfrågningar %n New Group Invite(s) %n ny gruppinbjudan %n nya Gruppinbjudningar By Name Efter namn By Activity Efter aktivitet All Alla Online Tillgänglig Offline Frånkopplad Friends Vänner Groups Grupper Search Contacts Sök kontakter Logout Tray action menu to logout user Logga ut Exit Tray action menu to exit tox Avsluta Filter... Filtrera... File Fil Edit Redigera Contacts Kontakter Change Status Ändra status Edit Profile Redigera profil Log out Logga ut Add Contact... Lägg till kontakt... Next Conversation Nästa konversation Previous Conversation Föregående konversation Show Tray action menu to show qTox window Visa Add friend title of the window Lägg till vän Group invites title of the window Gruppinbjudningar File transfers title of the window Filöverföringar Settings title of the window Inställningar My profile title of the window Min profil Failed to send file "%1" Kunde inte skicka filen "%1" File sent Fil skickad sent you a friend request. skickar en vänförfrågan. invites you to join a group. bjuder in dig till en grupp. qTox/translations/sw.ts000066400000000000000000003123351415623743500155540ustar00rootroot00000000000000 AVForm Audio/Video Default resolution Disabled Select region Screen %1 Audio Settings Gain Playback device Use slider to set volume of your speakers. Capture device Volume Video Settings Video device Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Resolution Rescan devices Test Sound Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Medium (32 kbps) Low (16 kbps) Very low (8 kbps) Threshold AboutForm About Original author: %1 You are using qTox version %1. Commit hash: %1 toxcore version: %1 Qt version: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` bug-tracker Replaces `%1` in the `A list of all known…` Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` contributors Replaces `%1` in `See a full list of…` AboutFriendForm Dialog username status message Used aliases: HISTORY OF ALIASES Automatically accept files from contact if set Auto accept files Default directory to save files: Auto accept for this contact is disabled Auto accept call: Manual Audio Audio + Video Automatically accept group chat invitations from this contact if set. Auto accept group invites Remove history (operation can not be undone!) Notes Input field for notes about the contact You can save comment about this contact here. History removed Choose an auto accept directory popup title <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version License Authors Known Issues Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Invalid Tox ID format Send friend request Add a friend Friend requests Accept Reject Couldn't add friend Tox ID, either 76 hexadecimal characters or name@example.com Type in Tox ID of your friend Friend request message Type message to send with the friend request or leave empty to send a default message %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to either 76 hexadecimal characters or name@example.com Tox ID format description Message The message you send in friend requests Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not IMPORTANT NOTE Reset settings All settings will be reset to default. Are you sure? Yes No Call active popup title You can't disconnect while a call is active! popup text Save File Logs (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Make Tox portable Reset to default settings Portable Connection Settings Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP Proxy type: Address: Text on proxy addr label Port: Text on proxy port label None SOCKS5 HTTP Reconnect reconnect button Debug Export Debug Log Copy Debug Log Enable LAN discovery ChatForm Send a file qTox wasn't able to open %1 Unable to open Bad idea %1 calling Calling %1 Failed to open temporary file Temporary file for screenshot qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch Call with %1 ended. %2 Call duration: %1 is typing Copy You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Start audio call End audio call Cancel audio call Accept audio call Can't start video call Start video call End video call Cancel video call Accept video call Sound can be disabled only during a call Unmute call Mute call Microphone can be muted only during a call Unmute microphone Mute microphone ChatLog Copy Select all pending ChatTextEdit Type your message here... CircleWidget Rename circle Menu for renaming a circle Remove circle Menu for removing a circle Open all in new window Core /me offers friendship, "%1" Invalid Tox ID Error while sending friendship request You need to write a message with your request Error while sending friendship request Your message is too long! Error while sending friendship request Friend is already added Error while sending friendship request Groupchat %1 DesktopNotify New message Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen 10Mb Ausgelassen 0kb/s Ausgelassen ETA:10:10 Ausgelassen Filename Ausgelassen Waiting to send... file transfer widget Accept to receive this file file transfer widget Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Resuming... file transfer widget Cancel transfer Pause transfer Paused file transfer widget Open file Open file directory Resume transfer Accept transfer Save a file Title of the file saving dialog Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Downloads Uploads FriendListWidget Today Yesterday Last 7 days This month Older than 6 Months Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Someone wants to make friends with you User ID: Friend request message: Accept Accept a friend request Reject Reject a friend request FriendWidget Invite to group Menu to invite a friend to a groupchat Move to circle... Menu to move a friend into a different circle To new circle Remove from circle '%1' Move to circle "%1" Open chat in new window Remove chat from this window To new group Invite to group '%1' Set alias... Auto accept files from this friend context menu entry Remove friend Menu to remove the friend from our friendlist Show details Choose an auto accept directory popup title New message Online Away Busy Offline Ausgelassen GeneralForm General Choose an auto accept directory popup title GeneralSettings General Settings The translation may not load until qTox restarts. Language: Show system tray icon Enable light tray icon. toolTip for light icon setting Light icon qTox will start minimized in tray. toolTip for Start in tray setting Start in tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Close to tray After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Minimize to tray Autostart Set where files will be saved. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Autoaccept files Set to 0 to disable Your status is changed to Away after set period of inactivity. Auto away after (0 to disable): Show contacts' status changes Start qTox on operating system startup (current profile). Default directory to save files: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Smileys Send file(s) Send a screenshot Save chat log Clear displayed messages Cleared Quote selected text Copy link address Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Export to file GenericNetCamView Tox video Show Messages Hide Messages Full Screen Toggle video preview Mute audio Mute microphone End video call Exit full screen GroupChatForm %1 has set the title to %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Create new group Group invites GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Set title... Open chat in new window Remove chat from this window Quit group Menu to quit a groupchat %n user(s) in chat Number of users in chat New Message Online IdentitySettings Public Information Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Your Tox ID (click to copy) Profile Rename profile. tooltip for renaming profile button Go back to the login screen tooltip for logout button Logout import profile button Remove password Change password This QR code contains your Tox ID. You may share this with your friends as well. Save image Copy image Rename rename profile button Delete profile. delete profile button tooltip Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Export export profile button Delete delete profile button Server Hide my name from the public list Register Your password Update Register on ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. Delete profile. Export profile Remove password from profile Change profile password My name: My status: My username My biography My profile LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Password: Confirm: Password strength: %p% Create Profile If the profile does not have a password, qTox can skip the login screen Load automatically Load Load Profile New Profile Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Password protected profiles can't be automatically loaded. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Import Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Your status ... Ausgelassen Add friends Create a group chat View completed file transfers Change your settings Close Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings Open Settings Nexus View OS X Menu bar Window OS X Menu bar Minimize OS X Menu bar Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox ProfileForm Choose a profile picture Error Rename "%1" renaming a profile Unable to open this file. Current profile: Remove Unable to read this image. The supplied image is too large. Please use another image. Couldn't rename the profile to "%1" Location not writable Title of permissions popup You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Nothing to remove Your profile does not have a password! Really delete password? deletion confirmation title Please enter a new password. Are you sure you want to delete this profile? deletion confirmation text Save save qr image Save QrCode (*.png) save dialog filter Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Change password button text Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Cancel Yes No LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Red Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! None No camera device set Desktop Desktop as a camera input for screen sharing Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status away contact status busy contact status offline contact status blocked contact status RemoveFriendDialog Remove friend Also remove chat history Remove Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Password: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Do you want to add %1 as a friend? User ID: Friend request message: Send Send a friend request Cancel Don't send a friend request UserInterfaceForm None User Interface UserInterfaceSettings Chat Base font: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme Style: Theme color: Timestamp format: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' Away Button to set your status to 'Away' Busy Button to set your status to 'Busy' toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File Edit Profile Change Status Log out Edit Logout Tray action menu to logout user Exit Tray action menu to exit tox Filter... Contacts Add Contact... Next Conversation Previous Conversation Executable file popup title You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Couldn't request friendship Status Your name Message failed to send Create new group... Add new circle... %n New Friend Request(s) %n New Group Invite(s) By Name By Activity All Online Offline Ausgelassen Friends Groups Search Contacts Groupchat #%1 Show Tray action menu to show qTox window Add friend title of the window Group invites title of the window File transfers title of the window Settings title of the window My profile title of the window Failed to send file "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/ta.ts000066400000000000000000003660011415623743500155260ustar00rootroot00000000000000 AVForm Audio/Video ஒலி/காணொளி Default resolution இயல்புநிலை தெளிவுத்திறன் Disabled முடக்கப்பட்டது Select region பகுதியை தேர்வு செய்க Screen %1 திரை %1 Audio Settings ஒலி பயன்பாட்டமைப்பு Gain பெருக்கம் Playback device மறுஇயக்க சாதனம் Use slider to set volume of your speakers. நகர்வுகோலைப் பயன்படுத்தி உங்கள் ஒலிப்பான்களின் ஒலியளவை அமைக்கவும். Capture device பிடிப்புச் சாதனம் Volume ஒலியளவு Video Settings காணொளி பயன்பாட்டமைப்பு Video device காணொளிச் சாதனம் Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. கேமராவி்ன் தெளிவுத்திறனை அமைக்கவும். மதிப்பின் உயர்வுக்கேற்ப உங்கள் நண்பர்களின் காணொளித் தரம் அமையும். ஆனால் உயரிய காணொளித் தரத்திற்கேற்ப உயரிய இணையத் திறனும் தேவை. சில நேரங்களில் உங்கள் இணைய இணைப்பு அதிக காணொளித் தரத்தைக் கையாளப் போதாது இருக்கலாம் இது காணொளி அழைப்புகளில் பிரச்சனைகளுக்கு வழிவகுக்கலாம். Resolution தெளிவுத்திறன் Rescan devices சாதனங்களை மறுமுறை துருவுக Test Sound ஒலியை சோதனை செய் Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality ஒலித்தரம் Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Medium (32 kbps) Low (16 kbps) Very low (8 kbps) Threshold AboutForm About குறிப்பு Original author: %1 மூல படைப்பாளர்: %1 You are using qTox version %1. நீங்கள் பயன்படுத்துவது qTox பதிப்பு %1 Commit hash: %1 கொள்கல மாற்ற தற்சார்பு முகவரி: %1 toxcore version: %1 toxcore பதி்ப்பு: %1 Qt version: %1 Qt பதி்ப்பு: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` முன்னறிந்த சிக்கல்களின் பட்டியலை Github இல் எங்கள் %1 கொண்டு அறியலாம். qTox இல் தாங்கள் சிக்கலோ பாதுகாப்புத் தளர்வோ கண்டறிந்தால் அதைத் தயவுசெய்து எங்கள் %2 பயனரியக்குவலைதள கட்டுரையின் வழிகாட்டுதல்களின்படி பதிவு செய்யவும். Click here to report a bug. நிரற்பிழை ஒன்றனை பதிய இங்கு சொடுக்குக. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` %1 முழுப்பட்டியலை Github இல் காண்க bug-tracker Replaces `%1` in the `A list of all known…` நிரற்பிழை கண்காணி Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` பயனுள்ள நிரற்பிழை பதிவுகளை எழுதுதல் contributors Replaces `%1` in `See a full list of…` பங்களிப்பாளர்கள் AboutFriendForm Dialog உரையாடல் username பயனர்பெயர் status message நிலைச்செய்தி Used aliases: பயன்பாட்டலுள்ள மாற்றுப்பெயர்கள்: HISTORY OF ALIASES இதுவரை பயன்படுத்தப்பட்ட மாற்றுப்பெயர்கள் Automatically accept files from contact if set அவ்வாறு அமைக்கப்பட்டிருந்தால் தொடர்பிடமிருந்து கோப்புகளைத் தன்னிச்சையாக ஏற்க Auto accept files கோப்புகள் தன்னிச்சையேற்பு Default directory to save files: கோப்புகளை சேமிக்க பொது கோப்பகம்: Auto accept for this contact is disabled இந்தத் தொடர்புக்கான தன்னிச்சையேற்பு முடக்கப்பட்டுள்ளது Auto accept call: அழைப்பு தன்னிச்சையேற்பு: Manual கைமுறைத் தேர்வு Audio ஒலி Audio + Video ஒலி + காணொளி Automatically accept group chat invitations from this contact if set. அவ்வாறு அமைக்கப்பட்டிருந்தால் தொடர்பிடமிருந்து தோழர்க்குழு அரட்டை அழைப்புகளைத் தன்னிச்சையாக ஏற்க. Auto accept group invites தோழர்க்குழு அழைப்புகளை தன்னிச்சையாக ஏற்க Remove history (operation can not be undone!) வரலாற்றை அகற்று (செயல்பாடு செயல்மீள இயலாது!) Notes குறிப்புகள் Input field for notes about the contact தொடர்பு பற்றிய குறிப்புகளுக்கான உள்ளீட்டுப் பகுதி You can save comment about this contact here. இந்தத் தொடர்பைப் பற்றிய கருத்துகளை இங்கே சேமிக்கலாம். History removed வரலாறு அகற்றப்பட்டது Choose an auto accept directory popup title தன்னிச்சையேற்புக்கான கோப்பகத்தைத் தேர்வு செய்க <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version பதிப்பு License உரிமம் Authors படைப்பாளர்கள் Known Issues முன்னறிந்த சிக்கல்கள் Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends தோழர் சேர்க்கை Invalid Tox ID format தவறான Tox அடையாளச்சர வடிவமைப்பு Send friend request தோழராக்க வேண்டுகோளை அனுப்புக Add a friend தோழரைச் சேர்க Friend requests தோழராக்க வேண்டுகோள்கள் Accept ஏற்றுக்கொள் Reject நிராகரி Couldn't add friend தோழரைச் சேர்க்க இயலவில்லை Tox ID, either 76 hexadecimal characters or name@example.com Tox அடையாளச்சரம், 76 அறுபதின்ம எழுத்துகள் அல்லது name@example.com Type in Tox ID of your friend தங்கள் தோழரின் Tox அடையாளச்சரத்தை உள்ளிடுக Friend request message தோழராக்க வேண்டுகோள் செய்தி Type message to send with the friend request or leave empty to send a default message தோழராக்க வேண்டுகோளுடன் அனுப்ப செய்தியை உள்ளிடுக, அல்லது பொதுமையான செய்தியை அனுப்ப வெறுமையாக விடவும் %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend தங்களையே தமக்குத் தோழராக்க இயலாது! Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to Tox அடையாளச்சரம் either 76 hexadecimal characters or name@example.com Tox ID format description 76 அறுபதின்ம எழுத்துகள் அல்லது name@example.com Message The message you send in friend requests செய்தி Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! இது %1! Tox செய்வோமா? Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced மேனிலைச் செயல்பாடுகள் Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. யாது செய்கின்றீர் என்பதை %1 அறிந்தால் தவிர, இங்கு மாற்றங்கள் ஏதும் செய்ய %2. இங்கு செய்யப்படும் மாற்றங்கள் qTox இல் பிரச்சினைகளைத் தோற்றுவிக்கலாம் மற்றும் தங்கள் தகவல் சேமிப்புகள், உதாரணமாக அரட்டை வரலாறு, இழக்க நேரலாம். really நன்கு not வேண்டாம் IMPORTANT NOTE முக்கிய குறிப்பு Reset settings அமைப்புகளை மீட்டமை All settings will be reset to default. Are you sure? தற்போதைய அமைப்புகள் பொதுமை அளவுகளுக்கு மீட்டமைக்கப்படும். இது தங்களின் உறுதியான முடிவா? Yes ஆம் No இல்லை Call active popup title அழைப்பு செயல்பாட்டிலுள்ளது You can't disconnect while a call is active! popup text அழைப்பு செயல்பாட்டில் உள்ளபொழுது இணைப்பைத் துண்டிக்கவியலாது! Save File கோப்பைச் சேமி Logs (*.log) செயற்பதிவுகள் (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox வழக்கமான கட்டமைப்பு அடைவிற்கு பதிலாக நடப்பு அடைவில் அமைப்புத்தகவல்களைச் சேமி Make Tox portable Tox ஐ திரட்டிச்செல்ல ஏதுவாக்கு Reset to default settings அமைப்புகளை பொதுமை அளவுகளுக்கு மீட்டமை Portable திரட்டிச்செல்லுமை Connection Settings இணைப்பு அமைப்புகள் Enable IPv6 (recommended) Text on a checkbox to enable IPv6 IPv6 செயல்பாட்டை அனுமதி (பரிந்துரைக்கப்படுகிறது) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip இதை முடக்குதல், உதாரணத்திற்கு மீது செய்ய உதவும். ஆனால் பிணையத்திற்கு சுமை சேர்க்கும் என்பதால் தேவைப்படும் பொழுது மட்டும் குறி நீக்கவும். Enable UDP (recommended) Text on checkbox to disable UDP UDP செயல்பாட்டை அனுமதி (பரிந்துரைக்கப்படுகிறது) Proxy type: பதிலி வகை: Address: Text on proxy addr label முகவரி: Port: Text on proxy port label முகவரி நுழைவு எண்: None ஏதிலை SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button மீண்டுமிணை Debug பிழைதேடு Export Debug Log பிழைதேட்டுப் பதிவைக் கோப்பாகச் சேமி Copy Debug Log பிழைதேட்டுப் பதிவை இடைநிலைப் பிரதியெடு Enable LAN discovery ChatForm Send a file கோப்பு ஒன்றை அனுப்பு qTox wasn't able to open %1 qTox %1 ஐ திறக்க இயலவில்லை Unable to open திறக்க இயலவில்லை Bad idea இது சரியற்ற செயல்முனைப்பு %1 calling %1 அழைக்கிறார் Calling %1 %1 அழைக்கப்படுகிறார் Failed to open temporary file Temporary file for screenshot தற்காலிகக் கோப்பைத் திறக்க இயலவில்லை qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch qTox இனால் திரைத்தன்னிலைப்படத்தைச் சேமிக்க இயலவில்லை Call with %1 ended. %2 %1 உடனான அழைப்பு முடிவடைந்தது. %2 Call duration: அழைப்பு நிகழ்ந்த காலம் %1 is typing %1 தட்டச்சு செய்கிறார் Copy இடைநிலைப் பிரதியெடு You're trying to send a sequential file, which is not going to work! தொடர்முறைப்படிவக் கோப்பினை அனுப்ப நீங்கள் முயல்வது வினைப்பயன் பெறாது! %1 is now %2 e.g. "Dubslow is now online" %1 இப்பொழுது %2 Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call ஒலி அழைப்பைத் தொடங்க இயலாது Start audio call ஒலி அழைப்பைத் தொடங்கு End audio call ஒலி அழைப்பை முடி Cancel audio call ஒலி அழைப்பை ரத்துசெய் Accept audio call ஒலி அழைப்பை ஏற்க Can't start video call காணொளி அழைப்பைத் தொடங்க இயலாது Start video call காணொளி அழைப்பைத் தொடங்கு End video call Cancel video call காணொளி அழைப்பை ரத்துசெய் Accept video call காணொளி அழைப்பை ஏற்க Sound can be disabled only during a call ஒலியை அழைப்பின்போது மட்டுமே வினைமுடக்க இயலும் Unmute call அழைப்பை ஒலியியக்கு Mute call அழைப்பை ஒலியணை Microphone can be muted only during a call ஒலிவாங்கியை அழைப்பின்போது மட்டுமே ஒலியணைக்க இயலும் Unmute microphone ஒலிவாங்கியை ஒலியியக்கு Mute microphone ஒலிவாங்கியை ஒலியணை ChatLog Copy இடைநிலைப் பிரதியெடு Select all அனைத்தையும் தேர்வுசெய் pending நிலுவையிலுள்ளது ChatTextEdit Type your message here... உங்களது செய்தியை இங்கு உள்ளிடுவீர்... CircleWidget Rename circle Menu for renaming a circle வட்டத்தை மறுபெயரிடு Remove circle Menu for removing a circle வட்டத்தை அகற்று Open all in new window அனைத்தையும் புதிய சாளரத்தில் திற Core /me offers friendship, "%1" /me தோழராக விரும்புகிறார் , "%1" Invalid Tox ID Error while sending friendship request தவறான Tox அடையாளச்சரம் You need to write a message with your request Error while sending friendship request உங்கள் தோழராக்க கோரிக்கையுடன் ஒரு செய்தியை அனுப்ப வேண்டும் Your message is too long! Error while sending friendship request உங்கள் செய்தி மிக நீண்டுள்ளது! Friend is already added Error while sending friendship request முன்னரே தோழராக்கப்பட்டார் Groupchat %1 DesktopNotify New message புதிய செய்தி Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen படிவம் 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen ETA 10 10 Filename Ausgelassen கோப்புப்பெயர் Waiting to send... file transfer widget அனுப்பக் காத்திருப்பிலுள்ளது... Accept to receive this file file transfer widget இந்த கோப்பைப் பெறவேண்டின் ஏற்புதல் அளியுங்கள் Location not writable Title of permissions popup தங்கள் கோப்பகத்தில் எழுதுதல் இயலாது You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup அந்தக் கோப்பகத்தில் எழுத உங்களுக்கு அனுமதி இல்லை. வேறொன்றைத் தேர்வுசெய்யவும் அல்லது சேமிப்புப் பெட்டகத்தை ரத்துசெய்யவும். Resuming... file transfer widget மீண்டும் தொடர்கிறது... Cancel transfer கோப்பிடமாற்றத்தை ரத்துசெய் Pause transfer கோப்பிடமாற்றத்தை இடைநிறுத்து Paused file transfer widget இடைநிறுத்தப்பட்டுள்ளது Open file கோப்பொன்று திற Open file directory கோப்பகமொன்று திற Resume transfer கோப்பிடமாற்றத்தை மீண்டும் தொடர் Accept transfer கோப்பிடமாற்றத்திற்கு அனுமதியளி Save a file Title of the file saving dialog கோப்பொன்று சேமி Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window இடமாற்றப்பட்ட கோப்புகள் Downloads பதிவிறக்கங்கள் Uploads பதிவேற்றங்கள் FriendListWidget Today இன்று Yesterday நேற்று Last 7 days கடந்த 7 நாட்கள் This month இந்த மாதம் Older than 6 Months 6 மாதங்களுக்கு மேல் பழமையானவை Never ஏற்காதே FriendRequestDialog Friend request Title of the window to aceept/deny a friend request தோழராக்க வேண்டுகோள் Someone wants to make friends with you ஒருவர் உங்களைத் தோழராக்க விரும்புகிறார் User ID: பயனர் அடையாளச்சரம்: Friend request message: தோழராக்க வேண்டுகோள் செய்தி: Accept Accept a friend request ஏற்றுக்கொள் Reject Reject a friend request நிராகரி FriendWidget Invite to group Menu to invite a friend to a groupchat தோழர்க்குழுவில் சேர அழைப்பளி Move to circle... Menu to move a friend into a different circle வேறு வட்டத்திற்கு மாற்று... To new circle புதிய வட்டத்திற்கு Remove from circle '%1' '%1' வட்டத்திலிருந்து நீக்கு Move to circle "%1" "%1" வட்டத்திற்கு மாற்று Open chat in new window அரட்டையைப் புதிய சாளரத்தில் திற Remove chat from this window அரட்டையை இச்சாளரத்திலிருந்து நீக்கு To new group வேறு தோழர்க்குழுவிற்கு Invite to group '%1' '%1' தோழர்க்குழுவில் சேர அழைப்பளி Set alias... மாற்றுப்பெயர் வை... Auto accept files from this friend context menu entry இத்தோழரின் கோப்புகளை தன்னிச்சையாக ஏற்க Remove friend Menu to remove the friend from our friendlist தோழரை நீக்கு Show details விவரங்களைக் காட்டு Choose an auto accept directory popup title தன்னிச்சையேற்புக்கான கோப்பகத்தைத் தேர்வு செய்க New message புதிய செய்தி Online தொடர்பிலுள்ளார் Away விலகியுள்ளார் Busy வேறுவினையேற்றுள்ளார் Offline Ausgelassen தொடர்பற்றுள்ளார் GeneralForm General பொதுப்படை Choose an auto accept directory popup title தன்னிச்சையேற்புக்கான கோப்பகத்தைத் தேர்வு செய்க GeneralSettings General Settings பொதுப்படை அமைப்புகள் The translation may not load until qTox restarts. qTox மறுதொடங்கும் வரை இம்மொழிபெயர்ப்பு செயல்படாமலிருக்கலாம். Language: மொழி: Show system tray icon கணிணியமைப்பியக்கியின் பின்னணிப் பெட்டகத்தில் மென்பொருள் சின்னத்தைக் காட்டு Enable light tray icon. toolTip for light icon setting பின்னணிப் பெட்டகத்தில் வெளிர்ச்சின்னத்தைக் காட்டு. Light icon வெளிர்ச்சின்னம் qTox will start minimized in tray. toolTip for Start in tray setting qTox தோன்றும்பொழுது பின்னணிப் பெட்டகத்தில் மறைந்தியங்கும். Start in tray தோற்றத்தில் பின்னணிப் பெட்டகத்தில் மறைந்தியங்கு After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting qTox இன் மூடு பொத்தானை (X) இயக்கியபின் பின்னணிப் பெட்டகத்தில் மறைந்தியங்கும், நினைவகமறைவு அடைவதற்கு பதிலாக. Close to tray பின்னணிப் பெட்டகத்தில் மறைந்தியங்கு After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting qTox இன் மறைசாளர பொத்தானை (_) இயக்கியபின் பின்னணிப் பெட்டகத்தில் மறைந்தியங்கும், முன்னணிப் பெட்டகத்திற்கு பதிலாக. Minimize to tray பின்னணிப் பெட்டகத்தில் சாளரமறைத்தியங்கு Autostart தான்தோற்றம் Set where files will be saved. கோப்புகள் சேமிக்குமிடத்தைத் தேர்ந்தெடுந்தெடுங்கள். You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip தாங்கள் இதை ஒவ்வொரு நண்பருக்கும் அவரை வலம் சுட்டி மாற்றியமைக்கலாம். Autoaccept files கோப்புகளைத் தன்னிச்சையாக ஏற்க Set to 0 to disable இச்செயல்பாட்டை முடக்க 0 என மாற்றியமைக்கவும் Your status is changed to Away after set period of inactivity. ஒரு குறிப்பிட்ட நேரத்திற்கு செயலற்றிருப்பின் தங்கள் நிலை 'விலகியுள்ளார்' என மாற்றப்படும். Auto away after (0 to disable): இந்நேரத்திற்குப் பிறகு தானாக 'விலகியுள்ளார்' என மாற்றிமை (0 எனில் செயல்பாடு முடங்கும்): Show contacts' status changes தோழர்களின் நிலைச்செய்தி மாற்றங்களைக் காண்பி Start qTox on operating system startup (current profile). கணிணியமைப்பியக்கியின் தொடக்கத்தில் qTox உம் தான்தொடங்கட்டும் (தற்போதைய பயனர் விவரமமைப்புத்தொகுப்பு). Default directory to save files: கோப்புகளை சேமிக்க பொது கோப்பகம்: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message செய்தியனுப்பு Smileys உணர்வுக்குறிகள் Send file(s) கோப்பு(கள்) அனுப்பு Send a screenshot திரைத்தன்னிலைப்படமொன்றை அனுப்பு Save chat log அரட்டைப்பதிவைச் சேமி Clear displayed messages திரையிலுள்ள செய்திகளை நீக்கு Cleared திரைத்தெளிவாக்கப்பட்டது Quote selected text தேர்ந்தெடுக்கப்பட்ட சொற்சரத்தை மேற்கோள் காட்டியனுப்பு Copy link address இணை முகவரியை இடைப்பிரதியெடு Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... அரட்டை வரலாற்றை திரையேற்று... Export to file தனிக்கோப்பாக்கு GenericNetCamView Tox video Tox காணொளி Show Messages செய்திகளைத் திரையிடு Hide Messages செய்திகளை மறை Full Screen Toggle video preview Mute audio Mute microphone ஒலிவாங்கியை ஒலியணை End video call Exit full screen GroupChatForm %1 has set the title to %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups தோழர்க்குழுக்கள் Create new group புதிய தோழர்க்குழுவை உருவாக்கு Group invites தோழர்க்குழு அழைப்புகள் GroupInviteWidget Invited by %1 on %2 at %3. %1 %2 அன்று %3 மணிக்கு விடுத்த அழைப்பு. Join சேர் Decline மறு GroupWidget Set title... தலைப்பு வை... Open chat in new window அரட்டையைப் புதிய சாளரத்தில் திற Remove chat from this window அரட்டையை இச்சாளரத்திலிருந்து நீக்கு Quit group Menu to quit a groupchat தோழர்க்குழுவை நீங்கு %n user(s) in chat Number of users in chat New Message Online தொடர்பிலுள்ளார் IdentitySettings Public Information பொதுவிற்த்தகவல்கள் Tox ID Tox அடையாளச்சரம் This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip இவ்வெழுத்துக்கள் மற்ற Tox வலைப்பயனிகளுக்குத் தங்களைத் தொடர்புகொள்ளும் வழி கூறும். தங்கள் தோழர்களுடன் தொடர்புகொள்ள இதனை அவர்களுடன் பகிருங்கள். Your Tox ID (click to copy) தங்கள் அடையாளச்சரம் (இடைப்பிரதியெடுக்க சொடுக்குக) Profile விவரமமைப்புத்தொகுப்பு Rename profile. tooltip for renaming profile button விவரமமைப்புத்தொகுப்பை மறுபெயரிடு. Go back to the login screen tooltip for logout button கடவுத்திரைக்குத் திரும்பிச் செல் Logout import profile button வெளியே கடவு Remove password கடவுச்சரம் நீக்கு Change password கடவுச்சரம் மாற்று This QR code contains your Tox ID. You may share this with your friends as well. இந்தக் QR குறி தங்களது Tox அடையாளச்சரத்தைக் கொண்டுள்ளது. இதனைத் தம் தோழர்களிடமும் பகிரலாம். Save image எண்ணியற் படத்தைச் சேமி Copy image எண்ணியற் படத்தை இடைப்பிரதியெடு Rename rename profile button மறுபெயரிடு Delete profile. delete profile button tooltip விவரமமைப்புத்தொகுப்பை அழி. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button தங்கள் விவரமமைப்புத்தொகுப்பைத் தனிக்கோப்பாக்கும். விவரமமைப்புத்தொகுப்பில் தங்கள் அரட்டைப் பதிவு இராது. Export export profile button தனிக்கோப்பாக்கு Delete delete profile button அழி Server வலைவழங்கி Hide my name from the public list என் பெயரை பொதுவிற்ப்பட்டியலில் இருந்து மறை Register வலைத்தளத்தில் பதிவு செய் Your password தங்கள் கடவுச்சரம் Update புதுப்பி Register on ToxMe ToxMe வலைத்தளத்தில் பதிவு செய் Name for the ToxMe service. Tooltip for the `Username` ToxMe field. ToxMe வலைச்சேவைக்கான பெயர். Optional. Something about you. Or your cat. Tooltip for the Biography text. விருப்ப உள்ளீடு. ஏதேனும் தங்களைப் பற்றி. அல்லது தங்கள் பூனையைப் பற்றி. Optional. Something about you. Or your cat. Tooltip for the Biography field. விருப்ப உள்ளீடு. ஏதேனும் தங்களைப் பற்றி. அல்லது தங்கள் பூனையைப் பற்றி. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. விவரமமைப்புத்தொகுப்பை மறுபெயரிடு. Delete profile. விவரமமைப்புத்தொகுப்பை அழி. Export profile Remove password from profile Change profile password My name: My status: My username My biography My profile LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Password: Confirm: Password strength: %p% Create Profile If the profile does not have a password, qTox can skip the login screen Load automatically Load Load Profile New Profile Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Password protected profiles can't be automatically loaded. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Import Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name Your status ... Ausgelassen Add friends Create a group chat View completed file transfers Change your settings Close Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings Open Settings Nexus View OS X Menu bar Window OS X Menu bar Minimize OS X Menu bar Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox qTox இல் Tox செய்கிறேன் ProfileForm Choose a profile picture Error Rename "%1" renaming a profile Unable to open this file. Current profile: Remove Unable to read this image. The supplied image is too large. Please use another image. Couldn't rename the profile to "%1" Location not writable Title of permissions popup தங்கள் கோப்பகத்தில் எழுதுதல் இயலாது You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup அந்தக் கோப்பகத்தில் எழுத உங்களுக்கு அனுமதி இல்லை. வேறொன்றைத் தேர்வுசெய்யவும் அல்லது சேமிப்புப் பெட்டகத்தை ரத்துசெய்யவும். Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Nothing to remove Your profile does not have a password! Really delete password? deletion confirmation title Please enter a new password. Are you sure you want to delete this profile? deletion confirmation text Save save qr image Save QrCode (*.png) save dialog filter Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register வலைத்தளத்தில் பதிவு செய் Update புதுப்பி Change password button text கடவுச்சரம் மாற்று Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Cancel Yes ஆம் No இல்லை LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend தோழரைச் சேர்க்க இயலவில்லை %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend தங்களையே தமக்குத் தோழராக்க இயலாது! QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Red Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! இது %1! Tox செய்வோமா? None No camera device set ஏதிலை Desktop Desktop as a camera input for screen sharing Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status தொடர்பிலுள்ளார் away contact status விலகியுள்ளார் busy contact status வேறுவினையேற்றுள்ளார் offline contact status தொடர்பற்றுள்ளார் blocked contact status RemoveFriendDialog Remove friend தோழரை நீக்கு Also remove chat history Remove Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form படிவம் Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Password: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI தோழரைச் சேர்க Do you want to add %1 as a friend? User ID: பயனர் அடையாளச்சரம்: Friend request message: தோழராக்க வேண்டுகோள் செய்தி: Send Send a friend request Cancel Don't send a friend request UserInterfaceForm None ஏதிலை User Interface UserInterfaceSettings Chat Base font: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message புதிய செய்தி Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme Style: Theme color: Timestamp format: Date format: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound ஒலியெழுப்பு Play sound while Busy வேறுவினையேற்றுள்ளபோது ஒலியெழுப்பு Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' தொடர்பிலுள்ளார் Away Button to set your status to 'Away' விலகியுள்ளார் Busy Button to set your status to 'Busy' வேறுவினையேற்றுள்ளார் toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File Edit Profile Change Status Log out Edit Logout Tray action menu to logout user வெளியே கடவு Exit Tray action menu to exit tox Filter... Contacts Add Contact... Next Conversation Previous Conversation Executable file popup title You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Couldn't request friendship Status Your name Message failed to send Create new group... Add new circle... %n New Friend Request(s) %n New Group Invite(s) By Name By Activity All Online தொடர்பிலுள்ளார் Offline Ausgelassen தொடர்பற்றுள்ளார் Friends Groups தோழர்க்குழுக்கள் Search Contacts Groupchat #%1 Show Tray action menu to show qTox window Add friend title of the window Group invites title of the window தோழர்க்குழு அழைப்புகள் File transfers title of the window Settings title of the window My profile title of the window Failed to send file "%1" கோப்பு %1 அனுப்ப இயலவில்லை File sent sent you a friend request. invites you to join a group. qTox/translations/tr.ts000066400000000000000000003275751415623743500155640ustar00rootroot00000000000000 AVForm Default resolution Öntanımlı çözünürlük Audio/Video Hata olabilir. Ses/Görüntü Disabled Devre dışı Select region Bölge seçin Screen %1 Ekran %1 Audio Settings Ses Ayarları Gain Kazanç Playback device Oynatma aygıtı Use slider to set volume of your speakers. Hoparlörlerinizin ses düzeyini ayarlamak için kaydırıcıyı kullanın. Capture device Yakalama aygıtı Volume Ses Düzeyi Video Settings Görüntü Ayarları Video device Görüntü aygıtı Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Kameranızın çözünürlüğünü ayarlayın. Daha yüksek değerler, arkadaşlarınıza daha iyi görüntü kalitesi sunabilir. Daha iyi görüntü kalitesinin daha iyi internet bağlantısı gerektirdiğini unutmayın. Bazen bağlantınız daha yüksek görüntü kalitesini işlemek için yetersiz kalabilir, bu da görüntülü aramalarda sorunlara neden olabilir. Resolution Çözünürlük Rescan devices Aygıtları yeniden tara Test Sound Sesi Sına Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Yankı giderici destekli deneysel ses arka ucunu etkinleştirir, etkili olması için qTox yeniden başlamalı. Enable experimental audio backend Deneysel ses arka ucunu etkinleştir Audio quality Ses kalitesi Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. İletilen ses kalitesi. Bant genişliğiniz yeterince yüksek değilse veya daha az internet kullanımı istiyorsanız bu ayarı azaltın. High (64 kbps) Yüksek (64 kbps) Medium (32 kbps) Orta (32 kbps) Low (16 kbps) Düşük (16 kbps) Very low (8 kbps) Çok düşük (8 kbps) Threshold Eşik AboutForm About Hakkında Original author: %1 Özgün yazar: %1 You are using qTox version %1. qTox'un %1 sürümünü kullanıyorsunuz. Commit hash: %1 İşleme hash'i: %1 toxcore version: %1 toxcore sürümü: %1 Qt version: %1 Qt sürümü: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Bilinen tüm sorunların bir listesi Github %1 bölümümüzde bulunabilir. qTox'da bir hata veya güvenlik açığı keşfederseniz, lütfen %2 viki makalemizdeki yönergelere göre bildirin. Click here to report a bug. Bir hatayı bildirmek için buraya tıkla. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` %1 listesinin tamamını Github'da gör bug-tracker Replaces `%1` in the `A list of all known…` hata-izleyici Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Yararlı Hata Bildirimleri Yazma contributors Replaces `%1` in `See a full list of…` katkıda bulunanlar AboutFriendForm Dialog İletişim Kutusu username kullanıcı adı status message durum iletisi Used aliases: Kullanılan rumuzlar: HISTORY OF ALIASES RUMUZ GEÇMİŞİ Automatically accept files from contact if set Ayarlıysa kişiden gelen dosyaları sormadan kabul et Auto accept files Dosyaları sormadan kabul et Default directory to save files: Dosyaların kaydedileceği öntanımlı dizin: Auto accept for this contact is disabled Bu kişi için sormadan kabul etme kapalı Auto accept call: Aramayı sormadan kabul et: Manual Elle Audio Ses Audio + Video Ses + Görüntü Automatically accept group chat invitations from this contact if set. Ayarlıysa bu kişiden gelen grup sohbeti davetlerini sormadan kabul et. Auto accept group invites Grup davetlerini sormadan kabul et Remove history (operation can not be undone!) Geçmişi kaldır (İşlem geri alınamaz!) Notes Notlar Input field for notes about the contact Kişiyle ilgili notlar için giriş alanı You can save comment about this contact here. Burada bu kişiyle ilgili yorum kaydedebilirsiniz. History removed Geçmiş kaldırıldı Choose an auto accept directory popup title Sormadan kabul etme dizini seç <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>Bu, arkadaşlarının açık anahtarıdır, herhangi bir kanalla kimliklerini doğrulamak için kullanın. Bunu diğer insanlara gönderemediğiniz için onlar bu kişiyi ekleyebilir.</p></body></html> Public key (not ToxID): Açık anahtar (ToxID değil): Confirmation Onay Are you sure to remove %1 chat history? %1 sohbet geçmişini kaldırmak istediğinizden emin misiniz? Failed to remove chat history with %1! Sohbet geçmişi kaldırma başarısız oldu %1! AboutSettings Version Sürüm License Lisans Authors Değiştirilebilir Yazarlar Known Issues Bilinen Sorunlar Open update download link Güncelleme linkini aç Update available Güncelleme var qTox is up to date ✓ qTox güncel AddFriendForm Couldn't add friend Arkadaş eklenemedi Invalid Tox ID format Geçersiz Tox ID biçimi Add Friends Arkadaş ekle Send friend request Arkadaş isteği gönder Add a friend Bir arkadaş ekle Friend requests Arkadaş isteği Accept Kabul et Reject Reddet Tox ID, either 76 hexadecimal characters or name@example.com Tox Kimliği, 76 onaltılık karakter ya da ad@ornek.com Type in Tox ID of your friend Arkadaşınızın Tox Kimliğini yazın Friend request message Arkadaşlık isteği iletisi Type message to send with the friend request or leave empty to send a default message Arkadaşlık isteği ile gönderilecek iletinizi yazın veya öntanımlı iletiyle göndermek için boş bırakın %1 Tox ID is invalid or does not exist Toxme error %1 Tox Kimliği geçersiz veya yok You can't add yourself as a friend! When trying to add your own Tox ID as friend Kendinizi, bir arkadaş olarak ekleyemezsiniz! Open contact list Kişi listesini aç Couldn't open file Dosyası açılamadı Couldn't open the contact file Error message when trying to open a contact list file to import Kişi dosyası açılamadı Invalid file Geçersiz dosya We couldn't find any contacts to import in this file! Dosyada içe aktarılacak kişi bulumamadı! Tox ID Tox ID of the person you're sending a friend request to Tox Kimliği either 76 hexadecimal characters or name@example.com Tox ID format description 76 onaltılık karakter ya da ad@ornek.com Message The message you send in friend requests İleti Open Button to choose a file with a list of contacts to import Send friend requests Arkadaş isteği gönder %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! %1 burada! Beni Toxlarsın belki? Import a list of contacts, one Tox ID per line Kişilerin bir listesini içe aktar, her satırda bir Tox Kimliği Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) %n kişi içe aktarılmaya hazır, onay için göndere tıklayın %n kişi içe aktarılmaya hazır, onay için göndere tıklayın Import contacts Kişileri içe aktar AdvancedForm Advanced Ya da basitçe gelişmiş, ileri seviye denilebilir, alışılageldiği üzere Gelişmiş Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Ne yaptığınızı %1 bilmiyorsanız, lütfen buradaki hiçbir şeyi %2 değiştirmeyin. Burada yapılan değişiklikler qTox'la sorunlara ve veri kaybına (örneğin geçmiş) bile neden olabilir. really gerçekten not asla IMPORTANT NOTE ÖNEMLİ NOT Reset settings Ayarları sıfırla All settings will be reset to default. Are you sure? Tüm ayarlar öntanımlılara sıfırlanacak. Emin misiniz? Yes Evet No Hayır Call active popup title Arama etkin You can't disconnect while a call is active! popup text Bir arama etkinken bağlantıyı kesemezsiniz! Save File Dosyayı Kaydet Logs (*.log) Günlükler (* .log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Ayarları, her zamanki 'conf' dizini yerine, çalışma dizinine kaydet Make Tox portable qTox'u taşınabilir yap Reset to default settings Öntanımlı ayarlara sıfırla Portable Taşınabilir Connection Settings Bağlantı Ayarları Enable IPv6 (recommended) Text on a checkbox to enable IPv6 IPv6'yı etkinleştir (önerilen) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Bunu devre dışı bırakmak, örneğin, Tor'da toxlamayı sağlar. Fakat Tox ağına yük bindirir, bu yüzden yalnızca gerektiğinde işareti kaldırın. Enable UDP (recommended) Text on checkbox to disable UDP UDP'yi etkinleştir (önerilen) Proxy type: Vekil türü: Address: Text on proxy addr label Adres: Port: Text on proxy port label Port: None Yok SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Yeniden bağlan Debug Hata Ayıklama Export Debug Log Hata ayıklama günlüğünü dışa aktar Copy Debug Log Hata ayıklama günlüğünü kopyala Enable LAN discovery Yerel Ağ keşfini etkinleştir ChatForm Send a file Dosya gönder Unable to open Açılamadı qTox wasn't able to open %1 qTox %1 ögesini açamadı Bad idea Kötü fikir %1 calling %1 arıyor Calling %1 %1 aranıyor Failed to open temporary file Temporary file for screenshot Geçici dosya açılamadı qTox wasn't able to save the screenshot qTox ekran görüntüsünü kaydedemedi Call with %1 ended. %2 %1 ile arama sonlandı. %2 Call duration: Arama süresi: %1 is typing %1 yazıyor Copy Kopyala You're trying to send a sequential file, which is not going to work! Sıralı bir dosya göndermeye çalışıyorsunuz, bu işe yaramayacak! %1 is now %2 e.g. "Dubslow is now online" %1 artık %2 Call with %1 ended unexpectedly. %2 %1 ile arama beklenmedik şekilde sonlandı. %2 Filename contained illegal characters Dosya adı yasaklanmış karakterler içeriyor Illegal characters have been changed to _ so you can save the file on windows. Yasaklanmış karakterler _ ile değiştirildi, böylece dosyayı Windows'da kaydedebilirsiniz. ChatFormHeader Can't start audio call Sesli arama başlatılamıyor Start audio call Sesli arama başlat End audio call Sesli aramayı bitir Cancel audio call Sesli aramayı iptal et Accept audio call Sesli aramayı kabul et Can't start video call Görüntülü arama başlatılamıyor Start video call Görüntülü arama başlat End video call Görüntülü aramayı bitir Cancel video call Görüntülü aramayı iptal et Accept video call Görüntülü aramayı kabul et Sound can be disabled only during a call Ses yalnızca bir arama sırasında kapatılabilir Unmute call Aramanın sesini aç Mute call Aramanın sesini kapa Microphone can be muted only during a call Mikrofon sadece bir arama sırasında kapatılabilir Unmute microphone Mikrofonun sesini aç Mute microphone Mikrofonun sesini kapat ChatLog pending bekliyor Copy Kopyala Select all Tümünü seç ChatTextEdit Type your message here... İletinizi buraya yazın... CircleWidget Rename circle Menu for renaming a circle Çevreyi yeniden adlandır Remove circle Menu for removing a circle Çevreyi kaldır Open all in new window Tümünü yeni pencerede aç Core /me offers friendship, "%1" /me arkadaşlık öneriyor, "%1" Invalid Tox ID Error while sending friendship request Geçersiz Tox Kimliği You need to write a message with your request Error while sending friendship request İsteğinizle birlikte bir ileti yazmalısınız Your message is too long! Error while sending friendship request İletiniz çok uzun! Friend is already added Error while sending friendship request Arkadaş zaten eklendi Groupchat %1 Grup sohbeti %1 DesktopNotify New message Yeni mesaj Incoming file transfer Gelen dosya aktarımı Friend request received Arkadaşlık isteği alındı New group message Yeni grup mesajı Group invite received Grup daveti alındı FileTransferWidget Form Form 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 ETA:10:10 Filename Dosya adı Waiting to send... file transfer widget Göndermek için bekliyor... Accept to receive this file file transfer widget Bu dosyayı almak için onaylayın Location not writable Title of permissions popup Konum yazılabilir değil You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Bu konuma yazmaya yetkiniz yok. Başka seçin, ya da kaydetmeyi iptal edin. Paused file transfer widget Duraklatıldı Resuming... file transfer widget Sürdürülüyor... Open file Dosya aç Open file directory Dosya dizinini aç Pause transfer Aktarımı duraklat Cancel transfer Aktarımı iptal et Resume transfer Aktarımı sürdür Accept transfer Aktarımı kabul et Save a file Title of the file saving dialog Bir dosya kaydet Remote Paused file transfer widget Uzaktan Duraklatıldı FilesForm Transferred Files "Headline" of the window Aktarılan Dosyalar Downloads İndirilenler Uploads Yüklenenler FriendListWidget Today Bugün Yesterday Dün Last 7 days Son 7 gün This month Bu ay Older than 6 Months 6 Aydan Eski Never Asla FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Arkadaş isteği Someone wants to make friends with you Biri sizinle arkadaş olmak istiyor User ID: Kullanıcı Kimliği: Friend request message: Arkadaşlık isteği iletisi: Accept Accept a friend request Kabul et Reject Reject a friend request Reddet FriendWidget Open chat in new window Sohbeti yeni pencerede aç Remove chat from this window Sohbeti bu pencereden kaldır Invite to group Menu to invite a friend to a groupchat Gruba davet et Move to circle... Menu to move a friend into a different circle Değiştirilebilir Çevreye taşı... To new circle Yeni çevreye Remove from circle '%1' '%1' çevresinden kaldır Move to circle "%1" "%1" çevresine taşı Set alias... Rumuz ayarla... Auto accept files from this friend context menu entry Bu arkadaştan gelen dosyaları sormadan kabul et Remove friend Menu to remove the friend from our friendlist Arkadaşı kaldır Choose an auto accept directory popup title Sormadan kabul etme dizini seç New message Yeni ileti Online Çevrimiçi Away Uzakta Busy Meşgul Offline Çevrimdışı To new group Yeni gruba Invite to group '%1' '%1' grubuna davet et Show details Ayrıntıları göster GeneralForm Choose an auto accept directory popup title Sormadan kabul etme dizini seç General Genel GeneralSettings General Settings Genel Ayarlar The translation may not load until qTox restarts. Çeviri, qTox yeniden başlayana kadar yüklenemeyebilir. Language: Dil: Start qTox on operating system startup (current profile). qTox'u işletim sistemi başlangıcında başlat (şu anki profil). Autostart Kendiliğinden başlat Enable light tray icon. toolTip for light icon setting Aydınlık tepsi simgesini etkinleştir. Light icon Aydınlık simge Show system tray icon Sistem tepsi simgesi göster qTox will start minimized in tray. toolTip for Start in tray setting qTox tepsiye küçültülmüş olarak başlayacak. Start in tray Tepside başlat After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Küçült'e (_) bastıktan sonra, qTox kendini görev çubuğu yerine sistem tepsisine küçültecek. Minimize to tray Tepsiye küçült After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Kapat'a (X) bastıktan sonra, qTox kendini kapatmak yerine, sistem tepsisine küçültecek. Close to tray Tepsiye kapat Your status is changed to Away after set period of inactivity. Durumunuz, belirli süre hareketsizlikten sonra Uzakta olarak değiştirildi. Auto away after (0 to disable): Kendilinden uzakta süresi (0 devre dışı bırakır): Set to 0 to disable Devre dışı kılmak için 0'a ayarlayın Set where files will be saved. Dosyaların nereye kaydedileceği ayarlayın. Default directory to save files: Dosyaların kaydedileceği öntanımlı dizin: You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Bunu, her bir arkadaş için, üstlerine sağ-tıklayarak ayarlayabilirsiniz. Autoaccept files Dosyaları sormadan kabul et Show contacts' status changes Kişilerin durum değişiklerini göster Check for updates Güncelleştirmeleri denetle Spell checking Yazım denetimi Max autoaccept file size (0 to disable): Maksimum otomatik kabul dosya boyutu (devre dışı bırakmak için 0): MB MB GenericChatForm Save chat log Sohbet günlüğünü kaydet Cleared Temizlendi Send message İleti gönder Smileys Yüz İfadeleri Send file(s) Dosya gönder Send a screenshot Bir ekran görüntüsü gönder Clear displayed messages Gösterilen iletileri temizle Quote selected text Seçili metni alıntıla Copy link address Bağlantı adresini kopyala Confirmation Onay You are sure that you want to clear all displayed messages? Görüntülenen tüm mesajları silmek istediğinize emin misiniz? Search in text Metinde ara Go to current date Şu anki tarihe git Load chat history... Sohbet geçmişini yükle... Export to file Dosyaya dışa aktar GenericNetCamView Tox video Tox görüntü Show Messages İletileri Göster Hide Messages İletileri Gizle Full Screen Tam Ekran Toggle video preview Video önizlemesini değiştir Mute audio Sesi kapat Mute microphone Mikrofonun sesini kapat End video call Görüntülü aramayı bitir Exit full screen Tam ekrandan çık GroupChatForm %1 has set the title to %2 %1 başlığı %2 olarak ayarladı %1 has joined the group %1 gruba katıldı %1 is now known as %2 %1 artık %2 olarak biliniyor %1 has left the group %1 gruptan ayrıldı %n user(s) in chat Number of users in chat %n kullanıcı sohbette %n kullanıcı sohbette mute sesi kapat unmute sesi aç GroupInviteForm Groups Gruplar Create new group Yeni grup oluştur Group invites Grup davetleri GroupInviteWidget Invited by %1 on %2 at %3. %1, %2 %3 tarihinde davet etti. Join Katıl Decline Reddet GroupWidget Open chat in new window Sohbeti yeni pencerede aç Remove chat from this window Sohbeti bu pencereden kaldır Set title... Başlık ayarla... Quit group Menu to quit a groupchat Gruptan çık %n user(s) in chat Number of users in chat %n kullanıcı sohbette %n kullanıcı sohbette New Message Yeni İleti Online Çevrimiçi IdentitySettings Public Information Genel Bilgi Tox ID Tox Kimliği This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Bu karakterler diğer Tox istemcilerine size nasıl ulaşacaklarını anlatır. İletişim kurmak için bunu arkadaşlarınızla paylaşın. Your Tox ID (click to copy) Tox Kimliğiniz (kopyalamak için tıkla) This QR code contains your Tox ID. You may share this with your friends as well. Bu QR kodu Tox Kimliğinizi içerir. Bunu da arkadaşlarızla paylaşabilirsiniz. Save image Resmi kaydet Copy image Resmi kopyala Profile Profil Rename profile. tooltip for renaming profile button Profili yeniden adlandır. Rename rename profile button Yeniden adlandır Delete profile. delete profile button tooltip Profili sil. Delete delete profile button Sil Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Tox profilinizi dosyaya aktarmanızı sağlar. Profil geçmişinizi içermez. Export export profile button Dışa aktar Go back to the login screen tooltip for logout button Giriş ekranına geri dön Logout import profile button Oturumu kapat Remove password Parolayı kaldır Change password Parolayı değiştir Server Sunucu Hide my name from the public list Adımı genel listeden gizle Register Kaydol Your password Parolanız Update Güncelle Register on ToxMe ToxMe'de kaydol Name for the ToxMe service. Tooltip for the `Username` ToxMe field. ToxMe hizmetinin adı. Optional. Something about you. Or your cat. Tooltip for the Biography text. İsteğe bağlı. Sizinle ilgili bir şey. Ya da kedinizle. Optional. Something about you. Or your cat. Tooltip for the Biography field. İsteğe bağlı. Sizinle ilgili bir şey. Ya da kedinizle. ToxMe service to register on. Kaydolunacak ToxMe hizmeti. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Ayarlı değilse, ToxMe girdilerini herkes görebilir. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Profilinizden parolanızı ve şifrelemeyi kaldırın. Name input Ad girişi Name visible to contacts Kişilere görünen ad Status message input Durum iletisi girişi Status message visible to contacts Kişilere görünen durum iletisi Your Tox ID Tox Kimliğiniz Save QR image as file QR resmini dosya olarak kaydet Copy QR image to clipboard QR resmini panoya kopyala ToxMe username to be shown on ToxMe ToxMe'de gösterilecek ToxMe kullanıcı adı Optional ToxMe biography to be shown on ToxMe ToxMe'de gösterilecek isteğe bağlı ToxMe özgeçmişi ToxMe service address ToxMe hizmet adresi Visibility on the ToxMe service ToxMe hizmetinde görünürlük Password Parola Update ToxMe entry ToxMe girdisi güncelle Rename profile. Profili yeniden adlandır. Delete profile. Profili sil. Export profile Profili dışa aktar Remove password from profile Parolayı profilden kaldır Change profile password Profil parolasını değiştir My name: Adım: My status: Durumum: My username Kullanıcı adım My biography Özgeçmişim My profile Profilim LoadHistoryDialog Load History Dialog Geçmiş İletişim Kutusunu Yükle Load history Geçmişi yükle from gelen to giden (about 100 messages are loaded) (yaklaşık 100 mesaj yüklendi) Select Date Dialog Tarih Seçme Diyaloğu Select a date Bir tarih seçin LoginScreen Username: Kullanıcı adı: Password: Parola: Confirm: Onayla: Password strength: %p% Parola gücü: %p% Create Profile Profil Oluştur If the profile does not have a password, qTox can skip the login screen Profilin parolası yoksa, qTox giriş ekranını atlayabilir Load automatically Kendiliğinden yükle Load Yükle New Profile Yeni Profil Load Profile Profil Yükle Couldn't create a new profile Yeni bir profil oluşturulamadı The username must not be empty. Kullanıcı adı boş olmamalı. The password must be at least 6 characters long. Parola en az 6 karakter uzunluğunda olmalı. The passwords you've entered are different. Please make sure to enter same password twice. Girdiğiniz parolalar farklı. Lütfen aynı parolayı iki kez girdiğinizden emin olun. A profile with this name already exists. Bu adda bir profil zaten var. Couldn't load profile Profil yüklenemedi There is no selected profile. You may want to create one. Seçili profil yok. Yeni birini oluşturmak isteyebilirsiniz. Couldn't load this profile Bu profil yüklenemedi This profile is already in use. Bu profil zaten kullanımda. Wrong password. Yanlış parola. Import İçe aktar Password protected profiles can't be automatically loaded. Parola korumalı profiller kendiliğinden yüklenemez. Username input field Kullanıcı adı giriş alanı Password input field, you can leave it empty (no password), or type at least 6 characters Parola giriş alanı, boş bırakabilirsiniz (parola yok), ya da en az 6 karakter yazın Password confirmation field Parola onay alanı Create a new profile button Yeni bir profil oluştur düğmesi Profile list Profil listesi List of profiles Profillerin listesi Password input Parola girişi Load automatically checkbox Kendiliğinden yükle onay kutusu Import profile Profili içe aktar Load selected profile button Seçili profili yükle düğmesi New profile creation page Yeni profil oluşturma sayfası Loading existing profile page Olan profili yükleme sayfası MainWindow Your name Adınız Your status Durumunuz ... ... Add friends Arkadaş ekle Create a group chat Grup sohbeti oluştur View completed file transfers Tamamlanan dosya aktarımlarını görüntüle Change your settings Ayarlarınızı değiştirin Close Kapat Open profile Profil aç Open profile page when clicked Tıklandığında profil sayfasını aç Status message input Durum iletisi girişi Set your status message that will be shown to others Diğerlerine gösterilecek durum iletinizi ayarlayın Status Durum Set availability status Bulunabilirlik durumunu ayarla Contact search Kişi arama Contact search input for known friends Bilinen arkadaşlar için kişi arama girişi Sorting and visibility Sıralama ve görünürlük Set friends sorting and visibility Arkadaşlar sıralama ve görünürlüğü ayarla Open Add friends page Arkadaş ekle sayfasını aç Groupchat Grup sohbeti Open groupchat management page Grup sohbeti yönetim sayfasını aç File transfers history Dosya aktarım geçmişi Open File transfers history Dosya transfer geçmişini aç Settings Ayarlar Open Settings Ayarları Aç Nexus View OS X Menu bar Görünüm Window OS X Menu bar Pencere Minimize OS X Menu bar Küçült Bring All to Front OS X Menu bar Tümünü Öne Getir Exit Fullscreen Tam Ekrandan Çık Enter Fullscreen Tam Ekran Yap NotificationEdgeWidget Unread message(s) Okunmamış mesaj Okunmamış mesajlar PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK-ETKİN PrivacyForm Confirmation Onay Do you want to permanently delete all chat history? Tüm sohbet geçmişinizi kalıcı olarak silmek istiyor musunuz? Privacy Gizlilik PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Arkadaşlarınız yazdığınızda görebilecekler. Send typing notifications Yazma bildirimlerini gönder Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Sohbet geçmişi saklama hala geliştiriliyor. Kayıt biçimi değişebilir, bu da veri kaybına neden olabilir. Keep chat history Sohbet geçmişini sakla NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam, Tox Kimliğinizin bir parçasıdır. Arkadaşlık istemleriyle rahatsız ediliyorsanız, NoSpam'ınızı değiştirmelisiniz. İnsanlar sizi eski kimliğinizle ekleyemez, ama şu anki arkadaşlarınız kalır. NoSpam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam, kimliğinizin her zaman değiştirebilen bir parçasıdır. Arkadaşlık istemleriyle rahatsız ediliyorsanız, NoSpam'ı değiştirin. Generate random NoSpam Rastgele NoSpam üret Privacy Gizlilik BlackList KaraListe Filter group message by group member's public key. Put public key here, one per line. Grup iletisini grup üyesinin açık anahtarıyla süz. Açık anahtarı buraya koyun, her satırda bir girdi. Profile Failed to derive key from password, the profile won't use the new password. Paroladan anahtar türetilemedi, profil yeni parolayı kullanmayacak. Couldn't change password on the database, it might be corrupted or use the old password. Veritabanındaki parola değiştirilemedi, bozulmuş olabilir veya eski parolayı kullanın. Toxing on qTox qTox'da tox'lanıyor ProfileForm Current profile: Şu anki profil: Remove Kaldır Choose a profile picture Bir profil resmi seç Error Hata Unable to open this file. Bu dosya açılamadı. Unable to read this image. Bu resim okunamadı. The supplied image is too large. Please use another image. Sağlanan resim çok büyük. lütfen başka bir resim kullanın. Rename "%1" renaming a profile "%1" profilini yeniden adlandır Couldn't rename the profile to "%1" Profil "%1" olarak yeniden adlandırılamadı Location not writable Title of permissions popup Konum, yazılabilir değil You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Bu konuma yazmaya yetkiniz yok. Başka seçin, ya da kaydetmeyi iptal edin. Failed to copy file Dosya kopyalanamadı The file you chose could not be written to. Seçtiğiniz dosyaya yazılamadı. Really delete profile? deletion confirmation title Profil gerçekten silinsin mi? Are you sure you want to delete this profile? deletion confirmation text Bu profili silmek istediğinize emin misiniz? Save save qr image Kaydet Save QrCode (*.png) save dialog filter QrCode kaydet (*.png) Nothing to remove Kaldırılacak birşey yok Your profile does not have a password! Profilinizin bir parolası yok! Really delete password? deletion confirmation title Parola gerçekten silinsin mi? Please enter a new password. Lütfen yeni bir parola girin. Files could not be deleted! deletion failed title Dosyalar silinemedi! Register (processing) Kayıt (işleniyor) Update (processing) Güncelleme (işleniyor) Done! Bitti! Account %1@%2 updated successfully %1@%2 hesabı başarıyla güncellendi Successfully added %1@%2 to the database. Save your password %1@%2 başarıyla veritabanına eklendi. Parolanızı kaydedin Toxme error Toxme hatası Register Kaydol Update Güncelle Change password button text Parolayı değiştir Set profile password button text Profil parolası ayarla Current profile location: %1 Şu anki profil konumu: %1 Couldn't change password Parola değiştirilemedi This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Bu karakterler diğer Tox istemcilerine size nasıl ulaşacaklarını anlatır. İletişim kurmak için bunu arkadaşlarınızla paylaşın. Bu kimlik NoSpam kodu (mavi), ve sağlama toplamı (gri) içerir. Empty path is unavaliable Boş konum kullanılamaz Failed to rename Yeniden adlandırılamadı Profile already exists Profil zaten var A profile named "%1" already exists. "%1" adında bir profil zaten var. Empty name Boş ad Empty name is unavaliable Boş ad kullanılamaz Empty path Boş konum Couldn't change password on the database, it might be corrupted or use the old password. Veritabanındaki parola değiştirilemedi, bozulmuş olabilir veya eski parolayı kullanın. Export profile Profili dışa aktar Tox save file (*.tox) save dialog filter Tox kayıt dosyası (*.tox) The following files could not be deleted: deletion failed text part 1 Aşağıdaki dosyalar silinemedi: Please manually remove them. deletion failed text part 2 Lütfen elle kaldırın. Are you sure you want to delete your password? deletion confirmation text Parolanızı silmek istediğinize emin misiniz? Images (%1) filetype filter Resimler (%1) ProfileImporter Import profile import dialog title Profili içe aktar Tox save file (*.tox) import dialog filter Tox kayıt dosyası (*.tox) Ignoring non-Tox file popup title Tox olmayan dosya yok sayılıyor Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Uyarı: Tox kayıt dosyası olmayan bir dosya seçtiniz; yok sayılıyor. Profile already exists import confirm title Profil zaten var A profile named "%1" already exists. Do you want to erase it? import confirm text "%1" isimli bir profil zaten var. Silmek ister misiniz? File doesn't exist Dosya yok Profile doesn't exist Profil yok Profile imported Profil içe aktarıldı %1.tox was successfully imported %1.tox başarıyla içe aktarıldı QApplication Ok Tamam Cancel İptal Yes Evet No Hayır LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend Arkadaş eklenemedi %1 is not a valid Toxme address. %1 geçerli bir Toxme adresi değil. You can't add yourself as a friend! When trying to add your own Tox ID as friend Kendinizi, bir arkadaş olarak ekleyemezsiniz! QObject Tox URI to parse İşlenecek Tox URI'si Starts new instance and loads specified profile. Yeni örnek başlatır ve belirtilen profili yükler. profile profil %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! %1 burada! Beni Toxlarsın belki? None No camera device set Yok Desktop Desktop as a camera input for screen sharing Masaüstü Default Öntanımlı Blue Mavi Olive Zeytin Red Kırmızı Violet Mor Incoming call... Gelen arama... Server doesn't support Toxme Sunucu Toxme desteklemiyor You're making too many requests. Wait an hour and try again Çok fazla istek yapıyorsunuz. Bir saat bekleyin ve yeniden deneyin This name is already in use Bu isim zaten kullanımda This Tox ID is already registered under another name Bu Tox Kimliği zaten başka bir ad altında kayıtlı Please don't use a space in your name Lütfen adınızda boşluk kullanmayın Password incorrect Parola yanlış You can't use this name Bu adı kullanamazsınız Name not found Ad bulunamadı Tox ID not sent Tox Kimliği gönderilmedi That user does not exist Bu kullanıcı yok Error Hata qTox couldn't open your chat logs, they will be disabled. qTox sohbet günlüklerinizi açamadı, devre dışı bırakılacaklar. Problem with HTTPS connection HTTPS bağlantısı ile sorun Internal ToxMe error İç ToxMe hatası Reformatting text in progress.. Metin yeniden biçimlendiriliyor... Starts new instance and opens the login screen. Yeni örnek başlatır ve giriş ekranını açar. Dark Koyu Dark blue Koyu mavi Dark olive Koyu zeytin Dark red Koyu kırmızı Dark violet Koyu menekşe Failed to load profile automatically. Profil otomatik olarak yüklenemedi. online contact status çevrimiçi away contact status uzakta busy contact status meşgul offline contact status çevrimdışı blocked contact status engellendi RemoveFriendDialog Remove friend Arkadaşı kaldır Also remove chat history Sohbet geçmişini de sil Remove Kaldır Are you sure you want to remove %1 from your contacts list? %1 kişisini kişiler listenizden kaldırmak istediğinize emin misiniz? Remove all chat history with the friend if set Ayarlıysa arkadaşla olan tüm sohbet geçmişini kaldırır ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Bir alanı seçmek için tıklayıp sürükleyin. qTox penceresini gizlemek/göstermek için %1 , veya iptal için %2 tuşuna basın. Space [Space] key on the keyboard Boşluk Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Seçimin bir ekran görüntüsü göndermek için %1, qTox penceresini gizlemek/göstermek için %2, veya iptal için %3 tuşuna basın. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Metin bulunamadı. Start Başlat SearchSettingsForm Form Form Start search: Aramayı başlat: from the end sondan from the beginning baştan after date tarihten sonra before date tarihten önce 00.00.0000 00.00.0000 Case sensitive Büyük/küçük harf duyarlı Whole words only Yalnızca tam sözcükler Use regular expressions Düzenli ifadeler kullan SetPasswordDialog Set your password Parolanızı ayarlayın Confirm: Onayla: Password: Parola: Password strength: %p% Parola gücü: %p% The password is too short Parola çok kısa The password doesn't match. Parola eşleşmiyor. Confirm password Parolayı onayla Confirm password input Parola onaylama girişi Password input Parola girişi Password input field, minimum 6 characters long Parola giriş alanı, en az 6 karakter uzunluğunda Settings Circle #%1 Çevre #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Bir arkadaş ekle Do you want to add %1 as a friend? %1 kişisini bir arkadaş olarak eklemek istiyor musunuz? User ID: Kullanıcı Kimliği: Friend request message: Arkadaşlık isteği iletisi: Send Send a friend request Gönder Cancel Don't send a friend request İptal UserInterfaceForm None Yok User Interface Kullanıcı Arayüzü UserInterfaceSettings Chat Sohbet Base font: Temel yazı tipi: px px Size: Boyut: New text styling preference may not load until qTox restarts. Yeni yazı şekillendirme tercihi qTox yeniden başlayana kadar uygulanmayabilir. Text Style format: Yazı Stili biçimi: Select text styling preference. Yazı şekillendirme tercihini seç. Plaintext Düz yazı Show formatting characters Biçimlendirme karakterlerini göster Don't show formatting characters Biçimlendirme karakterlerini gösterme New message Yeni ileti Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Bir ileti aldığınızda, henüz açık pencere yoksa, qTox'un penceresini aç. Open window Pencere aç Contact list Kişi listesi If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning İşaretlendiğinde, grup sohbetleri, arkadaşlar listesinin en üstüne, işaretlenmediğinde çevrimiçi arkadaşların altına yerleştirilecektir. Place groupchats at top of friend list Grup sohbetlerini arkadaş listesinin en üstüne yerleştir Your contact list will be shown in compact mode. toolTip for compact layout setting Kişi listeniz sıkışık kipte gösterilecek. Compact contact list Sıkışık kişi listesi Multiple windows mode Çok pencereli kip Open each chat in an individual window Her sohbeti ayrı pencerede aç Emoticons Yüz İfadeleri Use emoticons Yüz İfadeleri kullan Smiley Pack: Text on smiley pack label Yüz ifadesi Paketi: Emoticon size: Yüz İfadesi boyutu: px px Theme Tema Style: Stil: Theme color: Tema rengi: Timestamp format: Zaman biçimi: Date format: Tarih biçimi: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Etkinleştirilirse avatarı ayarlı olmayan her kişinin, öntanımlı bir resim yerine, Tox Kimliklerine göre üretilmiş bir avatarı olacak. Uygulanması için yeniden başlatma gerektirir. Use identicons instead of empty avatars Boş avatarlar yerine identicon kullan Use colored nicknames in chats Sohbetlerde renkli takma ad kullan Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Yeni ileti aldığınızda ve pencere seçili değilse bir bildirim göster. Notify Bildir Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Yalnızca grup sohbetlerindeki yeni mesajlarda söz edildiğinde haber verilir. Group chats only notify when mentioned Grup sohbetleri yalnızca söz edildiğinde bildirir Play sound Ses çal Play sound while Busy Meşgulken ses çal Notify via desktop notifications Masaüstü bildirimleri aracılığıyla bildir Hide message sender and contents Mesaj göndereni ve içeriğini gizle Widget Status Durum toxcore failed to start, the application will terminate after you close this message. toxcore başlatılamadı, bu ileti penceresini kapattığınızda uygulama sonlandırılacak. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text toxcore vekil sunucu ayarlarınızla başlatılamadı. qTox çalışamaz; lütfen ayarlarınızı değiştirip yeniden başlatın. Executable file popup title Çalıştırılabilir dosya You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text qTox'tan çalıştırılabilir bir dosyayı açmasını istediniz. Çalıştırılabilir dosyalar bilgisayarınıza zarar verebilir. Bu dosyayı açmak istediğinize emin misiniz? Your name Adınız Couldn't request friendship Arkadaşlık istenemedi Message failed to send İleti gönderilemedi Add new circle... Yeni çevre ekle... By Name Ada göre By Activity Etkinliğe göre All Tümü Online Çevrimiçi Offline Çevrimdışı Friends Arkadaşlar Groups Gruplar Search Contacts Kişilerde Ara Online Button to set your status to 'Online' Çevrimiçi Away Button to set your status to 'Away' Uzakta Busy Button to set your status to 'Busy' Meşgul Logout Tray action menu to logout user Oturumu kapat Exit Tray action menu to exit tox Çık Filter... Süz... File Dosya Edit Düzenle Contacts Kişiler Change Status Durumu Değiştir Edit Profile Profili Düzenle Log out Oturumu kapat Add Contact... Kişi Ekle... Next Conversation Sonraki Konuşma Previous Conversation Önceki Konuşma Groupchat #%1 Grup sohbeti #%1 Create new group... Yeni grup oluştur... %n New Friend Request(s) %n Yeni Arkadaşlık İsteği %n Yeni Arkadaşlık İsteği %n New Group Invite(s) %n Yeni Grup Daveti %n Yeni Grup Daveti Show Tray action menu to show qTox window Göster Add friend title of the window Arkadaş ekle Group invites title of the window Grup davetleri File transfers title of the window Dosya aktarımları Settings title of the window Ayarlar My profile title of the window Profilim Failed to send file "%1" "%1" dosyası gönderilemedi File sent Dosya gönderildi sent you a friend request. size bir arkadaşlık isteği gönderdi. invites you to join a group. sizi bir gruba katılmaya davet ediyor. qTox/translations/translations.qrc000066400000000000000000000023051415623743500177740ustar00rootroot00000000000000 ar.qm be.qm bg.qm cs.qm da.qm de.qm el.qm eo.qm es.qm et.qm fa.qm fi.qm fr.qm he.qm hr.qm hu.qm it.qm ja.qm jbo.qm ko.qm lt.qm mk.qm nl.qm no_nb.qm pl.qm pr.qm pt.qm pt_BR.qm ro.qm ru.qm sk.qm sl.qm sr.qm sr_Latn.qm sv.qm sw.qm ta.qm tr.qm ug.qm uk.qm zh_CN.qm zh_TW.qm qTox/translations/ug.ts000066400000000000000000003524251415623743500155420ustar00rootroot00000000000000 AVForm Audio/Video ئۈن/سىن Default resolution ئىتىراپلىق ئېنىقلىقى Disabled تاقاش Select region رايون تاللاڭ Screen %1 ئېكران %1 Audio Settings ئۈن تەڭشەكلىرى Gain ئاشۇرۇش Playback device قويۇش ئۈسكۈنىسى Use slider to set volume of your speakers. توچكىنى سۈرۈپ ئۈنقويغۇنىڭ ئاۋازىنى تەڭشەڭ. Capture device ئۈنئالغۇ ئەسۋابى Volume ئاۋاز مىقدارى Video Settings سىن تەڭشىگى Video device سىن ئەسۋابى Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. سىنئالغۇ (كامېرا) ئېنىقلىق تەڭشىگى. قانچە چوڭ بولسا، دوستىڭىزغا كۆرۈنىدىغان سىن شۇنچە سۈپەتلىك بولىدۇ. لېكىن سۈپەتلىك سىن ئ‍ۈچۈن ياخشى تور ئۇلىنىشى زۆرۈر. تور ئۇلىنىشىڭىز ياخىشى بولمىسا ئېنىقلىقى يۇقىرى بولغان سىننى يوللىيالمايدۇ بۇنىڭ بىلەن سىنلىق پاراڭلىشىشىڭىز تەسىرگە ئۇچرىشى مۇمكىن. Resolution سۈزۈكلىكى Rescan devices ئەسۋابلارنى قايتا ئىزدەش Test Sound ئاۋاز سىناش Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. ئەكىس سادانى يوقۇتۇش ئىقتىدرىنى ئېچىش، qTox نى قايتا قوزغۇتىش كېرەك. Enable experimental audio backend تەجىرىبدىكى ئاۋاز سۇپىسىنى ئېچىش Audio quality ئاۋاز سۈپىتى Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. ئۇزۇتىلىدىغان ئاۋاز سۈپىتى، ئەگەر تور تىزلكىلىڭىز بەك يۇقىرى بولمىسا تۆۋەنرەك تەڭشەك. High (64 kbps) يۇقىرى (64 kbps) Medium (32 kbps) ئوتتۇراھال (32 kbps) Low (16 kbps) تۆۋەن (16 kbps) Very low (8 kbps) بەك تۆۋەن (8 kbps) Threshold تۆۋەندىكى چەك AboutForm About ھەققىدە Original author: %1 ئەسلى ئاپتور: %1 You are using qTox version %1. ئىشلىتىۋاتقان qTox نەشىرى: %1. Commit hash: %1 بۇ نەشىرنىڭ hash قىممىتى: %1 toxcore version: %1 يادرو نەشىرى: %1 Qt version: %1 Qt نەشىرى: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` بارلىق مەسلىلەرنى بىرنىڭ GitHub بېتىمىزدىن %1 تاپالايسىز. ئەگەر سىز qTox نىڭ خاتالىقىنى بايقىسىڭىز، بىزنىڭ %2 بېتىمىزدىكى ئۇسۇل بويىچە مەلۇم قىلسىڭىز بولىدۇ. Click here to report a bug. بۇ يەرنى بېسىپ خاتالىقنى مەلۇم قىلىڭ. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` بارلىق تېزىملىكنى Github تىكى %1 دىن كۆرۈڭ bug-tracker Replaces `%1` in the `A list of all known…` خاتالىق خاتىرىسى Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` پايدىلىق مەسىلە دوكلاتى يېزىش contributors Replaces `%1` in `See a full list of…` ھەمكارلاشقانلار AboutFriendForm Dialog دېئالوگ username ئىشلەتكۈچى ئىسىمى status message ئۇچۇر ھالىتى Used aliases: ئىشلەتكەن ئىسىملار: HISTORY OF ALIASES بۇرۇن قوللانغان ئىسىملار Automatically accept files from contact if set دوستلار ئەۋەتكەن ھۆججەتنى ئاپتۇماتىك قۇبۇل قىلىش Auto accept files ھۆججەتلەرنى ئۆزلۈكىدىن قوبۇل قىلىش Default directory to save files: ھۆججەتلەرنى ساقلايدىغان ئورۇن: Auto accept for this contact is disabled بۇ كىشىنڭ ھۆججىتىنى ئۆزلۈكىدىن قۇبۇل قىلماسلىق Auto accept call: چاقىرىشنى ئاپتۇماتىك قۇبۇل قىلىش: Manual قولدا Audio ئاۋاز Audio + Video ئاۋاز + سىن Automatically accept group chat invitations from this contact if set. ئالاقىداشلارنىڭ توپ تەكلىپىنى ئاپتۇماتىك قۇبۇل قىلىش. Auto accept group invites توپ تەكلىپىنى ئاپتۇماتىك قۇبۇل قىلش Remove history (operation can not be undone!) تارىخنى ئۆچۈرۈش (كېيىن ئەسلىگە كەلتۈرگىنى بولمايدۇ!) Notes ئەسكەرتمىلەر Input field for notes about the contact ئالاقىداش ئىزاھاتى كىرگۈزۈش رامكىسى You can save comment about this contact here. بۇ تۇنۇشىڭىز توغرىسىدا ئەسكەرتىش بەرسىڭىز بولىدۇ. History removed خاتىرىلەرنى تازىلاش Choose an auto accept directory popup title ئاپتۇماتىك قۇبۇل قىلىش مۇندەرىجىسىنى تاللاڭ <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation ئىسپاتلاش Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version نۇسخا License رۇخسەتنامە Authors ئاپتورلار Known Issues بايقالغان مەسىلىلەر Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends دوست قوشۇش Invalid Tox ID format ئۈنۈمسىز Tox ID فورماتى Send friend request دوستلۇقنى ئىلتىماس قىلىش Add a friend دوست قوشۇش Friend requests دوستلۇقنى ئىلتىماس قىلىش Accept قۇبۇل قىلىش Reject رەت قىلىش Couldn't add friend دوست قوشۇلمىدى Tox ID, either 76 hexadecimal characters or name@example.com Tox ID بولسا 76 خانىلىق 16 بىتلىق ھەررپتىن تۈزۈلگەن ياكى name@example.com شەكلىدە Type in Tox ID of your friend دوستىڭىزنىڭ Tox ID سىنى كىرگۈزۈڭ Friend request message دوست قوشۇش ئىلتىماسى Type message to send with the friend request or leave empty to send a default message دوست ئىلتىماس ئۇچۇرىنى قۇشۇپ يوللىسىڭىز ياكى قۇرۇق قويۇپ يوللىسىڭىز بولىدۇ %1 Tox ID is invalid or does not exist Toxme error %1 بۇ Tox ID ئۈنۈمسىز ياكى يوق You can't add yourself as a friend! When trying to add your own Tox ID as friend ئۆزىڭىزنى دوستقا قوشالمايسىز! Open contact list دوستلار تىزىمكىنى ئېچىش Couldn't open file ھۆججەت ئېچىلمىدى Couldn't open the contact file Error message when trying to open a contact list file to import دوستلار تىزىملىك ھۆججىتى ئېچىلمىدى Invalid file ئۈنۈمسىز ھۆججەت We couldn't find any contacts to import in this file! بۇ ھۆججەتتىن دوستلار ئۇچۇرى تېپىلمىدى! Tox ID Tox ID of the person you're sending a friend request to Tox كىملىگى either 76 hexadecimal characters or name@example.com Tox ID format description 76 خانىلىق 16لىك سانلار سىستېمىسدىن تۈزۈلگەن ھەرپلەر، ياكى name@example.com Message The message you send in friend requests ئۇچۇر Open Button to choose a file with a list of contacts to import ئېچىش Send friend requests دوست قوشۇش ئىلتىماسى ئەۋەتىش %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! مەن %1 ! بىر Tox تا پاراڭلاشساق بولامدۇ؟ Import a list of contacts, one Tox ID per line دوستلار تىزىملىكىنى كىرگۈزىش، ھەر بىر Tox ID بىر قۇر Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) %n كىشى كىرگۈشىزكە ھازىرلاندى، مۇقۇملاشتۇرامسىز Import contacts ئالاقىداشلار كىرگۈزۈش AdvancedForm Advanced ئالىي Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. ئۆزىڭىزنىڭ نىمە ئىش قىلىۋاتقانلىقىڭىزنى %1 بىلەگەندىن باشقا، بۇ يەردىكى باشقا تەڭشىكلەرنى %2. بۇ يەردىكى ئۆزگىرىشلەر qTox ئەجەللىك خاتالىق پەيدا قىلىشى مومكىن، ھەتتە ئۇچۇرلىرىڭىز، مەسىلەن سۆھبەت خاتىرىسى يوقاپ كىتىشى. really ھەئە not ياق IMPORTANT NOTE موھىم ئۇچۇر Reset settings ئەسلى ھالەتكە قايىتتۇرۇش All settings will be reset to default. Are you sure? پۈتۈن تەڭشەلەرنى ئەسلى ھالىتىگە قايتۇرامسىز؟ Yes ھەئە No ياق Call active popup title سۆزلىشىۋاتىدۇ You can't disconnect while a call is active! popup text پاراڭلىشىۋاتقاندا ئۈزىۋەتسىڭىز بولمايدۇ! Save File ھۆججەت ساقلاش Logs (*.log) خاتىرە (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox خىزمەت مۇندەرجىسى، تەڭشەكلەر ساقلاش مۇندارجىسى ئەمەس Make Tox portable Tox نى كۆچمە ئۈسكىنىلەردە ئىشلىتىش Reset to default settings ئەسلى ھالەتكە قايتۇرۇش Portable كۆچمە ئۈسكىنە Connection Settings ئۇلاش تەڭشىگى Enable IPv6 (recommended) Text on a checkbox to enable IPv6 IPv6 نى قوزغىتىش (تەۋسىيە) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip زۆرۈر بولغاندا بۇ تۈرنى تاقىۋىتىڭ، مەسىلەن Tor نى ئىشلەتكەندە ياكى تور ئېقىمى يىتەرلىك بولمىغاندا. Enable UDP (recommended) Text on checkbox to disable UDP UDP نى ئېچىش (تەۋسىيە) Proxy type: ۋەكىل تۈرى: Address: Text on proxy addr label ئادرېس: Port: Text on proxy port label ئېغىز: None يوق SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button قايتا ئۇلاش Debug چاتاقنى تاپماق Export Debug Log چاتاق خاتىرىسىنى چىقىرىش Copy Debug Log چاتاق خاتىرىسىنى كۆچۈرۈش Enable LAN discovery ChatForm Send a file ھۆججەت يوللاش qTox wasn't able to open %1 qTox %1 ئاچالمىدى Unable to open ئېچىلمىدى Bad idea قاملاشمىغان پىكىر %1 calling %1 نى چاقىرىش Calling %1 ھازىر %1 نى چاقىرىۋاتىدۇ Failed to open temporary file Temporary file for screenshot ۋاقىتلىق ھۆججەت ئېچىلمىدى qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch ئېكران رەسىمى ساقلانمىدى Call with %1 ended. %2 %1 بىلەن پاراڭ ئۈچۈلدى. %2 Call duration: پاراڭ ۋاقتى: %1 is typing %1 خەت كىرگۈزىۋاتىدۇ Copy كۆچۈرۈش You're trying to send a sequential file, which is not going to work! بىردىن يوللاپ سىناپ بېقىڭ، بۇ مەشغۇلاتنى تاماملىيالمىدى! %1 is now %2 e.g. "Dubslow is now online" %1 ھازىر %2 Call with %1 ended unexpectedly. %2 %1 بىلەن بولغان ئالاقە ئۈزۈلۈپ قالدى. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call ئاۋازلىق پاراڭلىشىش ئېچىلمىدى Start audio call ئاۋازلىق چاقىرىش End audio call ئاۋازلىق چاقىرىش ئاخىرلاشتى Cancel audio call ئاۋازلىق پاراڭنى بىكار قىلىش Accept audio call قۇبۇل قىلىش Can't start video call سىنلىق پاراڭ ئېچىلمىدى Start video call سىنلىق پاراڭ End video call سىنلىق پاراڭنى ئاخىرلاشتۇرۇش Cancel video call سىنلىق پاراڭنى بىكار قىلىش Accept video call سىنلىق پاراڭنى قۇبۇل قىلىش Sound can be disabled only during a call پەقەت ئاۋازلىق پاراڭلاشقان ۋاقىتتا ئاۋاز تاقاش مەشغۇلاتنى قىلغىنى بولىدۇ Unmute call ئاۋازنى ئېچىش Mute call ئاۋازنى تاقاش Microphone can be muted only during a call پەقەت ئاۋازلىق پاراڭلاشقان ۋاقىتتا مىكروفۇن ئېتىۋىتىش مەشغۇلاتنى قىغىنى بولىدۇ Unmute microphone مىكروفون ئاۋازىنى ئېچىش Mute microphone مىكروفون ئاۋازىنى تاقاش ChatLog Copy كۆچۈرۈش Select all ھەممىنى تاللاش pending ساقلاش ChatTextEdit Type your message here... ئۇچۇر كىرگۈزۈڭ... CircleWidget Rename circle Menu for renaming a circle چەمبىرەك ئىسمىنى كۆزگەرتىش Remove circle Menu for removing a circle چەمبىرەك ئۆچۈرۈش Open all in new window ھەممىنى يېڭى كۆزنەكتە ئېچىش Core /me offers friendship, "%1" /me ئالاقىداش تەمىنلىدى، "%1" Invalid Tox ID Error while sending friendship request ئۈنۈمسىز Tox ID You need to write a message with your request Error while sending friendship request بۇ ئىلتىماسقا بىر ئىزاھات بىرىشىڭىز كېرەك Your message is too long! Error while sending friendship request ئۇچۇرىڭىز بەك ئۇزۇنكەن! Friend is already added Error while sending friendship request ئالاقىداش قوشۇلدى Groupchat %1 DesktopNotify New message يېڭى ئۇچۇر Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen جەدۋەل 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen ETA:10:10 Filename Ausgelassen ھۆججەت ئىسمى Waiting to send... file transfer widget يوللاشنى ساقلاۋاتىدۇ... Accept to receive this file file transfer widget ھۆججەتنى قۇبۇل قىلىش Location not writable Title of permissions popup بۇ ئورۇندا سالىغىنى بولمايدۇ You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup بۇ ئورۇنغا ھۆججەت ساقلاش ھوقۇقىڭىز يوقكەن، باشقا ئورۇن تاللاڭ. Resuming... file transfer widget ئەسلىگە كىلىۋاتىدۇ... Cancel transfer يوللاشتىن چېكىنىش Pause transfer يوللاشنى توختۇتۇش Paused file transfer widget توختىدى Open file ھۆججەت ئېچىش Open file directory مۇندەرىجە ئېچىش Resume transfer يوللاش Accept transfer يوللاشقا قوشۇلۇش Save a file Title of the file saving dialog ھۆججەت ساقلاش Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window يوللنغان ھۆججەتلەر Downloads چۈشۈرۈلگەنلەر Uploads يۈكلەنگەنلەر FriendListWidget Today بۈگۈن Yesterday تۈنۈگۈن Last 7 days ئاخىرقى 7 كۈنلۈك This month مۇشۇ ئايلىق Older than 6 Months 6 ئاي بۇرۇنقى Never ئەسلا FriendRequestDialog Friend request Title of the window to aceept/deny a friend request ئالاقىداش ئىلتىماسى Someone wants to make friends with you بەزىلەر سىزنى ئالاقىداشقا قوشماقچى User ID: ئىشلەتكۈچى كىملىگى: Friend request message: ئالاقىداش ئىلتىماس ئۇچۇرى: Accept Accept a friend request قوشۇلۇش Reject Reject a friend request رەت قىلىش FriendWidget Invite to group Menu to invite a friend to a groupchat توپقا كىرىش Move to circle... Menu to move a friend into a different circle چەمبىرەككە يۆتكەش... To new circle يېڭى چەمبىرەك قۇرۇش Remove from circle '%1' چەمبىرەكتىن '%1' ئۆچۈرۈلدى Move to circle "%1" "%1" چەمبىرەككە يۆتكەلدى Open chat in new window يېڭى كۆزنەك ئېچىش Remove chat from this window بۇ كۆزنەكنى ئۆچۈرىۋىتىش To new group يېڭى توپ Invite to group '%1' '%1' توپقا تەكلىپ قىلىش Set alias... ئىزاھات... Auto accept files from this friend context menu entry ئارقىداشلار ھۆججىتىنى ئاپتۇماتىك قۇبۇل قىلىش Remove friend Menu to remove the friend from our friendlist ئالاقىداش ئۆچۈرۈش Show details تەپسىلاتى Choose an auto accept directory popup title قۇبۇل قىلىش مۇندەرىجىسى تاللاڭ New message يېڭى ئۇچۇر Online توردا Away ئايرىلىش Busy ئالدىراش Offline Ausgelassen تورسىز GeneralForm General ئورتاق Choose an auto accept directory popup title قۇبۇل قىلىش مۇندەرىجىسى تاللاڭ GeneralSettings General Settings ئاساسىي تەڭشەكلەر The translation may not load until qTox restarts. qTox قايتا قوزغاتقاندىن كېيىن كۈچكە ئىگە. Language: تىل: Show system tray icon سىستېما پاي سىنبەلگىسىنى كۆرسىتىش Enable light tray icon. toolTip for light icon setting يورۇق پاي سىنبەلگىسىنى ئېچىش. Light icon يورۇق سىنبەلگە qTox will start minimized in tray. toolTip for Start in tray setting qTox نى پاي ھالەتتە قوزغۇتۇش. Start in tray پايدىن قوززۇتۇش After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting qTox نى (X) بىلەن تاقىغاندا، پاي ھالەتكە چۈشۈرۈش. Close to tray پاينى تاقاش After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting qTox نى ۋەزىپە ئىستونىغا چۈشۈرمەك، پاي ھالەتكە چۈشۈرۈش،. Minimize to tray پاي ھالەتكە چۈشۈرۈش Autostart ئاپتۇماتىك قوزغۇتۇش Set where files will be saved. ھۆججەت ساقلاش مۇندەرىجىسى بەلگىلەش. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip مائوس ئوڭ كونۇپكىسىنى بېسىش ئارقىلىش ئالاقىداشلارنى تەڭشىسىڭىز بولىدۇ. Autoaccept files ئاپتۇماتىك ھۆججەت قۇبۇل قىلىش Set to 0 to disable 0 قىلسىڭىز تاقىلىدۇ Your status is changed to Away after set period of inactivity. بەلگىلەنگەن ۋاقىت ئىچىدە مەشغۇلات قىلمىسىڭىز ھالىتڭىز تورسىزغا ئۆزگىرىدۇ. Auto away after (0 to disable): قانچىلىك ۋاقىتتىن كېيىن توردىن چۈشۈش (0 چەكلەنمەيدۇ): Show contacts' status changes ئالاقىداشلار ھالىتىنى كۆرسىتىش Start qTox on operating system startup (current profile). qTox نى ئپتۇماتىك قوزغىتىش (مۇشۇ ئىشكەتكۈچى). Default directory to save files: ھۆججەتلەرنى ساقلايدىغان ئورۇن: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message ئۇچۇر يوللاش Smileys يۈز ئىپادىلەر Send file(s) ھۆججەت يوللاش Send a screenshot ئېكران كەسمىسى يوللاش Save chat log پاراڭ خاتىرىسىنى ساقلاش Clear displayed messages ئۇچۇرلارنى تازىلاش Cleared تازىلاندى Quote selected text نەقىل ئېلىش Copy link address ئۇلانما ئادرېسىنى كۆچۈرۈش Confirmation ئىسپاتلاش You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... پاراڭ خاتىرىسىنى كىرگۈزۈش... Export to file ھۆججەت چىقىرىش GenericNetCamView Tox video Tox سىن Show Messages ئۇچۇر كۆرۈش Hide Messages ئۇچۇر يوشۇرۇش Full Screen Toggle video preview Mute audio Mute microphone مىكروفون ئاۋازىنى تاقاش End video call سىنلىق پاراڭنى ئاخىرلاشتۇرۇش Exit full screen GroupChatForm %1 has set the title to %2 %1 ماۋزۇنى %2 گە ئۆزگەرتتى %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups توپ Create new group توپ قۇرۇش Group invites توپ تەكلىپى GroupInviteWidget Invited by %1 on %2 at %3. %1 %2 تەرىپىدىن %3 كە تەپلىپ قىلىندى. Join قېتىلىش Decline رەت قىلىش GroupWidget Set title... ماۋزۇ تەڭشەش... Open chat in new window سۆھبەتنى يېڭى كۆزنەكتە ئېچىش Remove chat from this window بۇ كۆزنەكتىن سۆھبەتنى ئۆچۈرۈش Quit group Menu to quit a groupchat توپتىن چېكىنىش %n user(s) in chat Number of users in chat New Message Online توردا IdentitySettings Public Information ئاممىۋىي ئۇچۇر Tox ID Tox كىملىك This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip باشقا Tox ئەزالىرى بۇ ھەرپ-بەلگىلەر ئارقىلىق سىز بىلەن ئالاقىلىشالايدۇ. سىز بىلەن ئالاقىلىشىشى ئۈچۈن دوستلىرىڭىزغا ھەمبەھىرلەڭ. Your Tox ID (click to copy) سىزنىڭ Tox كىملىكىڭىز (مائۇسنى چەكسىڭىز كۆچۈرۈلىدۇ) Profile خاسلىق Rename profile. tooltip for renaming profile button ئىسمىنى ئۆزگەرتىش. Go back to the login screen tooltip for logout button كىرىش كۆزنىكىگە قايتىش Logout import profile button چېكىنىش Remove password پارولنى ئۆچۈرۈش Change password پارول ئۆزگەرتىش This QR code contains your Tox ID. You may share this with your friends as well. بۇ Tox كىملىكىڭىزنىڭ ئىككىلىك كودى، دوستىڭىز بىلەن ئورتاقلاشسىڭىز بولىدۇ. Save image رەسىمنى ساقلاش Copy image رەسىمنى كۆچۈرۈش Rename rename profile button ئۆزگەرتىش Delete profile. delete profile button tooltip خاسلىقنى ئۆچۈرۈش. Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Tox تەڭشەكلىرىڭىزنى بىر ھۆججەت قىلىپ ساقلايدۇ. بۇ ھۆججەت سۆھبەت خاتىرىسىنى ئۆز ئىچىگە ئالمايدۇ. Export export profile button چىقىرىش Delete delete profile button ئۆچۈرۈش Server مۇلازىمېتىر Hide my name from the public list ئاممىۋىي تىزىملىكتە ئىسىمنى يوشۇرۇش Register تىزىملىتىش Your password پارولىڭىز Update يېڭىلاش Register on ToxMe ToxMe غا تىزىملىتىش Name for the ToxMe service. Tooltip for the `Username` ToxMe field. ToxMe دىكى ئىسمىڭىز. Optional. Something about you. Or your cat. Tooltip for the Biography text. تاللانما. سىزگە مۇناسىۋەتلىك. ياكى مۇشۈكىڭىزگە. Optional. Something about you. Or your cat. Tooltip for the Biography field. تاللانما. سىزگە مۇناسىۋەتلىك. ياكى مۇشۈكىڭىزگە. ToxMe service to register on. ToxMe غا تىزىملىتىش مۇلازىمىتى. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. ئەگەر تاللىمىسىڭىز، ToxMe دىكى ئۇچۇرلىرىڭىز ئاممىۋىي ھالەتتە كۆرىنىدۇ. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. پارولىڭىزنى چىقىرىۋىتىڭ، ئاندىن ئارخىپىڭىزنى شىفىرلاشتۇرۇپ ساقلاڭ. Name input ئىسىم كىرگۈزۈڭ Name visible to contacts ئىسمنى ئالاقىداشلار قاتارىدا كۆرسىتىش Status message input ئۇچۇر كىرگۈزۈش ھالىتىدە Status message visible to contacts ئۇچۇر ھالىتىنى ئالاقىداشقا كۆرسىتىش Your Tox ID Tox كىملىگىڭىز Save QR image as file ئىككىلىك كود رەسىمىنى ساقلاش Copy QR image to clipboard ئىككىلىك كود رەسىمىنى كۆچۈرۈش ToxMe username to be shown on ToxMe ToxMe دا ToxMe ئىسمىنى كۆرسىتىش Optional ToxMe biography to be shown on ToxMe ToxMe دا كۆرىنىدىغان شەخسىي ئۇچۇرلار ToxMe service address ToxMe مۇلازىمېتىر ئادرېسى Visibility on the ToxMe service ToxMe مۇلازىمەت كۆرۈنىشى Password پارول Update ToxMe entry ToxMe خاتىرىسىنى يېڭىلاش Rename profile. خاسلىقنى ئۆزگەرتىش. Delete profile. خاسلىقنى ئۆچۈرۈش. Export profile خاسلىقنى چىقىرىۋىلىش Remove password from profile خالىقتىن پارولنى ئۆچۈرۈش Change profile password خاسلىق پارولىنى ئۆگەرتىش My name: ئىسمىڭىز: My status: ھالىتىڭىز: My username ئىسمىم My biography تەرجىمھالىم My profile خاسلىقىم LoadHistoryDialog Load History Dialog بۇرۇنقى خاتىرىلەرنى يۈكلەش Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: ئىسىم: Password: پاورل: Confirm: جەزىملەش: Password strength: %p% پارول ئۇزۇنلىقى: %p% Create Profile خاسلىق قۇرۇش If the profile does not have a password, qTox can skip the login screen ئەگەر ئارخىپىڭىزنىڭ پارولى بولمىسا، بۇ كۆزنىكىدىن ئاتلاپ ئۆتۈپ كىتىدۇ Load automatically ئۆزلۈكىدىن يۈكلەش Load يۈكلەش Load Profile خاسلىق يۈكلەش New Profile يېڭى خاسلىق Couldn't create a new profile يېڭى خاسلىق قۇرۇلمىدى The username must not be empty. ئىسمىڭىزنى بوش قويماڭ. The password must be at least 6 characters long. پارول 6 ھەرپتىن ئاز بولمىسۇن. The passwords you've entered are different. Please make sure to enter same password twice. ئىككى قېتىم كىرگۈزگەن پارول ئوخشاش ئەمەس. ئىككى پارولنىڭ ئوخشاشلىقىنى جەزىملەشتۈرۈڭ. A profile with this name already exists. بۇ خاسلىق بار. Password protected profiles can't be automatically loaded. پاروللانغان خاسلىقنى ئۆزلىكىدىن يۈكلىيەلمەيدۇ. Couldn't load profile خاسلىق يۈكلەنمىدى There is no selected profile. You may want to create one. خاسلىق تاللانمىدى. يېڭىدىن قۇرسىڭىزمۇ بولىدۇ. Couldn't load this profile بۇ خاسلىق يۈكلەنمىدى This profile is already in use. بۇ خاسلىق ئىشلىتىلىۋاتىدۇ. Wrong password. پارول خاتا. Import كىرگۈزۈش Username input field ئىسىم كىرگۈزۈش رامكىسى Password input field, you can leave it empty (no password), or type at least 6 characters پارول كىرگۈزىش كۆزنىكى، بوش قويسىڭىزمۇ ياكى ئاز بولغاندا 6 ھەرپ كىرگۈزمۇ بولىدۇ Password confirmation field جەزىملەش پاورل رامكىسى Create a new profile button يېڭى خاسلىق قۇرۇش كونۇپكىسى Profile list خاسلىقلار قاتارى List of profiles بارلىق خاسلىقلار Password input پارول كىرگۈزۈڭ Load automatically checkbox ئۆزلۈكىدىن يۈكلەش تاللانمىسى Import profile خاسلىق كىرگۈزۈش Load selected profile button ئارخىپ تاللاش كونۇپكىسى New profile creation page يېڭى خاسلىق قۇرۇش بېتى Loading existing profile page خاسلىق يۈكلىنىش بېتى MainWindow Your name ئىسمىڭىز Your status ھالىتىڭىز ... Ausgelassen ... Add friends دوست قوشۇش Create a group chat توپ قۇرۇش View completed file transfers يوللىنىپ بولغان ھۆججەتلەرنى كۆرۈش Change your settings تەڭشەك ئۆزگەرتىش Close تاقاش Open profile خاسلىق ئېچىش Open profile page when clicked خاسلىق بېتى ئېچىش Status message input ئۇچۇر كۈرگۈزۈش ھالىتى Set your status message that will be shown to others باشقىلارغا كۆرسىتىدىغان ئۇچۇر ھالىتىڭىز Status ھالەت Set availability status ھالەت تەڭشىكى Contact search ئالاقىداش ئىزدەش Contact search input for known friends دوستلار قاتارىدىن ئالاقىداش ئىزدەش رامكىسى Sorting and visibility تەرتىپلەش ۋە كۆرسۈتۈش Set friends sorting and visibility دوستلارنىڭ تەرتىپلەش ۋە كۆرۈنۈش تەڭشىكى Open Add friends page دوست قوشۇش بېتىنى ئېچىش Groupchat توپ Open groupchat management page توپ باشقۇرۇش بېتىنى ئېچىش File transfers history ھۆججەت يوللاش خاتىرىسى Open File transfers history ھۆججەت يوللاش خاتىرىسىنى ئېچىش Settings تەڭشەكلەر Open Settings تەكشەلەرنى ئېچىش Nexus View OS X Menu bar كۆرۈش Window OS X Menu bar چېسلا Minimize OS X Menu bar ئەڭ كىچىك Bring All to Front OS X Menu bar ئەڭ ئۈستىگە يۆتكەش Exit Fullscreen تولۇق ئېكراندىن چېكىنىش Enter Fullscreen تولۇق ئېكران ھالىتى NotificationEdgeWidget Unread message(s) ئوقۇلمىغان ئۇچۇر PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK قۇلۇپلاندى PrivacyForm Privacy مەخپىيەتلىك Confirmation ئىسپاتلاش Do you want to permanently delete all chat history? بارلىق سۆھبەت خاتىرىسنى مەڭگۈلۈك ئۆچۈرەمسىز؟ PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting ئۇچۇر كىرگۈزۈۋاتقانلىقىڭىزنى دوستىڭىز كۆرەلەيدۇ. Send typing notifications ئۇچۇر كىرگۈزۈۋاتقانلىقىنى يوللاش Keep chat history سۆھبەت خاتىرىسىنى ساقلاش NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam بۇ Tox كىملىكىڭىزنىڭ بىرقىسىمى. ئەگەر ئەخلەت تەپلىكلەر ئاۋارە قىلسا بۇنى ئۆزگەرتسىڭىز بولىدۇ. كونا Tox كىملىكىڭىزدىن سىزنى قوشالمايدۇ، بىراق قوشۇلۇپ بولغان دوستلار ساقلىنىپ قالىدۇ. NoSpam ئىم NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. بۇ Tox كىملىكىڭىزنىڭ بىرقىسىمى. ئەگەر ئەخلەت تەپلىكلەر ئاۋارە قىلسا بۇنى ئۆزگەرتسىڭىز بولىدۇ. Generate random NoSpam ئىختىيارى ئىم ھاسىل قىلىش Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting سۆھبەت خاتىرىسىنى ساقلاش ياسىلىش باسقۇچىدا. ساقلاش فورماتى كەلگۈسىدە ئۆزگىرىشى مۇمكىن، بۇنداقتا ئۇچۇرلىرىڭىز يوقاپ كىتىشى مۇمكىن. Privacy مەخپىيەتلىك BlackList قارا تىزىملىك Filter group message by group member's public key. Put public key here, one per line. ئەزالارنىڭ ئاچقۇچ كودى ئارقىلىق سۆھبەت خاتىرىسىنى سۈزۈش، ھەر بىر كود بىر قۇردىن. Profile Failed to derive key from password, the profile won't use the new password. پارولدىن ئاچقۇچنى تاپالمىى، خاسلىق بۇر پارولنى ئىشلىتەلمەيدۇ. Couldn't change password on the database, it might be corrupted or use the old password. سانداندىكى پارول ئۆزگەرمىدى، بەلكىم ساندان بۇزلغان ياكى پارول خاتا بولۇشى مۇمكىن. Toxing on qTox qTox ئارقىلىق سۆھبەتلىشىش ProfileForm Choose a profile picture باش رەسىم تاللاش Error خاتا Rename "%1" renaming a profile ئۆزگەرتىش "%1" Unable to open this file. بۇ ھۆججەت ئېچىلمىدى. Current profile: ھازىرقى ئارخىپ: Remove ئۆچۈرۈش Unable to read this image. بۇ رەسىمنى ئوقۇيالمىدى. The supplied image is too large. Please use another image. بۇ رەسىم بەك چوڭكەن. باشقا رەسىم تاللاڭ. Couldn't rename the profile to "%1" "%1" ئارخىپ ئىسمىنى ئۆزگەرتكىنى بولمىدى Location not writable Title of permissions popup يېزىش چەكلەنگەن ئورۇن You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup بۇ ئورۇنغا يېزىش ھوقۇقىڭىز يوق، باشقا ئورۇن تاللاڭ ياكى ساقلاشنى بىكار قىلىڭ. Failed to copy file ھۆججەت كۆچۈرۈلمىدى The file you chose could not be written to. تاللىغان ھۆججەتكە يىزىش يازالمىدى. Really delete profile? deletion confirmation title ئارخىپنى راسلا ئۆچۈرەمسىز؟ Nothing to remove ئۆچۈرىدىغان ھېچنىمە يوق Your profile does not have a password! ئارخىپىڭىزغا پارولانمىدى! Really delete password? deletion confirmation title پارولنى راسلا ئۆچۈرەمسىز؟ Please enter a new password. يېڭى پارول كىرگۈزىڭ. Are you sure you want to delete this profile? deletion confirmation text ئارخىپنى راسلا ئۆچۈرەمسىز؟ Save save qr image ساقلاش Save QrCode (*.png) save dialog filter ئىككىلىك كودنى ساقلاش (*.png) Files could not be deleted! deletion failed title ھۆججەت ئۆچۈرۈلمىدى! Register (processing) تىملاش (ھازىرلىنىۋاتىدۇ) Update (processing) يېڭىلاش (ھازىرلىنىۋاتىدۇ) Done! پۈتتى! Account %1@%2 updated successfully %1@%2 ھېسابى يېڭىلىنىپ بولدى Successfully added %1@%2 to the database. Save your password %1@%2 ساندانغا قوشۇلدى، پارولنى ياخشى ساقلاڭ Toxme error Toxme خاتا Register تىزىملىتىش Update يېڭىلاش Change password button text پارول ئۆزگەرتىش Set profile password button text پارول بەلگىلەش Current profile location: %1 ھازىرقى ئارخىپ ئورنى: %1 Couldn't change password پارول ئۆزگەرتىلمىدى This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). بۇ ئۇچۇرلار ئارقىلىق باشقا Tox ئىشلەتكۈچىلەر بىلەن ئالاقىلىشالايسىز. بۇنى دوستىڭىز بىلەن ئورتاقلاشسىڭىز ئۇلار سىز بىلەن ئالاقىلىشالايدۇ. بۇ Tox كىملىك NoSpam كودى(كۆك) ۋە تەستىق كودىنى(قوڭۇر) ئۆز ئىچىگە. Empty path is unavaliable بوش ئادرېسنى ئىشلەتكىنى بولمايدۇ Failed to rename ئۆزگەرتەلمىدى Profile already exists ئارخىپ بار A profile named "%1" already exists. ئارخىپ ئىسىمى "%1" بار. Empty name بوش ئىشىم Empty name is unavaliable بوش ئىشىم Empty path قۇرۇق ئادرېس Couldn't change password on the database, it might be corrupted or use the old password. سانداندىكى پارول ئۆزگەرمىدى، ساندان بۇزۇلغان ياكى پارول خاتا بولۇشى مۇمكىن. Export profile ئارخىپ چىقىرىش Tox save file (*.tox) save dialog filter Tox ئارخىپ ھۆججىتى (*.tox) The following files could not be deleted: deletion failed text part 1 بۇ ھۆججەتلەنى ئۆچۈرەلمىدى: Please manually remove them. deletion failed text part 2 قولدا ئۆچۈرىۋىتىڭ. Are you sure you want to delete your password? deletion confirmation text پارولنى راستلا ئۆچۈرەمسىز؟ Images (%1) filetype filter رەسىم (%1) ProfileImporter Import profile import dialog title ئارخىپ كىرگۈزىش Tox save file (*.tox) import dialog filter Tox ئارخىپ ھۆججىتى (*.tox) Ignoring non-Tox file popup title Tox ھۆججىتى بولمىسا كارى بولماسلىق Warning: You have chosen a file that is not a Tox save file; ignoring. popup text ئاگاھلاندۇرۇش: سىز تاللىغان ھۆججەت Tox ئارخىپ ھۆججىتى ئەمەس؛ كارىم يوق. Profile already exists import confirm title ئارخىپ بار A profile named "%1" already exists. Do you want to erase it? import confirm text ئارخىپ ئىسمى "%1" بار، بۇنى ئۆچۈرۈۋىتەمسىز؟ File doesn't exist ھۆججەت يوق Profile doesn't exist ئارخىپ يوق Profile imported ئارخىپ كىرگۈزۈلدى %1.tox was successfully imported %1.tox كىرگۈزۈلدى QApplication LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout RTL Ok تامام Cancel بىكار قىلىش Yes ھەئە No ياق QMessageBox Couldn't add friend دوست قوشۇلمىدى %1 is not a valid Toxme address. %1 ئۈنۈملۈك Toxme ئادرېسى ئەمەس. You can't add yourself as a friend! When trying to add your own Tox ID as friend ئۆزىڭىزنى قوشالمايسىز! QObject Tox URI to parse Tox ئۇلانما ئارېسى Starts new instance and loads specified profile. يېڭىدىن ئارخىپ قۇرۇپ بەلگىلەنگەن ھۆججەتنى كىرگۈزۈش. profile ئارخىپ Default ئىتىراپلىق Blue كۆك Olive زەيتۇن Red قىزىل Violet بىنەپشە Incoming call... تېلېفون كەلدى... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! مەن %1! بىز Tox تا پاراڭلىشامدۇق ؟ None No camera device set يوق Desktop Desktop as a camera input for screen sharing ئۈستەل يۈزى Server doesn't support Toxme مۇلازىمېتىر Toxme نى قوللىمايدۇ You're making too many requests. Wait an hour and try again بەك جىق ئۇچۇر ئەۋەتىپ كەتتىڭىز. بىر سائەتتىن كىيىن قايتا سىناپ بېقىڭ This name is already in use بۇ ئىسىم ئىشلىتىلىپ بولغان This Tox ID is already registered under another name بۇ Tox كىملىك تىزىملىتىلىپ بولغان Please don't use a space in your name ئىسمىڭىز ئارسىدا بوشلۇق ئىشلەتمەڭ Password incorrect پارول خاتا You can't use this name بۇ ئىسىمنى ئىشلىتەلمەيسىز Name not found ئىسىم تېپىلمىدى Tox ID not sent Tox كىملىك يوللانمىدى That user does not exist بۇنداق ئىشلەتكۈچى يوق Error خاتا qTox couldn't open your chat logs, they will be disabled. سۆھبەت خاتىرىڭىز ئېچىلمىدى، ئۇلارنى چەكلىۋەتسىڭىز بولىدۇ. Problem with HTTPS connection HTTPS ئۇلىنالمىدى Internal ToxMe error ToxMe ئىچكى خاتالىقى Reformatting text in progress.. يېزىق فورماتى تەڭشىلىۋاتىدۇ... Starts new instance and opens the login screen. يېڭىدىن ئۆزنەك قۇرۇپ كىرىش كۆزنىكىنى ئېچىش. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status توردا away contact status چىقىپ كىتىش busy contact status ئالدىراش offline contact status توردا يوق blocked contact status RemoveFriendDialog Remove friend دوست ئۆچۈرۈش Also remove chat history سۆھبەت خاتىرىسىنىمۇ بىرگە ئۆچۈرۈش Remove ئۆچۈرۈش Are you sure you want to remove %1 from your contacts list? ئالاقىداشلار تىزىملىكىدىن %1 نى ئۆچۈرەمسىز؟ Remove all chat history with the friend if set بۇنى تەڭشىسىڭىز بۇ دوستىڭىزنىڭ بارلىق سۆھبەت خاتىرىسىمۇ ئۆچۈپ كىتىدۇ ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet بىر چىكىپ مەلۇم دائىرىنى تاللاڭ، %1 نى بېسىپ qTox كۆزنىكى يوشۇرۇن ھالەتكە ئۆتكۈزسىڭىز ياكى %2 نى بېسىپ ۋاز كەچسىڭىز بولىدۇ. Space [Space] key on the keyboard بوشلۇق Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected %1 كونۇپكىسى ئارقىلىق تاللانغان ئېكران رەسىمىنى يوللىسىڭىز، %2 كونۇپكىسى ئارقىلىق qTox كۆزنىكىنى يوشۇرسىڭىز، ياكى %3 كونۇپكىسى ئارقىلىق ۋاز كەچسىڭىز بولىدۇ. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form جەدۋەل Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password پارول بەلگىلەش Confirm: جەزىملەش: Password: پارول: Password strength: %p% پارول ئۇزۇنلىقى: %p% The password is too short پارول بەك قىسقا The password doesn't match. ئىككى پارول ئوخشىمىدى. Confirm password جەزىملەش پارولى Confirm password input جەزىملەش پارولى كىرگۈزۈش Password input پارول كىرگۈزۈش Password input field, minimum 6 characters long پارول رامكىسى، 6 ھەرپتىن تۆۋەن بولمىسۇن Settings Circle #%1 چەمبىرەك #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI دوست قوشۇش Do you want to add %1 as a friend? %1 نى دوستقا قوشامسىز؟ User ID: ئىشلەتكۈچى ID سى: Friend request message: دوست ئىستىكى ئۇچۇرى: Send Send a friend request يوللاش Cancel Don't send a friend request چېكىنىش UserInterfaceForm None يوق User Interface كۆرۈنمە يۈز UserInterfaceSettings Chat سۆھبەت Base font: خەت شەكىلى: px px Size: ھەجىم New text styling preference may not load until qTox restarts. qTox قايتا قوزغالغاندا بۇ يېزىق ئۇسلۇبى يۈكلەنمەسلىكى مۇمكىن. Text Style format: يېزىق ئۇسلۇبى: Select text styling preference. يېزىق ئۇسلۇب تەڭشىكى. Plaintext ساپ تېسىت Show formatting characters يېزىق فورماتىنى كۆرسىتىش Don't show formatting characters يېزىق فورماتىنى كۆرسەتمەسلىك New message يېڭى ئۇچۇر Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting ئۇچۇر قۇبۇل قىلغاندا ئوچۇق كۆزنەك بولمىسا كۆزنەك ئېچىش. Open window كۆزنەك ئېچىش Contact list ئالاقىداشلار تىزىملىكى If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning ئەگەر تاللانسا، توپ ئالاقىداشلار تىزىملىكىنىڭ چوقىسىغا چىقىدۇ، ئۇنداق بولمىسا ئۇلار ئاستىدا كۆرىنىدۇ. Place groupchats at top of friend list توپنى چوقىلاش Your contact list will be shown in compact mode. toolTip for compact layout setting ئالاقىداشلىرىڭىز قىسقارتىلما ھالەتتە كۆرىنىدۇ. Compact contact list قىسقاتىلما ئالاقىداشلار قاتارى Multiple windows mode كۆپ كۆزنەك ھالىتى Open each chat in an individual window ھەر بىر سۆھبەتنى يېڭى كۆزنەكتە ئېچىش Emoticons يۈز ئىپادىسى Use emoticons ياز ئىپادىسى ئىشلىتىش Smiley Pack: Text on smiley pack label يۈز ئىپادە بولىقى: Emoticon size: يۈز ئىپادە ھەجىمى: px px Theme تېما Style: ئۇسلۇب: Theme color: تېما رېڭى: Timestamp format: ۋاقىت فورماتى: Date format: چىسلا فورماتى: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons ئەگەر قوزغىتىلسا، باش سۈرىتى يوق ئالاقىداشلارغا Tox كىملىكىگە ئاساسەن باش سۈرەت ھاسىل قىلىۇ، قايتا قوزغالغاندا كۈچكە ئېگە بولىدۇ. Use identicons instead of empty avatars بوش باش سۈرەتنى identicons غا ئالماشتۇرۇش Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound ئاۋازنى قويۇش Play sound while Busy ئالدىراش ھالەتتە ئاۋازنى قويۇۋىرىش Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' توردا Away Button to set your status to 'Away' ئايرىلىش Busy Button to set your status to 'Busy' ئالدىراش toxcore failed to start, the application will terminate after you close this message. يادرولۇق پروگرامما قوزغىلالمىدى، پىروگىرامما تاقالماقچى. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text ۋاكالەتچى مۇلازىمىتېر تەڭشىگەنلىكىڭىزدىن، يادرولۇق پروگرامما قوزغىلالمىدى، تەڭشەكنى ئۆزگەرتكەندىن كېيىن qTox نى قايتا قوزغۇتۇڭ. File ھۆججەت Edit Profile ئارخىپ تەھرىرلەش Change Status ھالەت ئۆزگەرتىش Log out قۇلۇپلاش Edit تەھرىرلەش Logout Tray action menu to logout user قۇلۇپلاش Exit Tray action menu to exit tox چېكىنىش Filter... سۈزۈش... Contacts ئالاقىداشلار Add Contact... ئالاقىداش قوشۇش... Next Conversation كېيىنكى پاراڭ Previous Conversation ئالدىنقى پاراڭ Executable file popup title ئىجرا بولىدىغان ھۆججەت You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text سىز qTox ئارقىلىق ئىجرا بولىدىغان ھۆججەت ئاچماقچى، بۇ ھۆججەت بەلكىم كومپيۇتېرىڭىزغا زىيان يەتكۈزىشى مۇمكىن، راستىنلا بۇ ھۆججەتنى ئاچامسىز؟ Couldn't request friendship ئالاقىداش قوشۇلمىدى Status ھالەت Your name ئاتىڭىز Message failed to send ئۇچۇر يوللانمىدى Create new group... توپ قۇرۇش... Add new circle... چەمبىرەككە چىقىرىش... %n New Friend Request(s) %n دوستلۇق ئىلتىماسى %n New Group Invite(s) %n پارچە توپ تەكلىپى By Name ئات بويىنچە By Activity ئاكتىپلىق بويىنچە All ھەممىسى Online توردا Offline Ausgelassen ئايرىلىش Friends دوستلار Groups توپلار Search Contacts ئالاقىداش ئىزدەش Groupchat #%1 توپ سۆھبەت #%1 Show Tray action menu to show qTox window كۆرسىتىش Add friend title of the window دوست قوشۇش Group invites title of the window توپ تەكلىپى File transfers title of the window ھۆججەت يوللاش Settings title of the window تەڭشەك My profile title of the window مېنىڭ Failed to send file "%1" ھۆججەت %1 يوللاش مەغلۇب بولدى File sent sent you a friend request. invites you to join a group. qTox/translations/uk.ts000066400000000000000000003644051415623743500155470ustar00rootroot00000000000000 AVForm Audio/Video Аудіо/Відео Default resolution Роздільна здатність за замовчуванням Disabled Вимкнений Select region Обрати ділянку Screen %1 Екран %1 Audio Settings Налаштування аудіо Gain Підсилення Playback device Пристрій відтворення Use slider to set volume of your speakers. Використовуйте повзунок для регулювання гучності динаміків. Capture device Пристрій захоплення Volume Гучність Video Settings Налаштування відео Video device Відео пристрій Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Встановлює роздільну здатність для Вашої камери. За більших значень Ваші співрозмовники будуть бачити Вас чіткіше. Зауважте, для кращої якості відео потрібен більш швидкий інтернет. Іноді Ваше інтернет з’єднання може бути недостатньо якісним для досить високої якості відео, що може спричинити проблеми під час відео зв’язку. Resolution Роздільна здатність Rescan devices Пересканувати пристрої Test Sound Перевірити звук Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Вмикає експерментальну звукову систему з підтримкою ехо пригнічування, потребує перезавантаження qTox. Enable experimental audio backend Увімкнути експерментальний аудіо бекенд Audio quality Якість аудіо Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. Якість передаваємого звуку. Зменшіть цей параметр якщо пропускна здатність з'єднання недостатня або ви хочете зменшити використання Інтернет трафіку. High (64 kbps) Висока (64 кбіт) Medium (32 kbps) Середня (32 кбіт) Low (16 kbps) Низька (16 кбіт) Very low (8 kbps) Дуже низька (8 кбіт) Threshold Поріг AboutForm About Про програму Original author: %1 Автор: %1 You are using qTox version %1. Ви використовуєте qTox версії %1. Commit hash: %1 Хеш коміту: %1 toxcore version: %1 Версія toxcore: %1 Qt version: %1 Версія Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Список знаних проблем знаходиться у Github за адресою %1. Якщо ви знайшли помилку чи ваду у системі безпеки qTox, будь ласка, повідомте про неї згідно до інструкції у нашій статті на wiki %2. Click here to report a bug. Натисніть щоб повідомити про помилку. See a full list of %1 at Github `%1` is replaced with translation of word `contributors` Переглянути повний список %1 на Github bug-tracker Replaces `%1` in the `A list of all known…` баг-трекері Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` Writing Useful Bug Reports contributors Replaces `%1` in `See a full list of…` Співавтори AboutFriendForm Dialog Діалог username Ім'я користувача status message Статус Used aliases: Використовуємі псевдоніми: HISTORY OF ALIASES ІСТОРІЯ ПСЕВДОНІМІВ Automatically accept files from contact if set Автоматично приймати файли від контакту, якщо вибрано Auto accept files Автоматично приймати файли Default directory to save files: Каталог для зберігання файлів за замовчуванням: Auto accept for this contact is disabled Автоматичний прийом файлів для цього контакту вимкнено Auto accept call: Автоприйом дзвінків: Manual Ручне Audio Аудіо Audio + Video Аудіо + Відео Automatically accept group chat invitations from this contact if set. Автоматично приймати запрошення в груповий чат від цього контакту якщо встановлено. Auto accept group invites Автоматично приймати запрошення в групи Remove history (operation can not be undone!) Видалити історію (операцію відмінити неможливо!) Notes Нотатки Input field for notes about the contact Поле для приміток про контакт You can save comment about this contact here. Ви можете залишити коментар про даний контакт тут. History removed Історію видалено Choose an auto accept directory popup title Оберіть теку, для автоматичного отримання файлів <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Підтвердження Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version Версія License Ліцензія Authors Автори Known Issues Відомі проблеми Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends Додати друзів Invalid Tox ID format Некоректний формат Tox ID Send friend request Надіслати запит на дружбу Couldn't add friend Не можемо додати друга Add a friend Додати друга Friend requests Запити дружби Accept Прийняти Reject Відхилити Tox ID, either 76 hexadecimal characters or name@example.com Tox ID - або 76 символів 0-9 A-F, або name@example.com Type in Tox ID of your friend Впишіть Tox ID вашого друга Friend request message Повідомлення у запрошенні на дружбу Type message to send with the friend request or leave empty to send a default message Впишіть повідомлення у запрошення на дружбу, або залишіть його порожнім, щоб надіслати стандартне повідомлення %1 Tox ID is invalid or does not exist Toxme error %1 Tox ID є неприйнятним або не існує You can't add yourself as a friend! When trying to add your own Tox ID as friend Ви не можете додати самого себе до друзів! Open contact list Відрити список контактів Couldn't open file Помилка відкриття файлу Couldn't open the contact file Error message when trying to open a contact list file to import Помилка відкриття файлу контактів Invalid file Неправильний файл We couldn't find any contacts to import in this file! Контактів для імпорту не знайдено! Tox ID Tox ID of the person you're sending a friend request to Ідентифікатор Tox either 76 hexadecimal characters or name@example.com Tox ID format description 76 шістнадцяткових символів або name@example.com Message The message you send in friend requests Повідомлення Open Button to choose a file with a list of contacts to import Відкрити Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Привіт, я %1! Додай мене в свій список контактів, будь ласка. Import a list of contacts, one Tox ID per line Імпортувати список контактів, один Ідентифікатор Tox на рядок Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts Імпортувати контакти AdvancedForm Advanced Більше Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. Якщо Ви не %1, будь ласка, %2 змінюйте тут нічого. Зміни в цьому місці можуть призвести до проблем із qTox та навіть до втрати ваших даних, наприклад, історії. really дійсно not не IMPORTANT NOTE ВАЖЛИВА ПРИМІТКА Reset settings Скинути налаштування All settings will be reset to default. Are you sure? Всі налаштування будуть скинуті до стандартних. Ви впевнені? Yes Так No Ні Call active popup title Дзвінок активний You can't disconnect while a call is active! popup text Ви не можете від'єднатись під час активного дзвінка! Save File Зберегти файл Logs (*.log) Логи (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Зберігати налаштування в робочій теці замість звичайної теки конфігурацій Make Tox portable Портативний запуск Tox Reset to default settings Скинути на типові значення Portable Connection Settings Параметри підключення Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Увімкнути IPv6 (рекомендовано) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Вимкнення цього дозволить, наприклад, використовувати qTox через Tor. Проте це збільшить навантаження на мережу Tox, то ж вимикайте лише в разі необхідності. Enable UDP (recommended) Text on checkbox to disable UDP Увімкнути UDP (рекомендовано) Proxy type: Тип проксі: Address: Text on proxy addr label Адреса: Port: Text on proxy port label Порт: None Відсутній SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button Перепідключитись Debug Відладка Export Debug Log Експортувати логи відладки Copy Debug Log Скопіювати логи відладки Enable LAN discovery ChatForm Send a file Надіслати файл qTox wasn't able to open %1 qTox не може відкрити файл %1 %1 calling Дзвінок від %1 Unable to open Неможливо відкрити Bad idea Погана ідея Calling %1 Дзінок до %1 Failed to open temporary file Temporary file for screenshot Помилка відкриття тимчасового файлу qTox wasn't able to save the screenshot qTox не зміг зберегти скриншот Call with %1 ended. %2 Дзвінок з %1 завершено. %2 Call duration: Тривалість дзвінка: %1 is typing %1 набирає повідомлення Copy Копіювати You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" %1 тепер вже відомий як %2 Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Не можу розпочати аудіодзвінок Start audio call Почати аудіодзвінок End audio call Завершити аудіодзвінок Cancel audio call Скинути аудіодзвінок Accept audio call Прийняти аудіо дзвінок Can't start video call Не можу розпочати відеодзвінок Start video call Почати відеодзвінок End video call Завершити відеодзвінок Cancel video call Скинути відеодзвінок Accept video call Прийняти відеодзвінок Sound can be disabled only during a call Звук можливо вимкнути лише під час дзвінка Unmute call Відновити дзвінок Mute call Призупинити дзвінок Microphone can be muted only during a call Вимкнути мікрофон можна лише під час дзвінка Unmute microphone Увімкнути мікрофон Mute microphone Вимкнути мікрофон ChatLog Copy Копіювати Select all Виділити всі pending очікування ChatTextEdit Type your message here... Наберіть Ваше повідомлення тут… CircleWidget Rename circle Menu for renaming a circle Перейменувати коло Remove circle Menu for removing a circle Видалити коло Open all in new window Відкрити всі в новому вікні Core /me offers friendship, "%1" /me пропонує дружбу, "%1" Invalid Tox ID Error while sending friendship request Невірний Tox ID You need to write a message with your request Error while sending friendship request Напишіть повідомлення з вашим запитом Your message is too long! Error while sending friendship request Ваше повідомлення завелике! Friend is already added Error while sending friendship request Друга вже додано Groupchat %1 DesktopNotify New message Нове повідомлення Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form 10Mb 10Мб 0kb/s 0кб/с ETA:10:10 Лишилось:10:10 Filename Назва файлу Waiting to send... file transfer widget Очікування передачі... Accept to receive this file file transfer widget Підтвердіть щоб прийняти файл Location not writable Title of permissions popup Немає прав на запис You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Ви не маєте прав на запис за цим розташуванням. Оберіть інше місце призначення, або скасуйте передачу. Resuming... file transfer widget Продовження... Cancel transfer Скасувати передачу Pause transfer Призупинити передачу Paused file transfer widget Призупинено Open file Відкрити файл Open file directory Відкрити каталог з файлом Resume transfer Продовжити передачу Accept transfer Підтвердити передачу Save a file Title of the file saving dialog Зберегти файл Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window Передані файли Downloads Завантажені Uploads Вивантажені FriendListWidget Today Сьогодні Yesterday Вчора Last 7 days Останні 7 днів This month Цього місяця Older than 6 Months Раніше 6 місяців Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Запит на дружбу Someone wants to make friends with you Дехто хоче долучитися до переліку ваших друзів User ID: ID користувача: Friend request message: Повідомлення запиту: Accept Accept a friend request Прийняти Reject Reject a friend request Відхилити FriendWidget Invite to group Menu to invite a friend to a groupchat Запросити до групи Open chat in new window Відкрити чат в новому вікні Remove chat from this window Видалити чат з цього вікна Move to circle... Menu to move a friend into a different circle Перемістити в коло... To new circle До нового кола Remove from circle '%1' Видалити з кола '%1' Move to circle "%1" Перемістити в коло "%1" Set alias... Встановити псевдонім… Auto accept files from this friend context menu entry Автоматично приймати файли від даного друга Show details Показати деталі New message Нове повідомлення Online В мережі Away Відійшов Busy Зайнятий Offline Не в мережі Choose an auto accept directory popup title Оберіть теку, для автоматичного отримання файлів Remove friend Menu to remove the friend from our friendlist Вилучити з друзів To new group До нової групи Invite to group '%1' Запросити до групи '%1' GeneralForm General Основні Choose an auto accept directory popup title Оберіть теку, для автоматичного отримання файлів GeneralSettings General Settings Основні параметри The translation may not load until qTox restarts. Переклад буде застосовано після перезавантаження qTox. Show system tray icon Показувати піктограму в системному лотку Enable light tray icon. toolTip for light icon setting Увімкнути світлі піктограми. Start qTox on operating system startup (current profile). Запускти qTox при завантаженні операційної системи (поточний профіль). qTox will start minimized in tray. toolTip for Start in tray setting qTox буде запускатися згорнутим в лоток. Start in tray Запускати у системному лотку After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting При дії закриття (X) qTox буде згортатися до лотку, замість виходу з програми. Close to tray Закривати до лотку After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting При дії згортання (_) qTox буде згортатися до лотку, замість панелі задач. Minimize to tray Мінімізувати до лотку Autostart Автозапуск Set where files will be saved. Вкажіть, куди саме зберігати файли. Your status is changed to Away after set period of inactivity. Ваш статус буде змінено на 'Відійшов' після вказаного проміжку часу. Auto away after (0 to disable): Автостатус 'Відійшов' після (0, щоб вимкнути): Default directory to save files: Каталог для зберігання файлів за замовчуванням: Show contacts' status changes Показувати зміну статусів контактів Set to 0 to disable Встановіть 0, аби вимкнути Light icon Світлі піктограми You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Ви також можете встановити це значення до кожного друга окремо викликавши правою кнопкою меню навпроти нього. Autoaccept files Автоматично приймати файли Language: Мова: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Відправити повідомлення Smileys Смайлики Send file(s) Відправити файл(и) Send a screenshot Відправити знімок екрану Save chat log Зберегти чат Clear displayed messages Очистити показані повідомлення Cleared Очищено Quote selected text Цитувати виділений текст Copy link address Скопіювати адресу посилання Confirmation Підтвердження You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Завантажити історію чату... Export to file GenericNetCamView Tox video Tox відео Show Messages Показати повідомлення Hide Messages Приховати повідомлення Full Screen Toggle video preview Mute audio Mute microphone Вимкнути мікрофон End video call Завершити відеодзвінок Exit full screen GroupChatForm %1 has set the title to %2 %1 встановив тему %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups Групи Create new group Створити нову групу Group invites Групові запрошення GroupInviteWidget Invited by %1 on %2 at %3. Join Долучитися Decline Відмовитись GroupWidget Open chat in new window Відкрити чат в новому вікні Remove chat from this window Видалити чат з цього вікна Quit group Menu to quit a groupchat Вийти з групи Set title... Встановити заголовок… %n user(s) in chat Number of users in chat New Message Online В мережі IdentitySettings Public Information Публічна інформація Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Цей набір символів дозволяє клієнтам Tox зв'язатись з Вами. Для комунікації поділіться ним з своїм друзями. Your Tox ID (click to copy) Ваш Tox ID (клацніть аби скопіювати) Profile Профіль Rename profile. tooltip for renaming profile button Перейменувати профіль. Delete profile. delete profile button tooltip Видалити профіль. Go back to the login screen tooltip for logout button Повернутись до екрану входу Logout import profile button Вийти з облікового запису Remove password Видалити пароль Change password Змінити пароль This QR code contains your Tox ID. You may share this with your friends as well. Цей QR код містить ваш Tox ID. Ви можете ділитися ним зі своїми друзями. Save image Зберегти зображення Copy image Копіювати зображення Rename rename profile button Перейменувати Export export profile button Експорт Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Дозволяє Вами експортувати ваш профіль Tox в файл. Профіль не містить Вашу історію переписки. Delete delete profile button Вилучити Server Сервер Hide my name from the public list Приховати моє ім'я з публічного списку Register Реєстрація Your password Ваш пароль Update Оновити Register on ToxMe Зареєструватись на ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Ім'я у сервісі ToxMe. Optional. Something about you. Or your cat. Tooltip for the Biography text. Опціонально. Щось про Вас. Чи Вашого кота. Optional. Something about you. Or your cat. Tooltip for the Biography field. Опціонально. Щось про Вас. Чи Вашого кота. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Якщо не вибрано, записи ToxMe будуть доступні всім. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Прибрати Ваш пароль та шифрування з Вашого профілю. Name input Поле вводу імені Name visible to contacts Ім'я, що буде показане контактам Status message input Поле ввода статуса Status message visible to contacts Статус, що буде показаний контактам Your Tox ID Ваш Tox ID Save QR image as file Зберегти QR код у файл Copy QR image to clipboard Скопіювати QR код у буфер обміну ToxMe username to be shown on ToxMe ToxMe нікнейм, що відображатиметься у ToxMe Optional ToxMe biography to be shown on ToxMe Опціонально. Біографія, що відображатиметься у ToxMe ToxMe service address Visibility on the ToxMe service Password Пароль Update ToxMe entry Оновити запис ToxMe Rename profile. Перейменувати профіль. Delete profile. Видалити профіль. Export profile Експорт профілю Remove password from profile Видалити пароль з профілю Change profile password Змінити пароль профілю My name: Моє ім'я: My status: Мій статус: My username Мій нікнейм My biography Моя біографія My profile Мій профіль LoadHistoryDialog Load History Dialog Історія Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Ім'я користувача: Password: Пароль: Confirm: Підтвердження: Password strength: %p% Надійність пароля: %p% Create Profile Створити профіль If the profile does not have a password, qTox can skip the login screen Якщо пароль профілю не задано, qTox може пропускати екран входу Load automatically "Завантажувати" is exact translation of English "Load", which is a synonym for "Download" in Ukrainian. So I think in this case more appropriate is "Входити автоматично", which means "Log in automatically". Завантажувати автоматично Load "Завантажити" is exact translation of English "Load", which is a synonym for "Download" in Ukrainian. So I think in this case more appropriate is "Увійти", which means "Log in". Завантажити New Profile I think that more appropriate is "Новий обліковий запис", but this string is too long. Новий профіль Load Profile I think that more appropriate is "Завантажити обліковий запис", but this string is too long. Завантажити профіль Couldn't create a new profile Неможливо створити новий профіль The username must not be empty. Ім'я користувача не може бути пустим. The password must be at least 6 characters long. Пароль повинен містити щонайменше 6 символів. The passwords you've entered are different. Please make sure to enter same password twice. Паролі, які Ви ввели, не співпадають. Переконайтесь, що ввели однаковий пароль двічі. A profile with this name already exists. Профіль з таким іменем уже існує. Couldn't load profile Неможливо завантажити профіль There is no selected profile. You may want to create one. Не обрано жодного профілю. Можливо, Ви б хотіли створити його. Couldn't load this profile Неможливо завантажити даний профіль This profile is already in use. Цей профіль вже використовується. Wrong password. Неправильний пароль. Import Імпорт Password protected profiles can't be automatically loaded. Профілі, захищені паролем, не можуть бути завантажені автоматично. Username input field Поле вводу для нікнейма Password input field, you can leave it empty (no password), or type at least 6 characters Поле вводу пароля. Його можна залишити порожнім (без пароля) або встановити щонайменше 6 символів Password confirmation field Підтвердіть пароль Create a new profile button Кнопка створення нового профілю Profile list Список профілів List of profiles Список профілів Password input Поле вводу пароля Load automatically checkbox Галочка для автоматичного завантаження Import profile Імпортувати профіль Load selected profile button Кнопка завантаження обраного профіля New profile creation page Сторінка створення нового профіля Loading existing profile page Сторінка завантаження існуючого профіля MainWindow Your name Ваше ім'я Your status Ваш статус ... Add friends Додати друзів Create a group chat Створити груповий чат View completed file transfers Переглянути завершені передачі файлів Change your settings Змінити параметри Close Закрити Open profile Відкрити профіль Open profile page when clicked По кліку відкрити сторінку профілю Status message input Поле ввода статуса Set your status message that will be shown to others Встановіть Ваш статус, який буде показано іншим Status Статус Set availability status Встановити доступність Contact search Пошук контакту Contact search input for known friends Поле пошуку по друзям Sorting and visibility Set friends sorting and visibility Open Add friends page Відкрити сторінку додавання друзів Groupchat Груповий чат Open groupchat management page Відкрити сторінку керування груповим чатом File transfers history Історія надісланих файлів Open File transfers history Відкрити історію надісланих файлів Settings Параметри Open Settings Відкрити налаштування Nexus View OS X Menu bar Вигляд Window OS X Menu bar Вікно Minimize OS X Menu bar Мінімізувати Bring All to Front OS X Menu bar На передній план Exit Fullscreen Вимкнути повноекранний режим Enter Fullscreen Увімкнути повноекранний режим NotificationEdgeWidget Unread message(s) %n непрочитане повідомлення %n непрочитаних повідомлень %n непрочитаних повідомлень PasswordEdit CAPS-LOCK ENABLED CAPS-LOCK УВІМКНЕНИЙ PrivacyForm Privacy Приватність Confirmation Підтвердження Do you want to permanently delete all chat history? Бажаєте назавжди видалити історію чату? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Ваші друзі зможуть побачити, коли ви друкуєте. Send typing notifications Відправляти сповіщення набирання повідомлення Keep chat history Зберігати історію переписки NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam - це частина Вашого Tox ID. Якщо Ви отримуєте багато небажаних запитів дружби, то Вам потрібно змінити NoSpam. Люди не зможуть додавати Вас за вашим старим ID, але ваші поточні друзі збережуться. NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam - це частина Вашого ID, що може бути змінена за бажанням. Якщо Вас спамлять запитами дружби, змініть NoSpam. Generate random NoSpam Згенерувати випадковий NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Зберігання історії переписки поки що в розробці. Можлива зміна формату зберігання, що може призвести до втрати даних. Privacy Приватність BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox Вітання з qTox ProfileForm Choose a profile picture Оберіть зображення для профілю Error Помилка Unable to open this file. Неможливо відкрити цей файл. Unable to read this image. Неможливо прочитати це зображення. The supplied image is too large. Please use another image. Зображення завелике. Виберіть інше, будь ласка. Rename "%1" renaming a profile Перейменувати «%1» Current profile: Поточний профіль: Remove Видалити Couldn't rename the profile to "%1" Неможливо перейменувати профіль в "%1" Location not writable Title of permissions popup Немає прав на запис You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Ви не маєте прав на запис за цим розташуванням. Оберіть інше місце призначення, або скасуйте передачу. Failed to copy file На вдалось скопіювати файл The file you chose could not be written to. Неможливо записати в файл, який ви обрали. Really delete profile? deletion confirmation title Ви впевнені? Nothing to remove Немає що видаляти Your profile does not have a password! Ваш обліковий запис не захищений паролем! Really delete password? deletion confirmation title Ви впевнені? Please enter a new password. Введіть новий пароль, будь ласка. Are you sure you want to delete this profile? deletion confirmation text Ви впевнені, що хочете видалити цей профіль? Save save qr image Збереження Save QrCode (*.png) save dialog filter Зберегти Qr код (*.png) Files could not be deleted! deletion failed title Не вдалось видалити файли! Register (processing) Реєстрація (обробка) Update (processing) Оновлення (обробка) Done! Готово! Account %1@%2 updated successfully Обліковий запис %1@%2 успішно оновлено Successfully added %1@%2 to the database. Save your password %1@%2 успішно додано до бази даних. Збережіть Ваш пароль Toxme error Помилка Toxme Register Реєстрація Update Оновити Change password button text Змінити пароль Set profile password button text Встановити пароль Current profile location: %1 Поточне місцезнаходження файлів профілю: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Помилка перейменування Profile already exists Профіль вже існує A profile named "%1" already exists. Профіль з іменем користувача "%1" вже існує. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Експорт профілю Tox save file (*.tox) save dialog filter Файл Tox (*.tox) The following files could not be deleted: deletion failed text part 1 Не вдалось видалити наступні файли: Please manually remove them. deletion failed text part 2 Будь ласка, видаліть їх самостійно. Are you sure you want to delete your password? deletion confirmation text Ви дійсно бажаєте видалити Ваш пароль? Images (%1) filetype filter Зображення (%1) ProfileImporter Import profile import dialog title Імпортувати профіль Tox save file (*.tox) import dialog filter Файл Tox (*.tox) Ignoring non-Tox file popup title Ігнорування не Tox файлу Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Увага: обраний Вами файл не являється файлом Tox; ігнорування. Profile already exists import confirm title Профіль вже існує A profile named "%1" already exists. Do you want to erase it? import confirm text Профіль із назвою «%1» вже існує. Бажаєте перезаписати його? File doesn't exist Файл не існує Profile doesn't exist Профіль не існує Profile imported Профіль імпортовано %1.tox was successfully imported %1.tox успішно імпортовано QApplication Ok Ок Cancel Скасувати Yes Так No Ні LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend Не можемо додати друга %1 is not a valid Toxme address. %1 - неправильна адреса ToxMe. You can't add yourself as a friend! When trying to add your own Tox ID as friend Ви не можете додати самого себе до друзів! QObject Tox URI to parse Tox URI для розбору Starts new instance and loads specified profile. Запускає новий екземпляр і завантажує вказаний профіль. profile профіль Default Типовий Blue Синій Olive Оливковий Red Червоний Violet Фіолетовий Incoming call... Вхідний дзвінок... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! That means "Hi, I'm %1! Please, add me to you contact list. It's difficult to translate "Tox me maybe" because in Ukrainian noun can't mean a verb (like "call" for noun and verb in English) Привіт, я %1! Додай мене в свій список контактів, будь ласка? Server doesn't support Toxme Сервер не підтримує Toxme You're making too many requests. Wait an hour and try again Ви відправляєте забагато запитів. Зачекайте годинку та спробуйте ще раз This name is already in use Це ім'я вже використовується This Tox ID is already registered under another name Цей Tox ID вже зареєстровано під іншим іменем Please don't use a space in your name Будь ласка, не використвуйте пробіли в Вашому імені Password incorrect Невірний пароль You can't use this name Ви не можете використати це ім'я Name not found Ім'я не знайдено Tox ID not sent Tox ID не надіслано That user does not exist Такого користувача не існує Error Помилка qTox couldn't open your chat logs, they will be disabled. qTox не може відкрити ваші чат логи, їх буде вимкнено. None No camera device set Відсутній Desktop Desktop as a camera input for screen sharing I think in this case more appropriate is "Екран" which means "Screen" Робочий стіл Problem with HTTPS connection Проблеми з з'єднанням HTTPS Internal ToxMe error Внутрішня помилка ToxMe Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status В мережі away contact status Відійшов busy contact status Зайнятий offline contact status Поза мережею blocked contact status RemoveFriendDialog Remove friend Вилучити з друзів Also remove chat history Також видалити історію переписки Remove Видалити Are you sure you want to remove %1 from your contacts list? Ви дійсно хочете видалити %1 з вашого списку контактів? Remove all chat history with the friend if set Коли встановлено, видаляти усю історію спілкування з другом ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Клікніть та перетягніть вказівник миші, щоб обрати область. Натисніть %1 щоб відобразити/приховати вікно qTox, або %2 для скасування. Space [Space] key on the keyboard Space Escape [Escape] key on the keyboard Escape Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Натисніть %1 щоб надіслати знімок вибраної області, %2 щоб відобразити/приховати вікно qTox, або %3 для скасування. Enter [Enter] key on the keyboard Enter SearchForm The text could not be found. Start SearchSettingsForm Form Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Встановіть свій пароль Confirm: Підтвердження: Password: Пароль: Password strength: %p% Надійність пароля: %p% The password is too short Пароль занадто короткий The password doesn't match. Паролі не співпадають. Confirm password Підтвердіть пароль Confirm password input Поле вводу підтвердження пароля Password input Поле введення пароля Password input field, minimum 6 characters long Поле введення пароля, мінімальна довжина 6 символів Settings Circle #%1 Коло №%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI Додати друга Do you want to add %1 as a friend? Бажаєте додати %1 як друга? User ID: ID користувача: Friend request message: Повідомлення запиту: Send Send a friend request Надіслати Cancel Don't send a friend request Скасувати UserInterfaceForm None Відсутній User Interface Інтерфейс користувача UserInterfaceSettings Chat Чат Base font: Базовий шрифт: px пікс Size: Розмір: New text styling preference may not load until qTox restarts. Нові налаштування стилю тексту можуть не застосуватись до перезавантаження qTox. Text Style format: Формат стилю тексту: Select text styling preference. Оберіть налаштування стилю тексту. Plaintext Звичайний текст Show formatting characters Відображати символи форматування Don't show formatting characters Не відображати символи форматування New message Нове повідомлення Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Відкривати вікно qTox при отриманні нового повідомлення, якщо вікно qTox ще не відкрите. Open window Відкрити вікно Contact list Список контактів If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Групові чати будуть зверху в списку контактів. Place groupchats at top of friend list Групові чати на початку Your contact list will be shown in compact mode. toolTip for compact layout setting Ваш перелік контактів відображатиметься в компактному режимі. Compact contact list Компактний список контактів Multiple windows mode Багатовіконний режим Open each chat in an individual window Відкривати кожен чат в окремому вікні Emoticons Емоції Use emoticons Використовувати смайлики Smiley Pack: Text on smiley pack label Набір смайлів: Emoticon size: Розмір смайлів: px px Theme Графічна тема Style: Стиль: Theme color: Графічна тема: Timestamp format: Формат часу: Date format: Формат дати: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Відтворювати звук Play sound while Busy Відтворювати звук якщо Зайнятий Notify via desktop notifications Hide message sender and contents Widget Online В мережі Online Button to set your status to 'Online' В мережі Away Button to set your status to 'Away' Відійшов Busy Button to set your status to 'Busy' Зайнятий toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Помилка запуску ядра tox із поточними параметрами проксі. qTox не працює; змініть параметри і перезапустіть. Add new circle... Додати нове коло... By Name За іменем By Activity За активністю All Всі контакти Offline Не в мережі Friends Друзі Groups Групи Search Contacts Пошук контактів Logout Tray action menu to logout user Вихід з облікового запису Exit Tray action menu to exit tox Вихід Filter... Фільтр... File Файл Edit Редагувати Contacts Контакти Change Status Змінити статус Edit Profile Редагувати профіль Log out Вихід з облікового запису Add Contact... Додати контакт... Next Conversation Наступна бесіда Previous Conversation Попередня бесіда Executable file popup title Виконуваний файл You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Ви намагаєтеся відкрити виконуваний файл. Виконувані файли можуть нанести шкоди вашому комп’ютеру. Ви все ще хочете відкрити цей файл? Couldn't request friendship Не вдалось надіслати запит на дружбу Status Статус toxcore failed to start, the application will terminate after you close this message. Неможливо запустити toxcore, додаток завершить роботу, коли Ви закриєте це повідомлення. Your name Ваше ім'я Message failed to send Не вдалось відправити повідомлення Groupchat #%1 Груповий чат #%1 Create new group... Створити нову групу... %n New Friend Request(s) %n новий запит дружби %n нових запитів дружби %n нових запитів дружби %n New Group Invite(s) %n нове запрошення в групові чати %n нових запрошення в групові чати %n нових запрошень в групові чати Show Tray action menu to show qTox window Відкрити Add friend title of the window Додати друга Group invites title of the window Групові запрошення File transfers title of the window Передачі файлів Settings title of the window Параметри My profile title of the window Мій профіль Failed to send file "%1" Не вдалось відправити файл "%1" File sent sent you a friend request. invites you to join a group. qTox/translations/zh_CN.ts000066400000000000000000003204551415623743500161260ustar00rootroot00000000000000 AVForm Audio/Video 音频/视频 Default resolution 默认分辨率 Disabled 已禁用 Select region 选择区域 Screen %1 屏幕 %1 Audio Settings 音频设置 Gain 增益 Playback device 播放设备 Use slider to set volume of your speakers. 使用滑块设置扬声器音量。 Capture device 音频捕获设备 Volume 音量 Video Settings 视频设置 Video device 视频设备 Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. 设置摄像头分辨率。 此值越高,您的好友看到的视频质量就越好。 请注意,更好的视频质量需要更好的网络连接来承载。 有时您的网络连接并不足以承载如此高的视频质量, 从而导致视频通话问题。 Resolution 分辨率 Rescan devices 重新扫描设备 Test Sound 测试音频 Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. 启用实验性的支持回声消除的音频后台,需要重启 qTox 使之生效。 Enable experimental audio backend 启用实验性的音频后台 Audio quality 音频质量 Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. 传输音频质量。如果您的带宽不够高或您希望降低网络流量,请降低该设置。 High (64 kbps) 高 (64 kbps) Medium (32 kbps) 中 (32kbps) Low (16 kbps) 低 (16 kbps) Very low (8 kbps) 极低 (8 kbps) Threshold 阈值 AboutForm About 关于 Original author: %1 原作者:%1 You are using qTox version %1. 正在使用 qTox 版本 %1。 Commit hash: %1 此版本哈希值: %1 toxcore version: %1 toxcore 版本:%1 Qt version: %1 Qt 版本:%1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` 所有已知问题的列表可以在 GitHub 上我们的 %1 里找到。如果您发现 qTox 的错误或者安全漏洞,请根据我们的维基文章 %2 提交报告。 Click here to report a bug. 单击这里提交错误报告。 See a full list of %1 at Github `%1` is replaced with translation of word `contributors` 在 Github 上查看 %1 完整列表 bug-tracker Replaces `%1` in the `A list of all known…` 漏洞追踪器 Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` 编写有用的错误报告 contributors Replaces `%1` in `See a full list of…` 贡献者 AboutFriendForm Dialog 对话框 username 用户名 status message 状态消息 Used aliases: 已用的别名: HISTORY OF ALIASES 曾用的别名 Automatically accept files from contact if set 如果设置则自动接收来自联系人的文件 Auto accept files 自动接收文件 Default directory to save files: 默认文件保存目录: Auto accept for this contact is disabled 已针对这个联系人禁用了自动接收 Auto accept call: 自动接听呼叫: Manual 手动 Audio 音频 Audio + Video 音视频 Automatically accept group chat invitations from this contact if set. 如果设置此项则自动接受来自该联系人的群聊邀请。 Auto accept group invites 自动接受群组邀请 Remove history (operation can not be undone!) 删除历史记录(操作无法撤消!) Notes 附注 Input field for notes about the contact 关于该联系人附注信息的输入框 You can save comment about this contact here. 您可以在这里记录关于该联系人的各种信息。 History removed 已删除历史记录 Choose an auto accept directory popup title 选择自动接受目录 <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> <html><head/><body><p>这是您朋友的公钥,使用它通过其它渠道来验证朋友的身份。您不能将该密钥发送给其他人以使他们可以添加该朋友为联系人。</p></body></html> Public key (not ToxID): 公钥(不是 ToxID): Confirmation 证实 Are you sure to remove %1 chat history? 您确定要移除与 %1 的聊天记录吗? Failed to remove chat history with %1! 移除与 %1 的聊天记录失败! AboutSettings Version 版本 License 许可证 Authors 作者 Known Issues 已知问题 Open update download link 打开下载更新的链接 Update available 有更新可用 qTox is up to date ✓ qTox 已经是最新的 ✓ AddFriendForm Add Friends 添加好友 Send friend request 发送好友请求 Add a friend 添加好友 Friend requests 好友请求 Accept 同意 Reject 拒绝 Couldn't add friend 无法添加好友 Invalid Tox ID format 无效的 Tox ID 格式 Tox ID, either 76 hexadecimal characters or name@example.com Tox ID,76 位十六进制字符或者类似于 name@example.com Type in Tox ID of your friend 输入好友的 Tox ID Friend request message 好友请求消息 Type message to send with the friend request or leave empty to send a default message 输入消息后随好友请求一起发送或者留空以发送默认消息 %1 Tox ID is invalid or does not exist Toxme error %1 这个 Tox ID 无效或不存在 You can't add yourself as a friend! When trying to add your own Tox ID as friend 您不能添加自己为好友! Open contact list 打开联系人列表 Couldn't open file 无法打开文件 Couldn't open the contact file Error message when trying to open a contact list file to import 无法打开该联系人列表文件 Invalid file 文件无效 We couldn't find any contacts to import in this file! 该文件中找不到可导入的联系人信息! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description 可以是 76 位十六进制字符或者类似 name@example.com 的形式 Message The message you send in friend requests 好友验证消息 Open Button to choose a file with a list of contacts to import 打开 Send friend requests 发送好友邀请 %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! 我是 %1!我们用 Tox 聊天吧? Import a list of contacts, one Tox ID per line 导入联系人列表,每行一个 Tox ID Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) 已经准备好导入 %n 个联系人,单击“发送”以确认 Import contacts 导入联系人 AdvancedForm Advanced 高级 Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. 除非您 %1 知道自己在做什么,否则请 %2 改变这里的任何设置。此处所做的更改可能导致 qTox 出现问题,甚至导致数据丢失,比如聊天历史记录丢失。 really 真的 not 不要 IMPORTANT NOTE 重要注意事项 Reset settings 重置设置 All settings will be reset to default. Are you sure? 所有的设置将被重置为默认值。您确定吗? Yes No Call active popup title 通话中 You can't disconnect while a call is active! popup text 您不能在通话时关闭连接! Save File 保存文件 Logs (*.log) 日志 (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox 保存设置到工作目录而不是通常的配置文件目录 Make Tox portable 使 Tox 可移动化 Reset to default settings 恢复默认设置 Portable 可移动 Connection Settings 连接设置 Enable IPv6 (recommended) Text on a checkbox to enable IPv6 启用 IPv6 (推荐) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip 禁用此选项允许,例如,在 Tor 上使用 Tox 。但是这会曾加 Tox 网络负载,因此仅在必要时取消选中。 Enable UDP (recommended) Text on checkbox to disable UDP 启用 UDP(推荐) Proxy type: 代理类型: Address: Text on proxy addr label 代理地址: Port: Text on proxy port label 端口: None SOCKS5 SOCKS5 HTTP HTTP Reconnect reconnect button 重新连接 Debug 调试 Export Debug Log 导出调试日志 Copy Debug Log 复制调试日志 Enable LAN discovery 启用局域网发现 ChatForm Send a file 发送文件 qTox wasn't able to open %1 qTox 无法打开 %1 %1 calling %1 呼叫 Calling %1 正在呼叫 %1 %1 is typing %1 正在输入 Copy 复制 Failed to open temporary file Temporary file for screenshot 无法打开临时文件 qTox wasn't able to save the screenshot qTox 无法保存截图 Call with %1 ended. %2 与 %1 的通话结束. %2 Call duration: 通话时长: Unable to open 无法打开 Bad idea 这不是个好主意 You're trying to send a sequential file, which is not going to work! 您正在尝试发送顺序文件,这不起作用! %1 is now %2 e.g. "Dubslow is now online" %1 现在 %2 Call with %1 ended unexpectedly. %2 与 %1 的通话意外中断。%2 Filename contained illegal characters 文件名包含非法字符 Illegal characters have been changed to _ so you can save the file on windows. 非法字符已经被更改为 _ 这样您就可以在 Windows 上保存该文件了。 ChatFormHeader Can't start audio call 无法开始语音通话 Start audio call 开始语音通话 End audio call 结束语音通话 Cancel audio call 取消语音通话 Accept audio call 接听语音通话 Can't start video call 无法开始视频通话 Start video call 开始视频通话 End video call 结束视频通话 Cancel video call 取消视频通话 Accept video call 接听视频通话 Sound can be disabled only during a call 只有在通话过程中才能执行禁用声音操作 Unmute call 取消通话静音 Mute call 静音通话 Microphone can be muted only during a call 只有在通话过程中才能执行麦克风静音操作 Unmute microphone 取消静音麦克风 Mute microphone 静音麦克风 ChatLog Copy 复制 Select all 全选 pending 等待 ChatTextEdit Type your message here... 在这里输入将要发送的消息... CircleWidget Rename circle Menu for renaming a circle 重命名圈子 Remove circle Menu for removing a circle 删除圈子 Open all in new window 全部在新窗口中打开 Core /me offers friendship, "%1" /me 提供了好友关系,“%1” Invalid Tox ID Error while sending friendship request 无效的 Tox ID You need to write a message with your request Error while sending friendship request 您需要随请求发送一条消息 Your message is too long! Error while sending friendship request 您的消息太长了! Friend is already added Error while sending friendship request 已添加为好友 Groupchat %1 群聊 %1 DesktopNotify New message 新消息 Incoming file transfer 传入文件传输 Friend request received 收到的好友请求 New group message 新群组消息 Group invite received 收到的群组邀请 FileTransferWidget Form 表单 10Mb 10Mb 0kb/s 0kb/s ETA:10:10 ETA:10:10 Filename 文件名 Waiting to send... file transfer widget 正在等待发送... Accept to receive this file file transfer widget 同意接收此文件 Location not writable Title of permissions popup 目录无法写入 You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup 您没有写入此目录的权限。请选择其它目录或取消。 Paused file transfer widget 已暂停 Resuming... file transfer widget 正在恢复... Open file 打开文件 Open file directory 打开文件目录 Pause transfer 暂停传输 Cancel transfer 取消传输 Resume transfer 恢复传输 Accept transfer 接受传输 Save a file Title of the file saving dialog 保存文件 Remote Paused file transfer widget 对方暂停 FilesForm Downloads 下载 Uploads 上传 Transferred Files "Headline" of the window 已传输的文件 FriendListWidget Today 今天 Yesterday 昨天 Last 7 days 近七天 This month 本月 Older than 6 Months 6 个月前 Never 永不 FriendRequestDialog Friend request Title of the window to aceept/deny a friend request 好友请求 Someone wants to make friends with you 有用户想要添加您为好友 User ID: 用户 ID : Friend request message: 好友请求消息: Accept Accept a friend request 同意 Reject Reject a friend request 拒绝 FriendWidget Invite to group Menu to invite a friend to a groupchat 邀请加入群组 Open chat in new window 在新窗口打开 Set alias... 添加备注名... Auto accept files from this friend context menu entry 自动接收此好友发送的文件 Show details 查看详情 Online 在线 Away 离开 Busy 忙碌 Offline 离线 Choose an auto accept directory popup title 选择默认接收目录 Remove friend Menu to remove the friend from our friendlist 删除好友 Remove chat from this window 从此窗口中移除聊天 To new group 到新建群组 Invite to group '%1' 邀请进入群聊 '%1' Move to circle... Menu to move a friend into a different circle 移动到圈子... To new circle 到新建圈子 Remove from circle '%1' 从圈子 '%1' 中移除 Move to circle "%1" 移动到圈子 "%1" New message 新消息 GeneralForm General 通用 Choose an auto accept directory popup title 选择默认接收目录 GeneralSettings General Settings 通用设置 The translation may not load until qTox restarts. 翻译将在qTox重启后生效。 Start in tray 启动到托盘 Close to tray 关闭到托盘 Minimize to tray 最小化到托盘 Start qTox on operating system startup (current profile). 开机自动启动 qTox(当前账户)。 Show contacts' status changes 显示联系人状态变化 Auto away after (0 to disable): 超过此时间自动离开(0 为禁用): Set to 0 to disable 设为 0 禁用 Language: 语言: Enable light tray icon. toolTip for light icon setting 使用亮色的托盘图标。 qTox will start minimized in tray. toolTip for Start in tray setting qTox 将在启动时最小化到系统托盘。 After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting 关闭按钮将隐藏 qTox 到状态栏, 而不关闭之。 After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting 最小化按钮将隐藏 qTox 到系统托盘, 而非系统任务栏。 Autostart 开机自动启动 Set where files will be saved. 设置文件保存位置。 Your status is changed to Away after set period of inactivity. 您的状态将在给定的无活动分钟数后被修改为离开。 Default directory to save files: 默认文件保存目录: Show system tray icon 显示系统托盘图标 Light icon 使用亮色图标 You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip 您可以通过右键点击他们来设置。 Autoaccept files 自动同意接收文件 Check for updates 检查更新 Spell checking 拼写检查 Max autoaccept file size (0 to disable): 自动接收的最大文件尺寸(0 为禁用): MB MB GenericChatForm Send message 发送消息 Smileys 表情 Send file(s) 发送文件 Send a screenshot 发送屏幕截图 Save chat log 保存聊天记录 Clear displayed messages 清屏 Cleared 已清除 Quote selected text 引用选中的文本 Copy link address 复制链接地址 Confirmation 确认 You are sure that you want to clear all displayed messages? 您确认要清除所有显示的消息吗? Search in text 在文本中搜索 Go to current date 转到当前日期 Load chat history... 加载聊天历史记录... Export to file 导出到文件 GenericNetCamView Tox video Tox 视频 Show Messages 显示消息 Hide Messages 隐藏消息 Full Screen 全屏 Toggle video preview 切换视频预览 Mute audio 静音 Mute microphone 静音麦克风 End video call 结束视频通话 Exit full screen 退出全屏 GroupChatForm %1 has set the title to %2 %1 已将标题设置为 %2 %1 has joined the group %1 加入了群组 %1 is now known as %2 %1 更名为 %2 %1 has left the group %1 离开了群 %n user(s) in chat Number of users in chat %n 位用户正在聊天 mute 静音 unmute 取消静音 GroupInviteForm Groups 群组 Create new group 创建聊天群组 Group invites 群组邀请 GroupInviteWidget Invited by %1 on %2 at %3. %1 于 %2 %3 邀请。 Join 加入 Decline 拒绝 GroupWidget Remove chat from this window 从这个窗口中删除会话 Set title... 设置名称... Quit group Menu to quit a groupchat 退出群组 Open chat in new window 在新窗口打开会话 %n user(s) in chat Number of users in chat %n 位用户正在聊天 New Message 新消息 Online 在线 IdentitySettings Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip 这组字符告诉其他 Tox 客户端如何与您联系。 将它共享给您的朋友以通信。 Your Tox ID (click to copy) 您的 Tox ID (点击复制) Your password 您的密码 Update 升级 Logout import profile button 登出 Remove password 删除密码 Change password 更改密码 Rename rename profile button 重命名 Export export profile button 导出 Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button 导出您的 Tox 配置文件到一个独立文件。 配置文件不包含您的历史记录。 Delete delete profile button 删除 Public Information 公开信息 This QR code contains your Tox ID. You may share this with your friends as well. 此二维码包含您的 Tox ID 。您可以将它分享给朋友们。 Save image 保存图像 Copy image 复制图像 Server 服务器 Hide my name from the public list 从公开列表中隐藏我的名字 Register 注册 Profile 配置文件 Rename profile. tooltip for renaming profile button 重命名账户。 Delete profile. delete profile button tooltip 删除帐户。 Go back to the login screen tooltip for logout button 返回到登录屏幕 Register on ToxMe 注册 ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. 您在 ToxMe 服务上的名称。 Optional. Something about you. Or your cat. Tooltip for the Biography text. 可选。关于您的事情。或者与您的猫咪有关的也行。 Optional. Something about you. Or your cat. Tooltip for the Biography field. 可选。关于您的事情。或者与您的猫咪有关的也行。 ToxMe service to register on. 要在 ToxMe 上注册的服务。 If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. 如果不选中,您在 ToxMe 上的条目将公开可见。 Remove your password and encryption from your profile. Tooltip for the `Remove password` button. 从您的配置文件移除您的密码和加密。 Name input 输入名字 Name visible to contacts 名字对联系人可见 Status message input 输入状态消息 Status message visible to contacts 状态消息对联系人可见 Your Tox ID 您的 Tox ID Save QR image as file 将二维码图片保存为文件 Copy QR image to clipboard 复制二维码图片到剪贴板 ToxMe username to be shown on ToxMe 在 ToxMe 上显示 ToxMe 用户名 Optional ToxMe biography to be shown on ToxMe 显示在 ToxMe 上的可选 ToxMe 个人资料 ToxMe service address ToxMe 服务地址 Visibility on the ToxMe service 在 ToxMe 服务上的可见性 Password 密码 Update ToxMe entry 更新 ToxMe 条目 Rename profile. 重命名账户。 Delete profile. 删除账户。 Export profile 导出账户 Remove password from profile 从个人档案中移除密码 Change profile password 更改个人资料密码 My name: 我的名字: My status: 我的状态: My username 我的用户名 My biography 我的简介 My profile 个人资料 LoadHistoryDialog Load History Dialog 加载历史记录对话框 Load history 加载历史记录 from to (about 100 messages are loaded) (加载了大约 100 条消息) Select Date Dialog 选择日期对话框 Select a date 选择日期 LoginScreen Username: 用户名: Password: 密码: Confirm: 确认: Password strength: %p% 密码长度:%p% Create Profile 创建配置 Load automatically 自动加载 Import 导入 Load 加载 New Profile 新建配置 Load Profile 加载配置 Couldn't create a new profile 无法创建新的配置 The username must not be empty. 用户名不能为空。 The password must be at least 6 characters long. 密码长度至少为6。 The passwords you've entered are different. Please make sure to enter same password twice. 您输入的密码不相同。 请确认再次输入的密码相同。 A profile with this name already exists. 该配置已经存在。 Password protected profiles can't be automatically loaded. 不能自动加载密码保护的配置。 Couldn't load profile 无法加载配置 There is no selected profile. You may want to create one. 没有选中的配置。 您也许想要创建一个配置。 Couldn't load this profile 无法加载这个配置 This profile is already in use. 该配置正在使用。 Wrong password. 密码错误。 If the profile does not have a password, qTox can skip the login screen 如果账户没有密码,qTox 可以跳过登录界面 Username input field 用户名输入框 Password input field, you can leave it empty (no password), or type at least 6 characters 密码输入框,您可以留空(无密码),或者输入至少 6 位字符 Password confirmation field 密码确认框 Create a new profile button 创建一个新的个人档案按钮 Profile list 帐户列表 List of profiles 所有个人档案的列表 Password input 密码输入 Load automatically checkbox 自动加载选项选择框 Import profile 导入用户档案 Load selected profile button 导入选择的个人档案按钮 New profile creation page 新档案创建页面 Loading existing profile page 正在加载现有的个人档案页面 MainWindow Your name 您的名字 Your status 您的状态 ... ... Add friends 添加好友 Create a group chat 创建聊天群组 View completed file transfers 查看已完成的文件传输 Change your settings 更改设置 Close 关闭 Open profile 打开个人档案 Open profile page when clicked 点击后打开个人档案页面 Status message input 输入状态消息 Set your status message that will be shown to others 设置您要展示给他人的个人状态消息 Status 状态 Set availability status 设置个人状态 Contact search 搜索联系人 Contact search input for known friends 搜索已知好友的联系人搜索输入框 Sorting and visibility 排序和可见性 Set friends sorting and visibility 设置好友排序和可见状态 Open Add friends page 打开添加好友页面 Groupchat 群聊 Open groupchat management page 打开群聊管理页面 File transfers history 文件传输历史记录 Open File transfers history 打开文件传输历史记录 Settings 设置 Open Settings 打开设置 Nexus View OS X Menu bar 查看 Window OS X Menu bar 日期 Minimize OS X Menu bar 最小化 Bring All to Front OS X Menu bar 移动全部到最上层 Exit Fullscreen 退出全屏 Enter Fullscreen 进入全屏模式 NotificationEdgeWidget Unread message(s) 未读消息 PasswordEdit CAPS-LOCK ENABLED 大写锁定已启用 PrivacyForm Privacy 隐私 Confirmation 确认 Do you want to permanently delete all chat history? 您想要永久删除所有的聊天记录吗? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting 您正在输入消息时好友会看到提示。 Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting 聊天记录的保存仍然在开发中。 保存格式未来可能更改,因此可能发生数据丢失。 Send typing notifications 发送正在输入通知 Keep chat history 保存聊天历史记录 NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam 是您的 Tox ID 一部分。 如果您被垃圾好友请求骚扰,您应该改变您的 NoSpam。 他人将无法使用旧 ID 添加您,但会保留您当前已添加的好友。 NoSpam NoSpam 混淆码 NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. NoSpam 混淆码是您的 Tox ID 中可以随意改变的一部分。 如果您收到骚扰的好友请求,那就改变您的 NoSpam 混淆码。 Generate random NoSpam 生成随机 NoSpam 混淆码 Privacy 隐私 BlackList 黑名单 Filter group message by group member's public key. Put public key here, one per line. 使用群成员的公钥过滤群聊信息。将公钥放在这里,每行一个。 Profile Failed to derive key from password, the profile won't use the new password. 从密码获取密钥失败,该帐户将不会使用新的密码。 Couldn't change password on the database, it might be corrupted or use the old password. 无法修改数据库中的密码。数据库可能损坏或使用了旧的密码。 Toxing on qTox 使用 qTox 与他人交流 ProfileForm Choose a profile picture 选择头像 Error 错误 The supplied image is too large. Please use another image. 提供的图片太大了。 请使用另一个图片。 You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup 您没有写入此目录的权限。请选择其它目录或取消保存。 Really delete profile? deletion confirmation title 确定要删除配置文件吗? Are you sure you want to delete this profile? deletion confirmation text 您确定要删除这个用户配置文件吗? Save save qr image 保存 Save QrCode (*.png) save dialog filter 保存二维码图片 (* .png) Nothing to remove 没有可以删除的东西 Your profile does not have a password! 您的账户没有设置密码 ! Really delete password? deletion confirmation title 确定要删除密码吗? Please enter a new password. 请输入新密码。 Register (processing) 注册(正在处理) Update (processing) 更新(正在处理) Done! 完成! Account %1@%2 updated successfully 帐户 %1@%2 已更新完成 Successfully added %1@%2 to the database. Save your password 已成功添加 %1@%2 到数据库。请保存您的密码 Toxme error Toxme 错误 Update 更新 Current profile: 当前账户: Remove 移除 Unable to open this file. 无法打开该文件 %s。 Unable to read this image. 无法读取此图像。 Rename "%1" renaming a profile 重命名“%1” Couldn't rename the profile to "%1" 无法重命名该账户为“%1” Location not writable Title of permissions popup 目录无法写入 Failed to copy file 复制文件失败 The file you chose could not be written to. 您选择的文件不可被写入。 Files could not be deleted! deletion failed title 无法删除文件! Register 注册 Change password button text 更改密码 Set profile password button text 设置账户密码 Current profile location: %1 当前账户的位置: %1 Couldn't change password 无法修改密码 This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). 这些字符告诉其它 Tox 客户端如何联系您。 将它分享给您的朋友们来开始通信。 该 ID 包含 NoSpam 代码 (蓝色字体部分),和校验码 (灰色字体部分)。 Empty path is unavaliable 空路径不可用 Failed to rename 重命名失败 Profile already exists 帐户已存在 A profile named "%1" already exists. 名为"%1"的帐户已存在。 Empty name 空名称 Empty name is unavaliable 空名称无效 Empty path 空路径 Couldn't change password on the database, it might be corrupted or use the old password. 无法修改数据库中的密码。数据库可能损坏或使用了旧的密码。 Export profile 导出用户档案 Tox save file (*.tox) save dialog filter Tox 存档文件 (*.tox) The following files could not be deleted: deletion failed text part 1 无法删除下列文件: Please manually remove them. deletion failed text part 2 请手动删除它们。 Are you sure you want to delete your password? deletion confirmation text 您确定要删除密码吗? Images (%1) filetype filter 图片 (%1) ProfileImporter Import profile import dialog title 导入用户配置文件 Warning: You have chosen a file that is not a Tox save file; ignoring. popup text 警告:您选择的文件不是 Tox 档案文件;忽略。 A profile named "%1" already exists. Do you want to erase it? import confirm text 一个叫“%1”的用户配置文件已经存在,您想要擦除它吗? Tox save file (*.tox) import dialog filter Tox 存档文件 (*.tox) Ignoring non-Tox file popup title 忽略非 Tox 的文件 Profile already exists import confirm title 账户已存在 File doesn't exist 文件不存在 Profile doesn't exist 账户不存在 Profile imported 已导入用户配置文件 %1.tox was successfully imported %1.tox 已成功导入 QApplication Ok Ok Cancel 取消 Yes No LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout LTR QMessageBox Couldn't add friend 无法添加好友 %1 is not a valid Toxme address. %1 不是一个有效的 Toxme 地址。 You can't add yourself as a friend! When trying to add your own Tox ID as friend 您不能加自己为好友! QObject Tox URI to parse Tox URI 解析 Starts new instance and loads specified profile. 开始一个新的实例并加载指定的配置文件。 profile 用户配置文件 Default 默认 Blue 蓝色 Olive 橄榄绿 Red 红色 Violet 紫色 Incoming call... 来电... Server doesn't support Toxme 服务器不支持 Toxme You're making too many requests. Wait an hour and try again 您发送了太多请求。等待一个小时后重试 This name is already in use 这个名字已被使用 This Tox ID is already registered under another name 此 Tox ID 已经被另一个名字注册 Please don't use a space in your name 请不要在您的名字中使用空格符号 Password incorrect 密码错误 You can't use this name 您不能使用此名称 Name not found 名字未找到 Tox ID not sent 没有发送 Tox ID That user does not exist 该用户不存在 %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! 我是 %1!我们使用 Tox 聊天吧? Error 错误 qTox couldn't open your chat logs, they will be disabled. qTox 无法打开您的聊天记录,将会禁用它们。 None No camera device set Desktop Desktop as a camera input for screen sharing 桌面 Problem with HTTPS connection HTTPS 连接问题 Internal ToxMe error ToxMe 内部错误 Reformatting text in progress.. 正在重新设置文本格式... Starts new instance and opens the login screen. 开启一个新进程并打开登录界面。 Dark 暗色 Dark blue 深蓝 Dark olive 橄榄绿 Dark red 暗红 Dark violet 紫罗兰 Failed to load profile automatically. 无法自动加载配置文件。 online contact status 在线 away contact status 离开 busy contact status 忙碌 offline contact status 离线 blocked contact status 已屏蔽 RemoveFriendDialog Remove friend 删除好友 Also remove chat history 同时删除聊天历史记录 Remove 删除 Are you sure you want to remove %1 from your contacts list? 您确定想要从联系人名单中删除 %1 吗? Remove all chat history with the friend if set 如果设置则删除与该好友的所有聊天历史记录 ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet 单击并拖动以选择一个区域。按 %1 以隐藏/显示 qTox 窗口,或 %2 来取消。 Space [Space] key on the keyboard 空格 Escape [Escape] key on the keyboard ESC Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected 按 %1 键发送所选区域的屏幕截图,%2 键以隐藏/显示 qTox 窗口,或 %3 键取消屏幕截图。 Enter [Enter] key on the keyboard 回车 SearchForm The text could not be found. 无法找到文本。 Start 开始 SearchSettingsForm Form 表单 Start search: 开始搜索: from the end 从结尾 from the beginning 从开头 after date 日期之后 before date 日期之前 00.00.0000 00.00.0000 Case sensitive 区分大小写 Whole words only 仅完整的词 Use regular expressions 使用正则表达式 SetPasswordDialog Set your password 设置密码 Confirm: 确认: Password: 密码: Password strength: %p% 密码长度:%p% The password is too short 密码长度太短 The password doesn't match. 密码不匹配。 Confirm password 确认密码 Confirm password input 确认密码输入框 Password input 密码输入 Password input field, minimum 6 characters long 密码输入框,最小长度为 6 个字符 Settings Circle #%1 圈子 # %1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI 添加好友 Do you want to add %1 as a friend? 您想加 %1 为好友吗? User ID: 用户 ID: Friend request message: 好友请求消息: Send Send a friend request 发送 Cancel Don't send a friend request 取消 UserInterfaceForm None User Interface 用户界面 UserInterfaceSettings Chat 聊天 Base font: 基础字体: px px Size: 尺寸: New text styling preference may not load until qTox restarts. qTox 重新启动之前可能不会加载新的文本样式设置。 Text Style format: 文本样式格式: Select text styling preference. 选择文本样式首选项。 Plaintext 纯文本 Show formatting characters 显示格式字符 Don't show formatting characters 不显示格式字符 New message 新消息 Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting 收到新消息并且没有打开的窗口时打开窗口。 Open window 打开窗口 Contact list 联系人列表 If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning 如果选中,群组将被置于好友列表的顶部,否则它们会被显示在在线好友列表的下方。 Place groupchats at top of friend list 将群聊置顶 Your contact list will be shown in compact mode. toolTip for compact layout setting 您的联系人列表将以紧缩格式显示。 Compact contact list 紧凑联系人列表 Multiple windows mode 多窗口模式 Open each chat in an individual window 在单独的窗口中打开每个会话 Emoticons 表情 Use emoticons 使用表情 Smiley Pack: Text on smiley pack label 表情包: Emoticon size: 表情大小: px px Theme 主题 Style: 风格: Theme color: 主题颜色: Timestamp format: 时间戳格式: Date format: 日期格式: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons 如果启用,每个没有头像集的联系人将基于他们的 Tox ID 生成头像而不使用默认的图片。需要重新启动才能生效。 Use identicons instead of empty avatars 使用 identicons 代替空的头像 Use colored nicknames in chats 在交谈中使用彩色昵称 Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting 当窗口没有激活时如果收到新消息就显示提醒。 Notify 通知 Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned 在群聊中仅通知提到了自己的新消息。 Group chats only notify when mentioned 群聊消息只有在被提到时才通知 Play sound 播放声音 Play sound while Busy 忙碌状态时仍播放提示音 Notify via desktop notifications 通过桌面提醒来通知 Hide message sender and contents 隐藏消息发件人和内容 Widget Online 在线 Online Button to set your status to 'Online' 在线 Away Button to set your status to 'Away' 离开 Busy Button to set your status to 'Busy' 忙碌 toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text 由于您的代理服务器设置,toxcore 启动失败。qTox 无法运行。请修改您的设置后重启 qTox。 Couldn't request friendship 无法添加为好友 You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text 您正在使用 qTox 打开一个可执行文件。可执行文件可能会破坏您的计算机。确定要打开此文件吗? Your name 您的名字 Offline 离线 Groups 群组 Logout Tray action menu to logout user 登出 Exit Tray action menu to exit tox 退出 Log out 登出 Message failed to send 消息发送失败 Status 状态 toxcore failed to start, the application will terminate after you close this message. toxcore 启动失败,应用程序将在您关闭此消息后终止。 Executable file popup title 可执行文件 Groupchat #%1 群聊 #%1 Create new group... 创建新群聊... Add new circle... 添加新圈子... %n New Friend Request(s) %n 个新好友请求 %n New Group Invite(s) %n 个新群聊邀请 By Name 按名称 By Activity 按活动日期 All 全部 Friends 好友 Search Contacts 搜索联系人 Filter... 筛选... File 文件名 Edit 编辑 Contacts 联系人 Change Status 更改状态 Edit Profile 修改资料 Add Contact... 添加联系人... Next Conversation 下一个对话 Previous Conversation 以前的对话 Show Tray action menu to show qTox window 显示 Add friend title of the window 添加好友 Group invites title of the window 群组邀请 File transfers title of the window 文件传输 Settings title of the window 设置 My profile title of the window 个人资料 Failed to send file "%1" 文件“%1”发送失败 File sent 文件已发送 sent you a friend request. 向您发送了好友请求。 invites you to join a group. 邀请您加入群组。 qTox/translations/zh_TW.ts000066400000000000000000003117101415623743500161520ustar00rootroot00000000000000 AVForm Audio/Video 音訊/視訊 Default resolution 預設解析度 Disabled 已停用 Select region 選擇區域 Screen %1 螢幕 %1 Audio Settings 音訊設定 Gain 增益 Playback device 播放裝置 Use slider to set volume of your speakers. Capture device 擷取裝置 Volume 音量 Video Settings 視訊設定 Video device 視訊裝置 Set resolution of your camera. The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. Resolution 解析度 Rescan devices 重新掃描裝置 Test Sound 測試音效 Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. Enable experimental audio backend Audio quality Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. High (64 kbps) Medium (32 kbps) Low (16 kbps) Very low (8 kbps) Threshold AboutForm About 關於 Original author: %1 原始作者:%1 You are using qTox version %1. 你正在使用 qTox 的 %1 版本。 Commit hash: %1 認可雜湊:%1 toxcore version: %1 toxcore 版本:%1 Qt version: %1 Qt 版本:%1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. `%1` is replaced by translation of `bug tracker` `%2` is replaced by translation of `Writing Useful Bug Reports` Click here to report a bug. 點選此處報告 Bug。 See a full list of %1 at Github `%1` is replaced with translation of word `contributors` bug-tracker Replaces `%1` in the `A list of all known…` Bug 追蹤器 Writing Useful Bug Reports Replaces `%2` in the `A list of all known…` contributors Replaces `%1` in `See a full list of…` 參與者 AboutFriendForm Dialog 對話方塊 username 使用者名稱 status message 狀態訊息 Used aliases: 使用的別名: HISTORY OF ALIASES 別名歷史 Automatically accept files from contact if set Auto accept files 自動接受檔案 Default directory to save files: 預設檔案儲存目錄: Auto accept for this contact is disabled Auto accept call: 自動接受通話: Manual 手動 Audio 音訊 Audio + Video 音訊與視訊 Automatically accept group chat invitations from this contact if set. Auto accept group invites Remove history (operation can not be undone!) Notes 附註 Input field for notes about the contact You can save comment about this contact here. History removed 已移除歷史記錄 Choose an auto accept directory popup title 選擇自動接受目錄 <html><head/><body><p>This is the public key of your friend, use it to verify their identity via another channel. You can not send this to other people so they can add this contact.</p></body></html> Public key (not ToxID): Confirmation Are you sure to remove %1 chat history? Failed to remove chat history with %1! AboutSettings Version 版本 License 授權 Authors 作者 Known Issues 已知問題 Open update download link Update available qTox is up to date ✓ AddFriendForm Add Friends 新增朋友 Invalid Tox ID format Send friend request 傳送朋友請求 Add a friend 新增朋友 Friend requests 朋友請求 Accept 接受 Reject 拒絕 Couldn't add friend 無法新增朋友 Tox ID, either 76 hexadecimal characters or name@example.com Type in Tox ID of your friend Friend request message 朋友請求訊息 Type message to send with the friend request or leave empty to send a default message %1 Tox ID is invalid or does not exist Toxme error You can't add yourself as a friend! When trying to add your own Tox ID as friend Open contact list Couldn't open file Couldn't open the contact file Error message when trying to open a contact list file to import Invalid file We couldn't find any contacts to import in this file! Tox ID Tox ID of the person you're sending a friend request to Tox ID either 76 hexadecimal characters or name@example.com Tox ID format description Message The message you send in friend requests 訊息 Open Button to choose a file with a list of contacts to import Send friend requests %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Import a list of contacts, one Tox ID per line Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) Import contacts AdvancedForm Advanced 進階 Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. really not IMPORTANT NOTE Reset settings 重設設定 All settings will be reset to default. Are you sure? Yes No Call active popup title You can't disconnect while a call is active! popup text Save File Logs (*.log) AdvancedSettings Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Make Tox portable Reset to default settings Portable Connection Settings Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Enable UDP (recommended) Text on checkbox to disable UDP Proxy type: Address: Text on proxy addr label Port: Text on proxy port label None SOCKS5 HTTP Reconnect reconnect button Debug 偵錯 Export Debug Log 匯出偵錯記錄檔 Copy Debug Log 複製偵錯記錄檔 Enable LAN discovery ChatForm Send a file 傳送檔案 qTox wasn't able to open %1 Unable to open Bad idea %1 calling Calling %1 Failed to open temporary file Temporary file for screenshot qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch Call with %1 ended. %2 Call duration: %1 is typing Copy You're trying to send a sequential file, which is not going to work! %1 is now %2 e.g. "Dubslow is now online" Call with %1 ended unexpectedly. %2 Filename contained illegal characters Illegal characters have been changed to _ so you can save the file on windows. ChatFormHeader Can't start audio call Start audio call 開始音訊通話 End audio call Cancel audio call Accept audio call Can't start video call Start video call 開始視訊通話 End video call 結束視訊通話 Cancel video call Accept video call Sound can be disabled only during a call Unmute call Mute call Microphone can be muted only during a call Unmute microphone Mute microphone ChatLog Copy Select all pending ChatTextEdit Type your message here... CircleWidget Rename circle Menu for renaming a circle Remove circle Menu for removing a circle Open all in new window Core /me offers friendship, "%1" Invalid Tox ID Error while sending friendship request 無效的 Tox ID You need to write a message with your request Error while sending friendship request Your message is too long! Error while sending friendship request Friend is already added Error while sending friendship request Groupchat %1 DesktopNotify New message 新訊息 Incoming file transfer Friend request received New group message Group invite received FileTransferWidget Form Ausgelassen 表單 10Mb Ausgelassen 10Mb 0kb/s Ausgelassen 0kb/s ETA:10:10 Ausgelassen ETA:10:10 Filename Ausgelassen 檔案名稱 Waiting to send... file transfer widget 等待傳送… Accept to receive this file file transfer widget 接受接收此檔案 Location not writable Title of permissions popup 位置不可寫入 You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Resuming... file transfer widget 恢復中… Cancel transfer 取消傳輸 Pause transfer 暫停傳輸 Paused file transfer widget 已暫停 Open file 開啟檔案 Open file directory 開啟檔案目錄 Resume transfer 恢復傳輸 Accept transfer 接受傳輸 Save a file Title of the file saving dialog 儲存檔案 Remote Paused file transfer widget FilesForm Transferred Files "Headline" of the window 傳輸的檔案 Downloads 下載 Uploads 上傳 FriendListWidget Today 今日 Yesterday 昨日 Last 7 days This month Older than 6 Months Never FriendRequestDialog Friend request Title of the window to aceept/deny a friend request Someone wants to make friends with you User ID: Friend request message: Accept Accept a friend request 接受 Reject Reject a friend request 拒絕 FriendWidget Invite to group Menu to invite a friend to a groupchat Move to circle... Menu to move a friend into a different circle To new circle Remove from circle '%1' Move to circle "%1" Open chat in new window Remove chat from this window To new group Invite to group '%1' Set alias... Auto accept files from this friend context menu entry Remove friend Menu to remove the friend from our friendlist Show details Choose an auto accept directory popup title 選擇自動接受目錄 New message 新訊息 Online 線上 Away 離開 Busy 忙碌 Offline Ausgelassen 離線 GeneralForm General 一般 Choose an auto accept directory popup title 選擇自動接受目錄 GeneralSettings General Settings 一般設定 The translation may not load until qTox restarts. Language: 語言: Show system tray icon 顯示系統匣圖示 Enable light tray icon. toolTip for light icon setting Light icon qTox will start minimized in tray. toolTip for Start in tray setting Start in tray After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting Close to tray After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting Minimize to tray Autostart Set where files will be saved. You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Autoaccept files Set to 0 to disable Your status is changed to Away after set period of inactivity. Auto away after (0 to disable): Show contacts' status changes Start qTox on operating system startup (current profile). Default directory to save files: 預設檔案儲存目錄: Check for updates Spell checking Max autoaccept file size (0 to disable): MB GenericChatForm Send message Smileys Send file(s) Send a screenshot Save chat log Clear displayed messages 清除顯示的訊息 Cleared 已清除 Quote selected text Copy link address 複製連結位址 Confirmation You are sure that you want to clear all displayed messages? Search in text Go to current date Load chat history... Export to file GenericNetCamView Tox video Tox 視訊 Show Messages 顯示訊息 Hide Messages 隱藏訊息 Full Screen Toggle video preview Mute audio Mute microphone End video call 結束視訊通話 Exit full screen GroupChatForm %1 has set the title to %2 %1 has joined the group %1 is now known as %2 %1 has left the group %n user(s) in chat Number of users in chat mute unmute GroupInviteForm Groups 群組 Create new group Group invites 群組邀請 GroupInviteWidget Invited by %1 on %2 at %3. Join Decline GroupWidget Set title... Open chat in new window Remove chat from this window Quit group Menu to quit a groupchat %n user(s) in chat Number of users in chat New Message Online 線上 IdentitySettings Public Information Tox ID Tox ID This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip Your Tox ID (click to copy) Profile Rename profile. tooltip for renaming profile button Go back to the login screen tooltip for logout button Logout import profile button 登出 Remove password Change password This QR code contains your Tox ID. You may share this with your friends as well. Save image Copy image Rename rename profile button Delete profile. delete profile button tooltip Allows you to export your Tox profile to a file. Profile does not contain your history. tooltip for profile exporting button Export export profile button Delete delete profile button Server Hide my name from the public list Register Your password Update Register on ToxMe Name for the ToxMe service. Tooltip for the `Username` ToxMe field. Optional. Something about you. Or your cat. Tooltip for the Biography text. Optional. Something about you. Or your cat. Tooltip for the Biography field. ToxMe service to register on. If not set, ToxMe entries are publicly visible. Tooltip for the `Hide my name from public list` ToxMe checkbox. Remove your password and encryption from your profile. Tooltip for the `Remove password` button. Name input Name visible to contacts Status message input Status message visible to contacts Your Tox ID Save QR image as file Copy QR image to clipboard ToxMe username to be shown on ToxMe Optional ToxMe biography to be shown on ToxMe ToxMe service address Visibility on the ToxMe service Password Update ToxMe entry Rename profile. Delete profile. Export profile Remove password from profile Change profile password My name: My status: My username My biography My profile 個人檔案 LoadHistoryDialog Load History Dialog Load history from to (about 100 messages are loaded) Select Date Dialog Select a date LoginScreen Username: Password: Confirm: Password strength: %p% Create Profile If the profile does not have a password, qTox can skip the login screen Load automatically Load Load Profile New Profile Couldn't create a new profile The username must not be empty. The password must be at least 6 characters long. The passwords you've entered are different. Please make sure to enter same password twice. A profile with this name already exists. Password protected profiles can't be automatically loaded. Couldn't load profile There is no selected profile. You may want to create one. Couldn't load this profile This profile is already in use. Wrong password. Import Username input field Password input field, you can leave it empty (no password), or type at least 6 characters Password confirmation field Create a new profile button Profile list List of profiles Password input Load automatically checkbox Import profile Load selected profile button New profile creation page Loading existing profile page MainWindow Your name 你的名字 Your status 你的狀態 ... Ausgelassen Add friends Create a group chat View completed file transfers Change your settings Close Open profile Open profile page when clicked Status message input Set your status message that will be shown to others Status 狀態 Set availability status Contact search Contact search input for known friends Sorting and visibility Set friends sorting and visibility Open Add friends page Groupchat Open groupchat management page File transfers history Open File transfers history Settings 設定 Open Settings Nexus View OS X Menu bar Window OS X Menu bar Minimize OS X Menu bar Bring All to Front OS X Menu bar Exit Fullscreen Enter Fullscreen NotificationEdgeWidget Unread message(s) PasswordEdit CAPS-LOCK ENABLED PrivacyForm Privacy Confirmation Do you want to permanently delete all chat history? PrivacySettings Your friends will be able to see when you are typing. tooltip for typing notifications setting Send typing notifications Keep chat history NoSpam is part of your Tox ID. If you are being spammed with friend requests, you should change your NoSpam. People will be unable to add you with your old ID, but you will keep your current friends. toolTip for nospam NoSpam NoSpam is a part of your ID that can be changed at will. If you are getting spammed with friend requests, change the NoSpam. Generate random NoSpam Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting Privacy BlackList Filter group message by group member's public key. Put public key here, one per line. Profile Failed to derive key from password, the profile won't use the new password. Couldn't change password on the database, it might be corrupted or use the old password. Toxing on qTox ProfileForm Choose a profile picture Error 錯誤 Rename "%1" renaming a profile 重新命名 "%1" Unable to open this file. 無法開啟此檔案。 Current profile: Remove 移除 Unable to read this image. The supplied image is too large. Please use another image. Couldn't rename the profile to "%1" Location not writable Title of permissions popup 位置不可寫入 You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Failed to copy file The file you chose could not be written to. Really delete profile? deletion confirmation title Nothing to remove Your profile does not have a password! Really delete password? deletion confirmation title Please enter a new password. Are you sure you want to delete this profile? deletion confirmation text Save save qr image Save QrCode (*.png) save dialog filter Files could not be deleted! deletion failed title Register (processing) Update (processing) Done! Account %1@%2 updated successfully Successfully added %1@%2 to the database. Save your password Toxme error Register Update Change password button text Set profile password button text Current profile location: %1 Couldn't change password This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable Failed to rename Profile already exists A profile named "%1" already exists. Empty name Empty name is unavaliable Empty path Couldn't change password on the database, it might be corrupted or use the old password. Export profile Tox save file (*.tox) save dialog filter The following files could not be deleted: deletion failed text part 1 Please manually remove them. deletion failed text part 2 Are you sure you want to delete your password? deletion confirmation text Images (%1) filetype filter ProfileImporter Import profile import dialog title Tox save file (*.tox) import dialog filter Ignoring non-Tox file popup title Warning: You have chosen a file that is not a Tox save file; ignoring. popup text Profile already exists import confirm title A profile named "%1" already exists. Do you want to erase it? import confirm text File doesn't exist Profile doesn't exist Profile imported %1.tox was successfully imported QApplication Ok Cancel Yes No LTR Translate this string to the string 'RTL' in right-to-left languages (for example Hebrew and Arabic) to get proper widget layout QMessageBox Couldn't add friend 無法新增朋友 %1 is not a valid Toxme address. You can't add yourself as a friend! When trying to add your own Tox ID as friend QObject Tox URI to parse Starts new instance and loads specified profile. profile Default Blue Olive Red Violet Incoming call... %1 here! Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! None No camera device set Desktop Desktop as a camera input for screen sharing Server doesn't support Toxme You're making too many requests. Wait an hour and try again This name is already in use This Tox ID is already registered under another name Please don't use a space in your name Password incorrect You can't use this name Name not found Tox ID not sent That user does not exist Error 錯誤 qTox couldn't open your chat logs, they will be disabled. Problem with HTTPS connection Internal ToxMe error Reformatting text in progress.. Starts new instance and opens the login screen. Dark Dark blue Dark olive Dark red Dark violet Failed to load profile automatically. online contact status away contact status busy contact status offline contact status blocked contact status RemoveFriendDialog Remove friend Also remove chat history Remove 移除 Are you sure you want to remove %1 from your contacts list? Remove all chat history with the friend if set ScreenshotGrabber Click and drag to select a region. Press %1 to hide/show qTox window, or %2 to cancel. Help text shown when no region has been selected yet Space [Space] key on the keyboard Escape [Escape] key on the keyboard Press %1 to send a screenshot of the selection, %2 to hide/show qTox window, or %3 to cancel. Help text shown when a region has been selected Enter [Enter] key on the keyboard SearchForm The text could not be found. Start SearchSettingsForm Form 表單 Start search: from the end from the beginning after date before date 00.00.0000 Case sensitive Whole words only Use regular expressions SetPasswordDialog Set your password Confirm: Password: Password strength: %p% The password is too short The password doesn't match. Confirm password Confirm password input Password input Password input field, minimum 6 characters long Settings Circle #%1 ToxURIDialog Add a friend Title of the window to add a friend through Tox URI 新增朋友 Do you want to add %1 as a friend? User ID: Friend request message: Send Send a friend request Cancel Don't send a friend request UserInterfaceForm None User Interface UserInterfaceSettings Chat Base font: px Size: New text styling preference may not load until qTox restarts. Text Style format: Select text styling preference. Plaintext Show formatting characters Don't show formatting characters New message 新訊息 Open qTox's window when you receive a new message and no window is open yet. tooltip for Show window setting Open window Contact list If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. toolTip for groupchat positioning Place groupchats at top of friend list Your contact list will be shown in compact mode. toolTip for compact layout setting Compact contact list Multiple windows mode Open each chat in an individual window Emoticons Use emoticons Smiley Pack: Text on smiley pack label Emoticon size: px Theme Style: Theme color: Timestamp format: Date format: 日期格式: If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons Use identicons instead of empty avatars Use colored nicknames in chats Show a notification when you receive a new message and the window is not selected. tooltip for Notify setting Notify Onlys notify about new messages in groupchats when mentioned. toolTip for Group chats only notify when mentioned Group chats only notify when mentioned Play sound Play sound while Busy Notify via desktop notifications Hide message sender and contents Widget Online Button to set your status to 'Online' 線上 Away Button to set your status to 'Away' 離開 Busy Button to set your status to 'Busy' 忙碌 toxcore failed to start, the application will terminate after you close this message. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text File 檔案 Edit Profile 編輯個人檔案 Change Status 變更狀態 Log out 登出 Edit 編輯 Logout Tray action menu to logout user 登出 Exit Tray action menu to exit tox 結束 Filter... 篩選… Contacts 聯絡人 Add Contact... 新增聯絡人… Next Conversation 下一個交談 Previous Conversation 上一個交談 Executable file popup title 可執行檔 You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Couldn't request friendship Status 狀態 Your name 你的名字 Message failed to send 訊息傳送失敗 Create new group... 建立新群組… Add new circle... 新增新圈子… %n New Friend Request(s) %n 筆新朋友請求 %n New Group Invite(s) %n 個新群組邀請 By Name By Activity All 所有 Online 線上 Offline Ausgelassen 離線 Friends 朋友 Groups 群組 Search Contacts 搜尋聯絡人 Groupchat #%1 群組聊天 #%1 Show Tray action menu to show qTox window 顯示 Add friend title of the window 新增朋友 Group invites title of the window 群組邀請 File transfers title of the window 檔案傳輸 Settings title of the window 設定 My profile title of the window 個人檔案 Failed to send file "%1" File sent sent you a friend request. invites you to join a group. qTox/updater/000077500000000000000000000000001415623743500134675ustar00rootroot00000000000000qTox/updater/main.cpp000066400000000000000000000057071415623743500151300ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "settings.h" #include "widget.h" #include #include #include #include #include #include #include #include static std::unique_ptr logFileStream{nullptr}; static std::unique_ptr logFileFile{nullptr}; static QMutex mutex; void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QString& msg) { // Silence qWarning spam due to bug in QTextBrowser (trying to open a file for base64 images) if (ctxt.function == QString("virtual bool QFSFileEngine::open(QIODevice::OpenMode)") && msg == QString("QFSFileEngine::open: No file name specified")) return; QString LogMsg = QString("[%1] %2:%3 : ") .arg(QTime::currentTime().toString("HH:mm:ss.zzz")) .arg(ctxt.file) .arg(ctxt.line); switch (type) { case QtDebugMsg: LogMsg += "Debug"; break; case QtWarningMsg: LogMsg += "Warning"; break; case QtCriticalMsg: LogMsg += "Critical"; break; case QtFatalMsg: LogMsg += "Fatal"; break; default: break; } LogMsg += ": " + msg + "\n"; QTextStream out(stderr, QIODevice::WriteOnly); out << LogMsg; if (!logFileStream) return; QMutexLocker locker(&mutex); *logFileStream << LogMsg; logFileStream->flush(); } int main(int argc, char* argv[]) { qInstallMessageHandler(logMessageHandler); QApplication a(argc, argv); Settings s; logFileStream.reset(new QTextStream); logFileFile.reset(new QFile(s.getSettingsDirPath() + "qtox.log")); if (logFileFile->open(QIODevice::Append)) { logFileStream->setDevice(logFileFile.get()); *logFileStream << QDateTime::currentDateTime().toString( "\nyyyy-MM-dd HH:mm:ss' Updater file logger starting\n'"); } else { qWarning() << "Couldn't open log file!\n"; logFileStream.release(); } long unsigned int bufsize = 100; char buf[100]; GetUserNameA(buf, &bufsize); qDebug() << "Updater running as user" << buf; Widget w(s); w.show(); return a.exec(); } qTox/updater/res.qrc000066400000000000000000000001431415623743500147650ustar00rootroot00000000000000 res/qtox-256x256.png qTox/updater/res/000077500000000000000000000000001415623743500142605ustar00rootroot00000000000000qTox/updater/res/qtox-256x256.png000066400000000000000000000127761415623743500167350ustar00rootroot00000000000000PNG  IHDR\rfsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<{IDATx TVeiu3if͙Y딂W.y QA"@-`94E-SRFMe +K YĨyE?`{{g\^Z|{}y/z4z1 pIP$/,X*``u`Tc7 ֜%xV^kj``:V+ XR+څAO 6~|:v@=/X.lFV#5Ep#8io5`؂J`^j;qWoWmY=c{NUN .`*V#|Gkcbm RZK]c` : =!&c4rF!z/k !7mVs1r$n\E!ս#qD{zoԝrz-wЇBÙI/"#1QO4= - |MG tq\N¢G0,ly> ͋Zx3ϛ?a˖-O_ߓ&߈{H?L$QKK*y%=zl˘yܹJJJϜ1sjxM[[ѫͱ7rߦU]dʈfλ)*!0 8,OMgHT[h1cژʼm>QPYeC* ԫT~ _8޾']W⡞g***Q!#B&<7e@ л@N8≾ KzG=Sr=j`+qqɚ5Yf 'zWF5o&2323~'y}|i>KM+5׿~露xqp)>x 9s&Y%NLҨW14ѬÓ/Z,ZhG# 5Fu Uj?>\QPлIZUf>T@+ ~IEEEkQoi#%Nr7*)=n%:/=S^^i7H:wܯRqp+,Գ)'O<Îe_u8m_~'!Bd؅>GOz~ϝ3ws$x6ՈrMIXHXH@mxUF|g_xa /(5:MNoٳgIuPѣ ?#7|P' u:xFRSR_6yPUz5|Modt@GzT@^{mDzTT=k{t}N3= `'F^3}#ڄe= `͗LuvzT.sY~fƏ?K'@U4Պ~j\ <*]vM#5y4%= wh^3rlt<*`,Q: = [c),,q6=^4^vVd^3m}/EG}tcx X VJG_Fkf+&"`iyO.aiXZ]:wMkfqu=]0y4aSᩯ!`ix?D'!ic,/^?pP9.:."VVȑ#: Ыgw 5z6(/$ KZs a3/ @J΂j6ڜAnƧ<_=@ @24~w yӀqGJGKv l+wkcl(NI@̠fO@8c cP@ ۞leticx6Ĥ3gl޴9 eeeDU'zXRJA g(fw+e o3VVӉDrlNgS @dIOӉԴyBccw "r\.ٽkȈX6 p>j3]:w-V2JӔ?x"Q)@()&,6|;4M4LI4%LȦ`a&`LJ4eph^3 $ДfD*' %;u X۞ܽk  'a}=zl#գVXͿ8RW'-5-@g˳'ՑB ˳t1:r\`yZx_<:r\`yT:W%E$@   @@ @ @ `1`C6A@)q? #ՑB ԫѣC5xq?-_C}l3}8E vs7k^bw /ǁ~pf ݞ e9a&`u{0(Wa}& #?|s^56R0Mh&,`LȠ`i&$Q0Mn&xR0Mij8$phwԝXD4aI4ag 2`7@Ό[( @nZ8$"ԋ}Χ*~^\ԋȦ^DQ\e@ Epʥn`0KlaU-ȧ5"Fne$U(O ǐɳ9+rz1A2IENDB`qTox/updater/serialize.cpp000066400000000000000000000137371415623743500161750ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "serialize.h" QByteArray doubleToData(double num) { union { char tab[8]; double n; } castUnion; // char n[8]; //*((double*) n) = num; castUnion.n = num; return QByteArray(castUnion.tab, 8); } QByteArray floatToData(float num) { union { char tab[4]; float n; } castUnion; castUnion.n = num; return QByteArray(castUnion.tab, 4); } float dataToFloat(QByteArray data) { union { char tab[4]; float n; } castUnion; castUnion.tab[0] = data.data()[0]; castUnion.tab[1] = data.data()[1]; castUnion.tab[2] = data.data()[2]; castUnion.tab[3] = data.data()[3]; return castUnion.n; } // Converts a string into PNet string data QByteArray stringToData(QString str) { QByteArray data(4, 0); // Write the size in a Uint of variable lenght (8-32 bits) int i = 0; uint num1 = (uint)str.toUtf8().size(); while (num1 >= 0x80) { data[i] = (unsigned char)(num1 | 0x80); i++; num1 = num1 >> 7; } data[i] = num1; data.resize(i + 1); data += str.toUtf8(); return data; } QString dataToString(QByteArray data) { // Variable UInt32 unsigned char num3; int num = 0; int num2 = 0; int i = 0; do { num3 = data[i]; i++; num |= (num3 & 0x7f) << num2; num2 += 7; } while ((num3 & 0x80) != 0); unsigned int strlen = (uint)num; if (!strlen) return QString(); data = data.right(data.size() - i); // Remove the strlen data.truncate(strlen); return QString(data); } float dataToRangedSingle(float min, float max, int numberOfBits, QByteArray data) { uint endvalue = 0; uint value = 0; if (numberOfBits <= 8) { endvalue = (uchar)data[0]; goto done; } value = (uchar)data[0]; numberOfBits -= 8; if (numberOfBits <= 8) { endvalue = (value | ((uint)((uchar)data[1]) << 8)); goto done; } value |= (uint)(((uchar)data[1]) << 8); numberOfBits -= 8; if (numberOfBits <= 8) { uint num2 = (uint)(((uchar)data[2]) << 0x10); endvalue = (value | num2); goto done; } value |= (uint)(((uchar)data[2]) << 0x10); numberOfBits -= 8; endvalue = (value | ((uint)(((uchar)data[3]) << 0x18))); goto done; done: float num = max - min; int num2 = (((int)1) << numberOfBits) - 1; float num3 = endvalue; float num4 = num3 / ((float)num2); return (min + (num4 * num)); } QByteArray rangedSingleToData(float value, float min, float max, int numberOfBits) { QByteArray data; float num = max - min; float num2 = (value - min) / num; int num3 = (((int)1) << numberOfBits) - 1; uint source = num3 * num2; if (numberOfBits <= 8) { data += (unsigned char)source; return data; } data += (unsigned char)source; numberOfBits -= 8; if (numberOfBits <= 8) { data += (unsigned char)(source >> 8); return data; } data += (unsigned char)(source >> 8); numberOfBits -= 8; if (numberOfBits <= 8) { data += (unsigned char)(source >> 16); return data; } data += (unsigned char)(source >> 16); data += (unsigned char)(source >> 24); return data; } uint8_t dataToUint8(QByteArray data) { return (uint8_t)data[0]; } uint16_t dataToUint16(QByteArray data) { return ((uint16_t)(uint8_t)data[0]) + (((uint16_t)(uint8_t)data[1]) << 8); } uint32_t dataToUint32(QByteArray data) { return ((uint32_t)(uint8_t)data[0]) + (((uint32_t)(uint8_t)data[1]) << 8) + (((uint32_t)(uint8_t)data[2]) << 16) + (((uint32_t)(uint8_t)data[3]) << 24); } uint64_t dataToUint64(QByteArray data) { return ((uint64_t)(uint8_t)data[0]) + (((uint64_t)(uint8_t)data[1]) << 8) + (((uint64_t)(uint8_t)data[2]) << 16) + (((uint64_t)(uint8_t)data[3]) << 24) + (((uint64_t)(uint8_t)data[4]) << 32) + (((uint64_t)(uint8_t)data[5]) << 40) + (((uint64_t)(uint8_t)data[6]) << 48) + (((uint64_t)(uint8_t)data[7]) << 56); } unsigned getVUint32Size(QByteArray data) { unsigned lensize = 0; { unsigned char num3; do { num3 = data[lensize]; lensize++; } while ((num3 & 0x80) != 0); } return lensize; } QByteArray uint8ToData(uint8_t num) { QByteArray data(1, 0); data[0] = (uint8_t)num; return data; } QByteArray uint16ToData(uint16_t num) { QByteArray data(2, 0); data[0] = (uint8_t)(num & 0xFF); data[1] = (uint8_t)((num >> 8) & 0xFF); return data; } QByteArray uint32ToData(uint32_t num) { QByteArray data(4, 0); data[0] = (uint8_t)(num & 0xFF); data[1] = (uint8_t)((num >> 8) & 0xFF); data[2] = (uint8_t)((num >> 16) & 0xFF); data[3] = (uint8_t)((num >> 24) & 0xFF); return data; } QByteArray uint64ToData(uint64_t num) { QByteArray data(8, 0); data[0] = (uint8_t)(num & 0xFF); data[1] = (uint8_t)((num >> 8) & 0xFF); data[2] = (uint8_t)((num >> 16) & 0xFF); data[3] = (uint8_t)((num >> 24) & 0xFF); data[4] = (uint8_t)((num >> 32) & 0xFF); data[5] = (uint8_t)((num >> 40) & 0xFF); data[6] = (uint8_t)((num >> 48) & 0xFF); data[7] = (uint8_t)((num >> 56) & 0xFF); return data; } qTox/updater/serialize.h000066400000000000000000000033111415623743500156250ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SERIALIZE_H #define SERIALIZE_H #include #include #include /// Most of those functions are unsafe unless otherwise specified /// Do not use them on untrusted data (e.g. check a signature first) QByteArray doubleToData(double num); QByteArray floatToData(float num); float dataToFloat(QByteArray data); QByteArray stringToData(QString str); QString dataToString(QByteArray data); float dataToRangedSingle(float min, float max, int numberOfBits, QByteArray data); QByteArray rangedSingleToData(float value, float min, float max, int numberOfBits); uint8_t dataToUint8(QByteArray data); uint16_t dataToUint16(QByteArray data); uint32_t dataToUint32(QByteArray data); uint64_t dataToUint64(QByteArray data); unsigned getVUint32Size(QByteArray data); QByteArray uint8ToData(uint8_t num); QByteArray uint16ToData(uint16_t num); QByteArray uint32ToData(uint32_t num); QByteArray uint64ToData(uint64_t num); #endif // SERIALIZE_H qTox/updater/settings.cpp000066400000000000000000000124721415623743500160410ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "settings.h" #include #include #include #include #include #ifdef Q_OS_WIN #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0600 // Vista for SHGetKnownFolderPath #include #include #include #include #endif Settings::Settings() { portable = false; QFile portableSettings(SETTINGS_FILE); if (portableSettings.exists()) { QSettings ps(SETTINGS_FILE, QSettings::IniFormat); ps.beginGroup("General"); portable = ps.value("makeToxPortable", false).toBool(); } qDebug() << "Portable: " << portable; #ifdef Q_OS_WIN // Get a primary unelevated token of the actual user hPrimaryToken = nullptr; HANDLE hShellProcess = nullptr, hShellProcessToken = nullptr; const DWORD dwTokenRights = TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID; DWORD dwPID = 0; HWND hwnd = nullptr; DWORD dwLastErr = 0; // Enable SeIncreaseQuotaPrivilege HANDLE hProcessToken = nullptr; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken)) goto unelevateFail; TOKEN_PRIVILEGES tkp; tkp.PrivilegeCount = 1; LookupPrivilegeValueW(nullptr, SE_INCREASE_QUOTA_NAME, &tkp.Privileges[0].Luid); tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hProcessToken, FALSE, &tkp, 0, nullptr, nullptr); dwLastErr = GetLastError(); CloseHandle(hProcessToken); if (ERROR_SUCCESS != dwLastErr) goto unelevateFail; // Get a primary copy of the desktop shell's token, // we're assuming the shell is running as the actual user hwnd = GetShellWindow(); if (!hwnd) goto unelevateFail; GetWindowThreadProcessId(hwnd, &dwPID); if (!dwPID) goto unelevateFail; hShellProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID); if (!hShellProcess) goto unelevateFail; if (!OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, &hShellProcessToken)) goto unelevateFail; // Duplicate the shell's process token to get a primary token. // Based on experimentation, this is the minimal set of rights required for // CreateProcessWithTokenW (contrary to current documentation). if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, nullptr, SecurityImpersonation, TokenPrimary, &hPrimaryToken)) goto unelevateFail; qDebug() << "Unelevated primary access token acquired"; goto unelevateCleanup; unelevateFail: qWarning() << "Unelevate failed, couldn't get access token"; unelevateCleanup: CloseHandle(hShellProcessToken); CloseHandle(hShellProcess); #endif } Settings::~Settings() { #ifdef Q_OS_WIN CloseHandle(hPrimaryToken); #endif } QString Settings::getSettingsDirPath() const { if (portable) return QString(".") + QDir::separator(); // workaround for https://bugreports.qt-project.org/browse/QTBUG-38845 #ifdef Q_OS_WIN wchar_t* path; bool isOld = false; // If true, we can't unelevate and just return the path for our current home auto shell32H = LoadLibrary(TEXT("shell32.dll")); if (!(isOld = (shell32H == nullptr))) { auto SHGetKnownFolderPathH = (decltype(&SHGetKnownFolderPath))GetProcAddress(shell32H, "SHGetKnownFolderPath"); if (!(isOld = (SHGetKnownFolderPathH == nullptr))) SHGetKnownFolderPathH(FOLDERID_RoamingAppData, 0, hPrimaryToken, &path); } if (isOld) { return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "AppData" + QDir::separator() + "Roaming" + QDir::separator() + "tox" + QDir::separator()); } else { QString pathStr = QString::fromStdWString(path); pathStr.replace("\\", "/"); return pathStr + "/tox"; } #elif defined(Q_OS_OSX) return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() + "Library" + QDir::separator() + "Application Support" + QDir::separator() + "Tox") + QDir::separator(); #else return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QDir::separator() + "tox") + QDir::separator(); #endif } #ifdef Q_OS_WIN HANDLE Settings::getPrimaryToken() const { return hPrimaryToken; } #endif qTox/updater/settings.h000077500000000000000000000024131415623743500155030ustar00rootroot00000000000000/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef SETTINGS_H #define SETTINGS_H #include #ifdef Q_OS_WIN #include #endif class Settings { public: Settings(); ~Settings(); QString getSettingsDirPath() const; ///< The returned path ends with a directory separator #ifdef Q_OS_WIN HANDLE getPrimaryToken() const; ///< Used to impersonnate the unelevated user #endif private: bool portable; static constexpr const char* SETTINGS_FILE = "qtox.ini"; #ifdef Q_OS_WIN HANDLE hPrimaryToken; #endif }; #endif // SETTINGS_H qTox/updater/update.cpp000066400000000000000000000101261415623743500154550ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "update.h" #include "serialize.h" #include "widget.h" #include #include #include #include #include unsigned char key[crypto_sign_PUBLICKEYBYTES] = {0x20, 0x89, 0x39, 0xaa, 0x9a, 0xe8, 0xb5, 0x21, 0x0e, 0xac, 0x02, 0xa9, 0xc4, 0x92, 0xd9, 0xa2, 0x17, 0x83, 0xbd, 0x78, 0x0a, 0xda, 0x33, 0xcd, 0xa5, 0xc6, 0x44, 0xc7, 0xfc, 0xed, 0x00, 0x13}; QByteArray getLocalFlist() { QByteArray flist; QFile flistFile("flist"); if (!flistFile.open(QIODevice::ReadOnly)) { qWarning() << "getLocalFlist: Can't open local flist"; return flist; } flist = flistFile.readAll(); flistFile.close(); return flist; } bool isUpToDate(UpdateFileMeta fileMeta) { QString appDir = qApp->applicationDirPath(); QFile file(appDir + QDir::separator() + fileMeta.installpath); if (!file.open(QIODevice::ReadOnly)) return false; // If the data we have is corrupted or old, mark it for update QByteArray data = file.readAll(); if (crypto_sign_verify_detached(fileMeta.sig, (unsigned char*)data.data(), data.size(), key) != 0) return false; return true; } QList genUpdateDiff(QList updateFlist, Widget* w) { QList diff; float progressDiff = 45; float progress = 5; for (UpdateFileMeta file : updateFlist) { if (!isUpToDate(file)) diff += file; progress += progressDiff / updateFlist.size(); w->setProgress(progress); } return diff; } QList parseFlist(QByteArray flistData) { QList flist; if (flistData.isEmpty()) { qWarning() << "AutoUpdater::parseflist: Empty data"; return flist; } // Check version if (flistData[0] != '1') { qWarning() << "AutoUpdater: parseflist: Bad version " << (uint8_t)flistData[0]; return flist; } flistData = flistData.mid(1); // Check signature if (flistData.size() < (int)(crypto_sign_BYTES)) { qWarning() << "AutoUpdater::parseflist: Truncated data"; return flist; } else { QByteArray msgData = flistData.mid(crypto_sign_BYTES); unsigned char* msg = (unsigned char*)msgData.data(); if (crypto_sign_verify_detached((unsigned char*)flistData.data(), msg, msgData.size(), key) != 0) { qCritical() << "AutoUpdater: parseflist: FORGED FLIST FILE"; return flist; } flistData = flistData.mid(crypto_sign_BYTES); } // Parse. We assume no errors handling needed since the signature is valid. while (!flistData.isEmpty()) { UpdateFileMeta newFile; memcpy(newFile.sig, flistData.data(), crypto_sign_BYTES); flistData = flistData.mid(crypto_sign_BYTES); newFile.id = dataToString(flistData); flistData = flistData.mid(newFile.id.size() + getVUint32Size(flistData)); newFile.installpath = dataToString(flistData); flistData = flistData.mid(newFile.installpath.size() + getVUint32Size(flistData)); newFile.size = dataToUint64(flistData); flistData = flistData.mid(8); flist += newFile; } return flist; } qTox/updater/update.h000066400000000000000000000036541415623743500151320ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef UPDATE_H #define UPDATE_H #include #include #include class Widget; struct UpdateFileMeta { unsigned char sig[crypto_sign_BYTES]; ///< Signature of the file (ed25519) QString id; ///< Unique id of the file QString installpath; ///< Local path including the file name. May be relative to qtox-updater or /// absolute uint64_t size; ///< Size in bytes of the file bool operator==(const UpdateFileMeta& other) { return (size == other.size && id == other.id && installpath == other.installpath && memcmp(sig, other.sig, crypto_sign_BYTES) == 0); } }; struct UpdateFile { UpdateFileMeta metadata; QByteArray data; }; /// Gets the local flist. Returns an empty array on error QByteArray getLocalFlist(); /// Parses and validates a flist file. Returns an empty list on error QList parseFlist(QByteArray flistData); /// Generates a list of files we need to update QList genUpdateDiff(QList updateFlist, Widget* w); extern unsigned char key[crypto_sign_PUBLICKEYBYTES]; #endif // UPDATE_H qTox/updater/updater.pro000066400000000000000000000012411415623743500156530ustar00rootroot00000000000000#------------------------------------------------- # # Project created by QtCreator 2014-11-09T21:09:08 # #------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = qtox-updater TEMPLATE = app CONFIG += c++11 QMAKE_CXXFLAGS += -fno-exceptions SOURCES += main.cpp\ widget.cpp \ update.cpp \ serialize.cpp \ settings.cpp HEADERS += widget.h \ update.h \ serialize.h \ settings.h FORMS += widget.ui RESOURCES += \ res.qrc INCLUDEPATH += libs/include RC_FILE = windows/updater.rc LIBS += -L$$PWD/libs/lib/ -lsodium win32 { LIBS += -lshell32 -luuid } qTox/updater/widget.cpp000066400000000000000000000157121415623743500154640ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "widget.h" #include "ui_widget.h" #include #include #include #include #include #include #include #include "update.h" #ifdef Q_OS_WIN #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0600 // Vista for SHGetKnownFolderPath #include #include #include #include const bool supported = true; const QString QTOX_PATH = "qtox.exe"; #else const bool supported = false; const QString QTOX_PATH; #endif const QString SETTINGS_FILE = "settings.ini"; Widget::Widget(const Settings& s) : QWidget(nullptr) , ui(new Ui::Widget) , settings{s} { ui->setupUi(this); // Updates only for supported platforms if (!supported) fatalError(tr("The qTox updater is not supported on this platform.")); QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); } Widget::~Widget() { delete ui; } void Widget::setProgress(int value) { ui->progress->setValue(value); ui->progress->repaint(); qApp->processEvents(); } void Widget::fatalError(QString message) { qCritical() << "Update aborted with error:" << message; QMessageBox::critical(this, tr("Error"), message + '\n' + tr("qTox will restart now.")); deleteUpdate(); restoreBackups(); startQToxAndExit(); } void Widget::deleteUpdate() { QDir updateDir(settings.getSettingsDirPath() + "/update/"); updateDir.removeRecursively(); } void Widget::startQToxAndExit() { #ifdef Q_OS_WIN // Try to restart qTox as the actual user with our unelevated token STARTUPINFOW si; PROCESS_INFORMATION pi; SecureZeroMemory(&si, sizeof(si)); SecureZeroMemory(&pi, sizeof(pi)); si.cb = sizeof(si); bool unelevateOk = true; auto advapi32H = LoadLibrary(TEXT("advapi32.dll")); if ((unelevateOk = (advapi32H != nullptr))) { auto CreateProcessWithTokenWH = (decltype(&CreateProcessWithTokenW))GetProcAddress(advapi32H, "CreateProcessWithTokenW"); if ((unelevateOk = (CreateProcessWithTokenWH != nullptr))) { if (!CreateProcessWithTokenWH(settings.getPrimaryToken(), 0, QTOX_PATH.toStdWString().c_str(), 0, 0, 0, QApplication::applicationDirPath().toStdWString().c_str(), &si, &pi)) unelevateOk = false; } } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); if (!unelevateOk) { qWarning() << "Failed to start unelevated qTox"; QProcess::startDetached(QTOX_PATH); } #else QProcess::startDetached(QTOX_PATH); #endif exit(0); } void Widget::deleteBackups() { for (QString file : backups) QFile(file + ".bak").remove(); } void Widget::restoreBackups() { for (QString file : backups) QFile(file + ".bak").rename(file); } void Widget::update() { /// 1. Find and parse the update (0-5%) // Check that the dir exists QString updateDirStr = settings.getSettingsDirPath() + "/update/"; QDir updateDir(updateDirStr); if (!updateDir.exists()) fatalError(tr("No update found.")); setProgress(2); // Check that we have a flist and that every file on the diff exists QFile updateFlistFile(updateDirStr + "flist"); if (!updateFlistFile.open(QIODevice::ReadOnly)) fatalError(tr("The update is incomplete!")); QByteArray updateFlistData = updateFlistFile.readAll(); updateFlistFile.close(); QList updateFlist = parseFlist(updateFlistData); setProgress(5); /// 2. Generate a diff (5-50%) QList diff = genUpdateDiff(updateFlist, this); for (UpdateFileMeta fileMeta : diff) if (!QFile::exists(updateDirStr + fileMeta.installpath)) fatalError(tr("The update is incomplete.")); if (diff.size() == 0) fatalError(tr("The update is empty!")); setProgress(50); qDebug() << "Diff generated," << diff.size() << "files to update"; /// 2. Check the update (50-75%) float checkProgressStep = 25.0 / (float)diff.size(); float checkProgress = 50; for (UpdateFileMeta fileMeta : diff) { UpdateFile file; file.metadata = fileMeta; QFile fileFile(updateDirStr + fileMeta.installpath); if (!fileFile.open(QIODevice::ReadOnly)) fatalError(tr("Update files are unreadable.")); file.data = fileFile.readAll(); fileFile.close(); if (file.data.size() != (int)fileMeta.size) fatalError(tr("Update files are corrupted.")); if (crypto_sign_verify_detached(file.metadata.sig, (unsigned char*)file.data.data(), file.data.size(), key) != 0) fatalError(tr("Update files are corrupted.")); checkProgress += checkProgressStep; setProgress(checkProgress); } setProgress(75); qDebug() << "Update files signature verified, installing"; /// 3. Install the update (75-95%) float installProgressStep = 20.0 / (float)diff.size(); float installProgress = 75; for (UpdateFileMeta fileMeta : diff) { // Backup old files if (QFile(fileMeta.installpath).exists()) { QFile(fileMeta.installpath + ".bak").remove(); QFile(fileMeta.installpath).rename(fileMeta.installpath + ".bak"); backups.append(fileMeta.installpath); } // Install new ones QDir().mkpath(QFileInfo(fileMeta.installpath).absolutePath()); QFile fileFile(updateDirStr + fileMeta.installpath); if (!fileFile.copy(fileMeta.installpath)) fatalError(tr("Unable to copy the update's files from ") + (updateDirStr + fileMeta.installpath) + " to " + fileMeta.installpath); installProgress += installProgressStep; setProgress(installProgress); } setProgress(95); /// 4. Delete the update and backups (95-100%) deleteUpdate(); setProgress(97); deleteBackups(); setProgress(100); /// 5. Start qTox and exit qDebug() << "Update applied, restarting qTox!"; startQToxAndExit(); } qTox/updater/widget.h000066400000000000000000000026751415623743500151350ustar00rootroot00000000000000/* Copyright © 2014-2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #ifndef WIDGET_H #define WIDGET_H #include "settings.h" #include #ifdef Q_OS_WIN #include #endif namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(const Settings& s); ~Widget(); // Utilities void deleteBackups(); void restoreBackups(); void setProgress(int value); // Noreturn void fatalError(QString message); ///< Calls deleteUpdate and startQToxAndExit void deleteUpdate(); void startQToxAndExit(); public slots: // Finds and applies the update void update(); private: Ui::Widget* ui; QStringList backups; const Settings& settings; }; #endif // WIDGET_H qTox/updater/widget.ui000066400000000000000000000065771415623743500153300ustar00rootroot00000000000000 Widget 0 0 401 224 qTox Updater :/res/qtox-256x256.png:/res/qtox-256x256.png 0 13 191 191 :/res/qtox-256x256.png true 206 95 171 20 0 0 Qt::AlignCenter false 205 115 171 20 Updating qTox ... Qt::AlignCenter 201 170 181 20 <html><head/><body><p><a href="https://tox.chat"><span style=" text-decoration: underline; color:#0000ff;">https://tox.chat</span></a></p></body></html> Qt::AlignCenter true 200 183 181 20 <a href="https://github.com/qTox/qtox">https://github.com/qTox/qtox</a> Qt::AlignCenter 195 32 191 31 14 qTox Update Qt::AlignCenter qTox/updater/windows/000077500000000000000000000000001415623743500151615ustar00rootroot00000000000000qTox/updater/windows/updater.exe.manifest000066400000000000000000000006241415623743500211370ustar00rootroot00000000000000 qTox/updater/windows/updater.ico000066400000000000000000000410761415623743500173310ustar00rootroot00000000000000@@ (B(@ %#%S%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%j%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%0%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%2%#%g%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%h%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%646:8::8::8::8::8::8::8::8::8::8::8::8:1/1%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%;9;ӺkTTTTTTTTTTTTTTz,*,%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%TTTTTTTTTTTTTTTTVTV%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%VTTTTTTTTTTTTTTTT%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%TTTTTTTTTTTTTTTTT%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%TTTTTTTTTTTTTTTTT%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%^TTTTTTTTTTTTTTTT%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%sTTTTTTTTTTTTTTTTgfg%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%trtTTTTTTTTTTTTTTTTJIJ%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%;9;TTTTTTTTTTTTTTTZ)')%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%TTTTTTTTTTTTTT\Z\%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%)')sTTTTTTTTTTTT%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%.,.fTTTTTTTTTTs('(%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%+)+popUTTTTTTUece(&(%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%868ٲnTTTTr535%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%,*,|TTTTTT,*,%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%-+-iTTTTTTTTi/-/%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%{y{TTTTTTTTTT%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%757TTTTTTTTTTTT979%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%TRTTTTTTTTTTTTTVTV%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%xwxTTTTTTTTTTTTyxy%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%xwxTTTTTTTTTTTTxwx%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%SQSTTTTTTTTTTTTSRS%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%757TTTTTTTTTTTT767%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%tstTTTTTTTTTTwvw%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%*(*hTTTTTTTTh*(*%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%*(*tstTTTTTTtst*(*%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%757SQSxwxxwxSQS757%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%k%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%h%#%5%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%202%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%2%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%OMO646%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&qU}|}0.0%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%USUTTe/-/%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%%#%c%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%535TTTl,*,%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%b%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%535a_aljltst~TTTTt(&(%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%:8:ՉTTTTTTTTTTTONO%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%babTTTTTTTTTTTTTp%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%FEFTTTTTTTTTTTTTTT;9;%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%313ZTTTTTTTTTTTTTTTjij%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%XWXTTTTTTTTTTTTTTTT%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%TTTTTTTTTTTTTTTTr%#%%#%%#%%#%%#%%#%%#%%#%|%#%%#%%#%%#%%#%%#%eTTTTTTTTTTTTTTTT]%#%%#%%#%%#%%#%%#%%#%|%#%g%#%%#%%#%%#%%#%%#%eTTTTTTTTTTTTTTTTd%#%%#%%#%%#%%#%%#%%#%g%#%O%#%%#%%#%%#%%#%%#%TTTTTTTTTTTTTTTT%#%%#%%#%%#%%#%%#%%#%O%#%7%#%%#%%#%%#%%#%%#%][]TTTTTTTTTTTTTTTT_]_%#%%#%%#%%#%%#%%#%%#%7%#%%#%%#%%#%%#%%#%%#%757UTTTTTTTTTTTTTTT;9;%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%fefTTTTTTTTTTTTTTsrs%#%%#%%#%%#%%#%%#%%#%%#%i%#%%#%%#%%#%%#%%#%(&(mTTTTTTTTTTTTf*(*%#%%#%%#%%#%%#%%#%%#%o%#%%#%%#%%#%%#%%#%%#%%#%0.0薕|TTTTTTTTTTr434%#%%#%%#%%#%%#%%#%%#%%#%%#%x%#%%#%%#%%#%%#%%#%%#%&$&RQRZTTTTTTXZYZ&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%)');9;SRSxwxyxyVTV=;=+)+%#%%#%%#%%#%%#%%#%%#%%#%%#%%#% %#%3%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%8%#%H%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%K%#%K%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%J%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%@%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%D%#%%#%K%#%w%#%%#%%#%%#%%#%%#%%#%x%#%L%#%?????????????`8<>???qTox/updater/windows/updater.rc000066400000000000000000000001131415623743500171460ustar00rootroot00000000000000ID_ICON ICON DISCARDABLE "updater.ico" 1 24 "updater.exe.manifest"qTox/util/000077500000000000000000000000001415623743500130005ustar00rootroot00000000000000qTox/util/include/000077500000000000000000000000001415623743500144235ustar00rootroot00000000000000qTox/util/include/util/000077500000000000000000000000001415623743500154005ustar00rootroot00000000000000qTox/util/include/util/compatiblerecursivemutex.h000066400000000000000000000020571415623743500227070ustar00rootroot00000000000000/* Copyright © 2021 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #pragma once #include #include #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) class CompatibleRecursiveMutex : public QRecursiveMutex {}; #else class CompatibleRecursiveMutex : public QMutex { public: CompatibleRecursiveMutex() : QMutex(QMutex::Recursive) {} }; #endif qTox/verify-commit-messages.sh000077500000000000000000000041251415623743500167630ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016 Zetok Zalbavar # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Script for verifying conformance to commit message format of commits in # commit range supplied. # # Script fails (non-zero exit status) if commit messages don't conform. # usage: # ./$script $commit_range # # $commit_range – in format `abdce..12345` # Fail as soon as an error appears set -eu -o pipefail ARG="$1" echo "" # ← formatting grep_for_invalid() { # we can't rely on differentiating merge and normal commits since short clones that travis does may not be able # to tell if the oldest commit is a merge commit or not git log --format=format:'%s' "$ARG" \ | grep -v -E '^((feat|fix|docs|style|refactor|perf|revert|test|chore)(\(.{,12}\))?:.{1,68})|(Merge .{1,70})$' } # Conform, /OR ELSE/. if grep_for_invalid then echo "" echo "Above ↑ commits don't conform to commit message format:" echo "https://github.com/qTox/qTox/blob/master/CONTRIBUTING.md#commit-message-format" echo "" echo "Please fix." echo "" echo "If you're not sure how to rewrite history, here's a helpful tutorial:" echo "https://www.atlassian.com/git/tutorials/rewriting-history/git-commit--amend/" echo "" echo "If you're still not sure what to do, feel free to pop on IRC, or ask in PR comments for help :)" # fail the build exit 1 fi qTox/windows/000077500000000000000000000000001415623743500135155ustar00rootroot00000000000000qTox/windows/cross-compile/000077500000000000000000000000001415623743500162745ustar00rootroot00000000000000qTox/windows/cross-compile/README.md000066400000000000000000000110401415623743500175470ustar00rootroot00000000000000# Cross-compile from Linux to Windows ## Intro Following these instructions you will be able to cross-compile qTox for Windows. This script can be used by qTox users and devs to compile qTox for Windows themselves. Please note that the compilation script doesn't build the updater. ## Usage [Install Docker](https://docs.docker.com/install). Create 2 directories: * `workspace` -- a directory that will contain a cache of qTox dependencies and the final qTox cross-compilation build. You should create this directory. * `qtox` -- the root directory of a qTox repository. This directory must contain the qTox source code that will be cross-compiled. These directories will be mounted inside a Docker container at `/workspace` and `/qtox`. > Note: > The contents of `qtox` directory are not modified during compilation. The > `build.sh` script makes a temporary copy of the `qtox` directory for the > compilation. Once you sort out the directories, you are ready to run the `build.sh` script in a Docker container. > Note: > The`build.sh` script takes 2 arguments: architecture and build type. > Valid values for the architecture are `i686` for 32-bit and `x86_64` for > 64-bit. Valid values for the build type are `release` and `debug`. All > case sensitive. To start cross-compiling for 32-bit release version of qTox run: ```sh sudo docker run --rm \ -v /absolute/path/to/your/workspace:/workspace \ -v /absolute/path/to/your/qtox:/qtox \ debian:buster-slim \ /bin/bash /qtox/windows/cross-compile/build.sh i686 release ``` If you want to debug some compilation issue, you might want to instead run: ```sh # Get shell inside Debian Buster container so that you can poke around if needed sudo docker run -it \ --rm \ -v /absolute/path/to/your/workspace:/workspace \ -v /absolute/path/to/your/qtox:/qtox \ debian:buster-slim \ /bin/bash # Run the script bash /qtox/windows/cross-compile/build.sh i686 release ``` These will cross-compile all of the qTox dependencies and qTox itself, storing them in the `workspace` directory. The first time you run it for each architecture, it will take a long time for the cross-compilation to finish, as qTox has a lot of dependencies that need to be cross-compiled. But once you do it once for each architecture, the dependencies will get cached inside the `workspace` directory, and the next time you build qTox, the `build.sh` script will skip recompiling them, going straight to compiling qTox, which is a lot faster. > Note: > On a certain Intel Core i7 processor, a fresh build takes about 125 > minutes on a single core, and about 30 minutes using all 8 hyperthreads. > Once built, however, it takes about 8 minutes on a single core and 2 > minutes using 8 hyperthreads to rebuild using the cached dependencies. After cross-compiling has finished, you should find the comiled qTox in a `workspace/i686/qtox` or `workspace/x86_64/qtox` directory, depending on the architecture. You will also find `workspace/dep-cache` directory, where all the cross-compiled qTox dependencies will be cached for the future builds. You can remove any directory inside the `dep-cache`, which will result in the `build.sh` re-compiling the removed dependency only. The `workspace` direcory structure for reference: ``` workspace ├── i686 │   ├── dep-cache │   │   ├── libexif │   │   ├── libffmpeg │   │   ├── libopenal │   │   ├── libopenssl │   │   ├── libopus │   │   ├── libqrencode │   │   ├── libqt5 │   │   ├── libsodium │   │   ├── libsqlcipher │   │   ├── libtoxcore │   │   ├── libvpx │   │   ├── mingw-w64-debug-scripts │   │   └── nsis_shellexecuteasuser │   └── qtox │   ├── debug │   └── release └── x86_64 ├── dep-cache │   ├── libexif │   ├── libffmpeg │   ├── libopenal │   ├── libopenssl │   ├── libopus │   ├── libqrencode │   ├── libqt5 │   ├── libsodium │   ├── libsqlcipher │   ├── libtoxcore │   ├── libvpx │   ├── mingw-w64-debug-scripts │   └── nsis_shellexecuteasuser └── qtox ├── debug └── release ``` qTox/windows/cross-compile/build.sh000066400000000000000000001174661415623743500177460ustar00rootroot00000000000000#!/usr/bin/env bash # MIT License # # Copyright (c) 2017-2020 Maxim Biro # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # Known issues: # - Doesn't build qTox updater, because it wasn't ported to cmake yet and # because it requires static Qt, which means we'd need to build Qt twice, and # building Qt takes really long time. set -euo pipefail # Common directory paths readonly WORKSPACE_DIR="/workspace" readonly QTOX_SRC_DIR="/qtox" # Make sure we run in an expected environment if [ ! -f /etc/os-release ] || ! cat /etc/os-release | grep -qi 'buster' then echo "Error: This script should be run on Debian Buster." exit 1 fi if [ ! -d "$WORKSPACE_DIR" ] || [ ! -d "$QTOX_SRC_DIR" ] then echo "Error: At least one of $WORKSPACE_DIR or $QTOX_SRC_DIR directories is missing." exit 1 fi if [ ! -d "$QTOX_SRC_DIR/src" ] then echo "Error: $QTOX_SRC_DIR/src directory is missing, make sure $QTOX_SRC_DIR contains qTox source code." exit 1 fi if [[ "$(id -u)" != "0" ]] then echo "Error: This script must be run as root." exit 1 fi # Check arguments readonly ARCH=$1 readonly BUILD_TYPE=$2 if [ -z "$ARCH" ] then echo "Error: No architecture was specified. Please specify either 'i686' or 'x86_64', case sensitive, as the first argument to the script." exit 1 fi if [[ "$ARCH" != "i686" ]] && [[ "$ARCH" != "x86_64" ]] then echo "Error: Incorrect architecture was specified. Please specify either 'i686' or 'x86_64', case sensitive, as the first argument to the script." exit 1 fi if [ -z "$BUILD_TYPE" ] then echo "Error: No build type was specified. Please specify either 'release' or 'debug', case sensitive, as the second argument to the script." exit 1 fi if [[ "$BUILD_TYPE" != "release" ]] && [[ "$BUILD_TYPE" != "debug" ]] then echo "Error: Incorrect build type was specified. Please specify either 'release' or 'debug', case sensitive, as the second argument to the script." exit 1 fi # More directory variables readonly BUILD_DIR="/build" readonly DEP_DIR="$WORKSPACE_DIR/$ARCH/dep-cache" readonly APT_CACHE_DIR="$WORKSPACE_DIR/$ARCH/apt_cache" # Create the expected directory structure # Just make sure those exist mkdir -p "$WORKSPACE_DIR" mkdir -p "$DEP_DIR" mkdir -p "$APT_CACHE_DIR" # Build dir should be empty rm -rf "$BUILD_DIR" mkdir -p "$BUILD_DIR" cd "$BUILD_DIR" set -x echo "Restoring package cache" # ensure at least one file exists touch "$APT_CACHE_DIR"/dummy # restore apt cache cp -r "$APT_CACHE_DIR"/* /var/cache/ # remove docker specific config file, this file prevents usage of the package cache rm -f /etc/apt/apt.conf.d/docker-clean readonly CURL_OPTIONS="-L --connect-timeout 10" # Get packages apt-get update apt-get install -y --no-install-recommends \ autoconf \ automake \ build-essential \ bsdtar \ ca-certificates \ cmake \ git \ libtool \ nsis \ pkg-config \ python3-pefile \ tclsh \ texinfo \ unzip \ curl \ yasm \ zip if [[ "$ARCH" == "i686" ]] then apt-get install -y --no-install-recommends \ g++-mingw-w64-i686 \ gcc-mingw-w64-i686 elif [[ "$ARCH" == "x86_64" ]] then apt-get install -y --no-install-recommends \ g++-mingw-w64-x86-64 \ gcc-mingw-w64-x86-64 fi # Install wine to run qTox tests in set +u if [ -z "$TRAVIS_CI_STAGE" ] || [[ "$TRAVIS_CI_STAGE" == "stage3" ]] then dpkg --add-architecture i386 apt-get update apt-get install -y wine wine32 wine64 fi set -u # Use all cores for building MAKEFLAGS=j$(nproc) export MAKEFLAGS # Helper functions # We check sha256 of all tarballs we download check_sha256() { if ! ( echo "$1 $2" | sha256sum -c --status - ) then echo "Error: sha256 of $2 doesn't match the known one." echo "Expected: $1 $2" echo -n "Got: " sha256sum "$2" exit 1 else echo "sha256 matches the expected one: $1" fi } # If it's not a tarball but a git repo, let's check a hash of a file containing hashes of all files check_sha256_git() { # There shoudl be .git directory if [ ! -d ".git" ] then echo "Error: this function should be called in the root of a git repository." exit 1 fi # Create a file listing hashes of all the files except .git/* find . -type f | grep -v "^./.git" | LC_COLLATE=C sort --stable --ignore-case | xargs sha256sum > "/tmp/hashes-$1.sha" check_sha256 "$1" "/tmp/hashes-$1.sha" } # Strip binaries to reduce file size, we don't need this information anyway strip_all() { set +e for PREFIX_DIR in $DEP_DIR/*; do strip --strip-unneeded $PREFIX_DIR/bin/* $ARCH-w64-mingw32-strip --strip-unneeded $PREFIX_DIR/bin/* $ARCH-w64-mingw32-strip --strip-unneeded $PREFIX_DIR/lib/* done set -e } # Store apt cache store_apt_cache() { # prevent old packages from polluting the cache apt-get autoclean cp -r /var/cache/apt/ "$APT_CACHE_DIR" } # OpenSSL OPENSSL_PREFIX_DIR="$DEP_DIR/libopenssl" OPENSSL_VERSION=1.1.1h # hash from https://www.openssl.org/source/ OPENSSL_HASH="5c9ca8774bd7b03e5784f26ae9e9e6d749c9da2438545077e6b3d755a06595d9" OPENSSL_FILENAME="openssl-$OPENSSL_VERSION.tar.gz" if [ ! -f "$OPENSSL_PREFIX_DIR/done" ] then rm -rf "$OPENSSL_PREFIX_DIR" mkdir -p "$OPENSSL_PREFIX_DIR" curl $CURL_OPTIONS -O "https://www.openssl.org/source/$OPENSSL_FILENAME" check_sha256 "$OPENSSL_HASH" "$OPENSSL_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf "$OPENSSL_FILENAME" rm $OPENSSL_FILENAME cd openssl* CONFIGURE_OPTIONS="--prefix=$OPENSSL_PREFIX_DIR --openssldir=${OPENSSL_PREFIX_DIR}/ssl shared" if [[ "$ARCH" == "x86_64" ]] then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS mingw64 --cross-compile-prefix=x86_64-w64-mingw32-" elif [[ "$ARCH" == "i686" ]] then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS mingw --cross-compile-prefix=i686-w64-mingw32-" fi ./Configure $CONFIGURE_OPTIONS make make install echo -n $OPENSSL_VERSION > $OPENSSL_PREFIX_DIR/done CONFIGURE_OPTIONS="" cd .. rm -rf ./openssl* else echo "Using cached build of OpenSSL `cat $OPENSSL_PREFIX_DIR/done`" fi # Qt QT_PREFIX_DIR="$DEP_DIR/libqt5" QT_MAJOR=5 QT_MINOR=12 QT_PATCH=12 QT_VERSION=$QT_MAJOR.$QT_MINOR.$QT_PATCH # hash from https://download.qt.io/archive/qt/5.12/5.12.12/single/qt-everywhere-src-5.12.12.tar.xz.mirrorlist QT_HASH="1979a3233f689cb8b3e2783917f8f98f6a2e1821a70815fb737f020cd4b6ab06" QT_FILENAME="qt-everywhere-src-$QT_VERSION.tar.xz" if [ ! -f "$QT_PREFIX_DIR/done" ] then rm -rf "$QT_PREFIX_DIR" mkdir -p "$QT_PREFIX_DIR" curl $CURL_OPTIONS -O "https://download.qt.io/official_releases/qt/$QT_MAJOR.$QT_MINOR/$QT_VERSION/single/$QT_FILENAME" check_sha256 "$QT_HASH" "$QT_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf $QT_FILENAME rm $QT_FILENAME cd qt* export PKG_CONFIG_PATH="$OPENSSL_PREFIX_DIR/lib/pkgconfig" export OPENSSL_LIBS="$(pkg-config --libs openssl)" # So, apparently Travis CI terminates a build if it generates more than 4mb of stdout output # which happens when building Qt CONFIGURE_EXTRA="" set +u if [[ -n "$TRAVIS_CI_STAGE" ]] then CONFIGURE_EXTRA="-silent" fi set -u ./configure -prefix $QT_PREFIX_DIR \ -release \ -shared \ -device-option CROSS_COMPILE=$ARCH-w64-mingw32- \ -xplatform win32-g++ \ -openssl \ $(pkg-config --cflags openssl) \ -opensource -confirm-license \ -pch \ -nomake examples \ -nomake tools \ -nomake tests \ -skip 3d \ -skip activeqt \ -skip androidextras \ -skip canvas3d \ -skip charts \ -skip connectivity \ -skip datavis3d \ -skip declarative \ -skip doc \ -skip gamepad \ -skip graphicaleffects \ -skip imageformats \ -skip location \ -skip macextras \ -skip multimedia \ -skip networkauth \ -skip purchasing \ -skip quickcontrols \ -skip quickcontrols2 \ -skip remoteobjects \ -skip script \ -skip scxml \ -skip sensors \ -skip serialbus \ -skip serialport \ -skip speech \ -skip translations \ -skip virtualkeyboard \ -skip wayland \ -skip webchannel \ -skip webengine \ -skip webglplugin \ -skip websockets \ -skip webview \ -skip x11extras \ -skip xmlpatterns \ -no-dbus \ -no-icu \ -no-compile-examples \ -qt-libjpeg \ -qt-libpng \ -qt-zlib \ -qt-pcre \ -opengl desktop $CONFIGURE_EXTRA make make install echo -n $QT_VERSION > $QT_PREFIX_DIR/done unset PKG_CONFIG_PATH unset OPENSSL_LIBS cd .. rm -rf ./qt* else echo "Using cached build of Qt `cat $QT_PREFIX_DIR/done`" fi # Stop here if running the first stage on Travis CI set +u if [[ "$TRAVIS_CI_STAGE" == "stage1" ]] then # Strip to reduce cache size strip_all store_apt_cache # Chmod since everything is root:root chmod 777 -R "$WORKSPACE_DIR" exit 0 fi set -u # SQLCipher SQLCIPHER_PREFIX_DIR="$DEP_DIR/libsqlcipher" SQLCIPHER_VERSION=v4.5.0 SQLCIPHER_HASH="20c46a855c47d5a0a159fdcaa8491ec7bdbaa706a734ee52bc76188b929afb14" SQLCIPHER_FILENAME="$SQLCIPHER_VERSION.tar.gz" if [ ! -f "$SQLCIPHER_PREFIX_DIR/done" ] then rm -rf "$SQLCIPHER_PREFIX_DIR" mkdir -p "$SQLCIPHER_PREFIX_DIR" curl $CURL_OPTIONS -O "https://github.com/sqlcipher/sqlcipher/archive/$SQLCIPHER_FILENAME" check_sha256 "$SQLCIPHER_HASH" "$SQLCIPHER_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf "$SQLCIPHER_FILENAME" rm $SQLCIPHER_FILENAME cd sqlcipher* sed -i s/'if test "$TARGET_EXEEXT" = ".exe"'/'if test ".exe" = ".exe"'/g configure sed -i 's|exec $PWD/mksourceid manifest|exec $PWD/mksourceid.exe manifest|g' tool/mksqlite3h.tcl ./configure --host="$ARCH-w64-mingw32" \ --prefix="$SQLCIPHER_PREFIX_DIR" \ --enable-shared \ --disable-static \ --enable-tempstore=yes \ CFLAGS="-O2 -g0 -DSQLITE_HAS_CODEC -I$OPENSSL_PREFIX_DIR/include/" \ LDFLAGS="-lcrypto -lgdi32 -L$OPENSSL_PREFIX_DIR/lib/" \ LIBS="-lgdi32 -lws2_32" sed -i s/"TEXE = $"/"TEXE = .exe"/ Makefile make make install echo -n $SQLCIPHER_VERSION > $SQLCIPHER_PREFIX_DIR/done cd .. rm -rf ./sqlcipher* else echo "Using cached build of SQLCipher `cat $SQLCIPHER_PREFIX_DIR/done`" fi # FFmpeg FFMPEG_PREFIX_DIR="$DEP_DIR/libffmpeg" FFMPEG_VERSION=4.4.1 FFMPEG_HASH="eadbad9e9ab30b25f5520fbfde99fae4a92a1ae3c0257a8d68569a4651e30e02" FFMPEG_FILENAME="ffmpeg-$FFMPEG_VERSION.tar.xz" if [ ! -f "$FFMPEG_PREFIX_DIR/done" ] then rm -rf "$FFMPEG_PREFIX_DIR" mkdir -p "$FFMPEG_PREFIX_DIR" curl $CURL_OPTIONS -O "https://www.ffmpeg.org/releases/$FFMPEG_FILENAME" check_sha256 "$FFMPEG_HASH" "$FFMPEG_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf $FFMPEG_FILENAME rm $FFMPEG_FILENAME cd ffmpeg* if [[ "$ARCH" == "x86_64"* ]] then CONFIGURE_OPTIONS="--arch=x86_64" elif [[ "$ARCH" == "i686" ]] then CONFIGURE_OPTIONS="--arch=x86" fi ./configure $CONFIGURE_OPTIONS \ --enable-gpl \ --enable-shared \ --disable-static \ --prefix="$FFMPEG_PREFIX_DIR" \ --target-os="mingw32" \ --cross-prefix="$ARCH-w64-mingw32-" \ --pkg-config="pkg-config" \ --extra-cflags="-O2 -g0" \ --disable-debug \ --disable-programs \ --disable-protocols \ --disable-doc \ --disable-sdl2 \ --disable-avfilter \ --disable-avresample \ --disable-filters \ --disable-iconv \ --disable-network \ --disable-muxers \ --disable-postproc \ --disable-swresample \ --disable-swscale-alpha \ --disable-dct \ --disable-dwt \ --disable-lsp \ --disable-lzo \ --disable-mdct \ --disable-rdft \ --disable-fft \ --disable-faan \ --disable-vaapi \ --disable-vdpau \ --disable-zlib \ --disable-xlib \ --disable-bzlib \ --disable-lzma \ --disable-encoders \ --disable-decoders \ --disable-demuxers \ --disable-parsers \ --disable-bsfs \ --enable-demuxer=h264 \ --enable-demuxer=mjpeg \ --enable-parser=h264 \ --enable-parser=mjpeg \ --enable-decoder=h264 \ --enable-decoder=mjpeg \ --enable-decoder=rawvideo make make install echo -n $FFMPEG_VERSION > $FFMPEG_PREFIX_DIR/done CONFIGURE_OPTIONS="" cd .. rm -rf ./ffmpeg* else echo "Using cached build of FFmpeg `cat $FFMPEG_PREFIX_DIR/done`" fi # Openal-soft (irungentoo's fork) # We can stop using the fork once OpenAL-Soft gets loopback capture implemented: # https://github.com/kcat/openal-soft/pull/421 OPENAL_PREFIX_DIR="$DEP_DIR/libopenal" OPENAL_VERSION=b80570bed017de60b67c6452264c634085c3b148 OPENAL_HASH="734ef00895a9c1eb1505c11d638030b73593376df75da66ac5db6aa3e2f76807" if [ ! -f "$OPENAL_PREFIX_DIR/done" ] then rm -rf "$OPENAL_PREFIX_DIR" mkdir -p "$OPENAL_PREFIX_DIR" git clone https://github.com/irungentoo/openal-soft-tox openal-soft-tox cd openal* git checkout $OPENAL_VERSION check_sha256_git "$OPENAL_HASH" # https://github.com/microsoft/vcpkg/blob/3baf583934f3077070e9ed4e7684f743ecced577/ports/openal-soft/cmake-3-11.patch > cmake-3-11.patch cat << "EOF" diff --git a/CMakeLists.txt b/CMakeLists.txt index a871f4c..f9f6b34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -965,7 +965,8 @@ OPTION(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) OPTION(ALSOFT_REQUIRE_MMDEVAPI "Require MMDevApi backend" OFF) IF(HAVE_WINDOWS_H) # Check MMSystem backend - CHECK_INCLUDE_FILES("windows.h;mmsystem.h" HAVE_MMSYSTEM_H -D_WIN32_WINNT=0x0502) + set(CMAKE_REQUIRED_DEFINITIONS -D_WIN32_WINNT=0x0502) + CHECK_INCLUDE_FILES("windows.h;mmsystem.h" HAVE_MMSYSTEM_H) IF(HAVE_MMSYSTEM_H) CHECK_SHARED_FUNCTION_EXISTS(waveOutOpen "windows.h;mmsystem.h" winmm "" HAVE_LIBWINMM) IF(HAVE_LIBWINMM) EOF patch -l < cmake-3-11.patch mkdir -p build cd build echo " SET(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_C_COMPILER $ARCH-w64-mingw32-gcc) SET(CMAKE_CXX_COMPILER $ARCH-w64-mingw32-g++) SET(CMAKE_RC_COMPILER $ARCH-w64-mingw32-windres) SET(CMAKE_FIND_ROOT_PATH /usr/$ARCH-w64-mingw32) # search for programs in the build host directories SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # for libraries and headers in the target directories SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) " > toolchain.cmake CFLAGS="-fPIC" cmake -DCMAKE_INSTALL_PREFIX="$OPENAL_PREFIX_DIR" \ -DCMAKE_BUILD_TYPE=Release \ -DALSOFT_UTILS=OFF \ -DALSOFT_EXAMPLES=OFF \ -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \ -DDSOUND_INCLUDE_DIR=/usr/$ARCH-w64-mingw32/include \ -DDSOUND_LIBRARY=/usr/$ARCH-w64-mingw32/lib/libdsound.a \ .. make make install echo -n $OPENAL_VERSION > $OPENAL_PREFIX_DIR/done cd .. cd .. rm -rf ./openal* else echo "Using cached build of irungentoo's OpenAL-Soft fork `cat $OPENAL_PREFIX_DIR/done`" fi # QREncode QRENCODE_PREFIX_DIR="$DEP_DIR/libqrencode" QRENCODE_VERSION=4.1.1 QRENCODE_HASH="e455d9732f8041cf5b9c388e345a641fd15707860f928e94507b1961256a6923" QRENCODE_FILENAME="qrencode-$QRENCODE_VERSION.tar.bz2" if [ ! -f "$QRENCODE_PREFIX_DIR/done" ] then rm -rf "$QRENCODE_PREFIX_DIR" mkdir -p "$QRENCODE_PREFIX_DIR" curl $CURL_OPTIONS -O https://fukuchi.org/works/qrencode/$QRENCODE_FILENAME check_sha256 "$QRENCODE_HASH" "$QRENCODE_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf "$QRENCODE_FILENAME" rm $QRENCODE_FILENAME cd qrencode* CFLAGS="-O2 -g0" ./configure --host="$ARCH-w64-mingw32" \ --prefix="$QRENCODE_PREFIX_DIR" \ --enable-shared \ --disable-static \ --disable-sdltest \ --without-tools \ --without-debug make make install echo -n $QRENCODE_VERSION > $QRENCODE_PREFIX_DIR/done cd .. rm -rf ./qrencode* else echo "Using cached build of QREncode `cat $QRENCODE_PREFIX_DIR/done`" fi # Exif EXIF_PREFIX_DIR="$DEP_DIR/libexif" EXIF_VERSION="0.6.24" EXIF_HASH="d47564c433b733d83b6704c70477e0a4067811d184ec565258ac563d8223f6ae" EXIF_FILENAME="libexif-$EXIF_VERSION.tar.bz2" if [ ! -f "$EXIF_PREFIX_DIR/done" ] then rm -rf "$EXIF_PREFIX_DIR" mkdir -p "$EXIF_PREFIX_DIR" curl $CURL_OPTIONS -O "https://github.com/libexif/libexif/releases/download/v${EXIF_VERSION}/$EXIF_FILENAME" check_sha256 "$EXIF_HASH" "$EXIF_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf $EXIF_FILENAME rm $EXIF_FILENAME cd libexif* CFLAGS="-O2 -g0" ./configure --host="$ARCH-w64-mingw32" \ --prefix="$EXIF_PREFIX_DIR" \ --enable-shared \ --disable-static \ --disable-docs \ --disable-nls make make install echo -n $EXIF_VERSION > $EXIF_PREFIX_DIR/done cd .. rm -rf ./libexif* else echo "Using cached build of Exif `cat $EXIF_PREFIX_DIR/done`" fi # Opus OPUS_PREFIX_DIR="$DEP_DIR/libopus" OPUS_VERSION=1.3.1 # https://archive.mozilla.org/pub/opus/SHA256SUMS.txt OPUS_HASH="65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d" OPUS_FILENAME="opus-$OPUS_VERSION.tar.gz" if [ ! -f "$OPUS_PREFIX_DIR/done" ] then rm -rf "$OPUS_PREFIX_DIR" mkdir -p "$OPUS_PREFIX_DIR" curl $CURL_OPTIONS -O "https://archive.mozilla.org/pub/opus/$OPUS_FILENAME" check_sha256 "$OPUS_HASH" "$OPUS_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf "$OPUS_FILENAME" rm $OPUS_FILENAME cd opus* CFLAGS="-O2 -g0" ./configure --host="$ARCH-w64-mingw32" \ --prefix="$OPUS_PREFIX_DIR" \ --enable-shared \ --disable-static \ --disable-extra-programs \ --disable-doc make make install echo -n $OPUS_VERSION > $OPUS_PREFIX_DIR/done cd .. rm -rf ./opus* else echo "Using cached build of Opus `cat $OPUS_PREFIX_DIR/done`" fi # Sodium SODIUM_PREFIX_DIR="$DEP_DIR/libsodium" SODIUM_VERSION=1.0.18 SODIUM_HASH="6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e46636c1" SODIUM_FILENAME="libsodium-$SODIUM_VERSION.tar.gz" if [ ! -f "$SODIUM_PREFIX_DIR/done" ] then rm -rf "$SODIUM_PREFIX_DIR" mkdir -p "$SODIUM_PREFIX_DIR" curl $CURL_OPTIONS -O "https://download.libsodium.org/libsodium/releases/$SODIUM_FILENAME" check_sha256 "$SODIUM_HASH" "$SODIUM_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf "$SODIUM_FILENAME" rm "$SODIUM_FILENAME" cd libsodium* ./configure --host="$ARCH-w64-mingw32" \ --prefix="$SODIUM_PREFIX_DIR" \ --enable-shared \ --disable-static make make install echo -n $SODIUM_VERSION > $SODIUM_PREFIX_DIR/done cd .. rm -rf ./libsodium* else echo "Using cached build of Sodium `cat $SODIUM_PREFIX_DIR/done`" fi # VPX VPX_PREFIX_DIR="$DEP_DIR/libvpx" VPX_VERSION=v1.11.0 VPX_HASH="965e51c91ad9851e2337aebcc0f517440c637c506f3a03948062e3d5ea129a83" VPX_FILENAME="libvpx-$VPX_VERSION.tar.gz" if [ ! -f "$VPX_PREFIX_DIR/done" ] then rm -rf "$VPX_PREFIX_DIR" mkdir -p "$VPX_PREFIX_DIR" curl $CURL_OPTIONS https://github.com/webmproject/libvpx/archive/$VPX_VERSION.tar.gz -o $VPX_FILENAME check_sha256 "$VPX_HASH" "$VPX_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf "$VPX_FILENAME" rm $VPX_FILENAME cd libvpx* if [[ "$ARCH" == "x86_64" ]] then VPX_TARGET=x86_64-win64-gcc # There is a bug in gcc that breaks avx512 on 64-bit Windows https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412 # VPX fails to build due to it. # This is a workaround as suggested in https://stackoverflow.com/questions/43152633 VPX_CFLAGS="-fno-asynchronous-unwind-tables" elif [[ "$ARCH" == "i686" ]] then VPX_TARGET=x86-win32-gcc VPX_CFLAGS="" fi cd .. # Fix VPX not supporting creation of dll # Modified version of https://aur.archlinux.org/cgit/aur.git/tree/configure.patch?h=mingw-w64-libvpx&id=6d658aa0f4d8409fcbd0f20be2c0adcf1e81a297 > configure.patch cat << "EOF" diff -ruN libvpx/build/make/configure.sh patched/build/make/configure.sh --- libvpx/build/make/configure.sh 2019-02-13 16:56:48.972857636 +0100 +++ patched/build/make/configure.sh 2019-02-13 16:50:37.995967583 +0100 @@ -1426,11 +1426,13 @@ win32) add_asflags -f win32 enabled debug && add_asflags -g cv8 + add_ldflags "-Wl,-no-undefined" EXE_SFX=.exe ;; win64) add_asflags -f win64 enabled debug && add_asflags -g cv8 + add_ldflags "-Wl,-no-undefined" EXE_SFX=.exe ;; linux*|solaris*|android*) diff -ruN libvpx/build/make/Makefile patched/build/make/Makefile --- libvpx/build/make/Makefile 2019-02-13 16:56:48.972857636 +0100 +++ patched/build/make/Makefile 2019-02-13 16:50:37.995967583 +0100 @@ -304,6 +304,7 @@ $(if $(quiet),@echo " [LD] $$@") $(qexec)$$(LD) -shared $$(LDFLAGS) \ -Wl,--no-undefined -Wl,-soname,$$(SONAME) \ + -Wl,-out-implib,libvpx.dll.a \ -Wl,--version-script,$$(EXPORTS_FILE) -o $$@ \ $$(filter %.o,$$^) $$(extralibs) endef @@ -388,7 +389,7 @@ .libs: $(LIBS) @touch $@ $(foreach lib,$(filter %_g.a,$(LIBS)),$(eval $(call archive_template,$(lib)))) -$(foreach lib,$(filter %so.$(SO_VERSION_MAJOR).$(SO_VERSION_MINOR).$(SO_VERSION_PATCH),$(LIBS)),$(eval $(call so_template,$(lib)))) +$(foreach lib,$(filter %dll,$(LIBS)),$(eval $(call so_template,$(lib)))) $(foreach lib,$(filter %$(SO_VERSION_MAJOR).dylib,$(LIBS)),$(eval $(call dl_template,$(lib)))) $(foreach lib,$(filter %$(SO_VERSION_MAJOR).dll,$(LIBS)),$(eval $(call dll_template,$(lib)))) diff -ruN libvpx/configure patched/configure --- libvpx/configure 2019-02-13 16:56:49.162860897 +0100 +++ patched/configure 2019-02-13 16:53:03.328719607 +0100 @@ -513,23 +513,23 @@ } process_detect() { - if enabled shared; then + #if enabled shared; then # Can only build shared libs on a subset of platforms. Doing this check # here rather than at option parse time because the target auto-detect # magic happens after the command line has been parsed. - case "${tgt_os}" in - linux|os2|solaris|darwin*|iphonesimulator*) + # case "${tgt_os}" in + # linux|os2|solaris|darwin*|iphonesimulator*) # Supported platforms - ;; - *) - if enabled gnu; then - echo "--enable-shared is only supported on ELF; assuming this is OK" - else - die "--enable-shared only supported on ELF, OS/2, and Darwin for now" - fi - ;; - esac - fi + # ;; + # *) + # if enabled gnu; then + # echo "--enable-shared is only supported on ELF; assuming this is OK" + # else + # die "--enable-shared only supported on ELF, OS/2, and Darwin for now" + # fi + # ;; + # esac + #fi if [ -z "$CC" ] || enabled external_build; then echo "Bypassing toolchain for environment detection." enable_feature external_build diff -ruN libvpx/examples.mk patched/examples.mk --- libvpx/examples.mk 2019-02-13 16:56:49.162860897 +0100 +++ patched/examples.mk 2019-02-13 16:50:37.995967583 +0100 @@ -315,7 +315,7 @@ ifneq ($(filter os2%,$(TGT_OS)),) SHARED_LIB_SUF=_dll.a else -SHARED_LIB_SUF=.so +SHARED_LIB_SUF=.dll.a endif endif CODEC_LIB_SUF=$(if $(CONFIG_SHARED),$(SHARED_LIB_SUF),.a) diff -ruN libvpx/libs.mk patched/libs.mk --- libvpx/libs.mk 2019-02-13 16:56:48.972857636 +0100 +++ patched/libs.mk 2019-02-13 16:50:37.995967583 +0100 @@ -256,12 +256,12 @@ LIBVPX_SO_SYMLINKS := LIBVPX_SO_IMPLIB := libvpx_dll.a else -LIBVPX_SO := libvpx.so.$(SO_VERSION_MAJOR).$(SO_VERSION_MINOR).$(SO_VERSION_PATCH) -SHARED_LIB_SUF := .so +LIBVPX_SO := libvpx.dll +SHARED_LIB_SUF := .dll EXPORT_FILE := libvpx.ver -LIBVPX_SO_SYMLINKS := $(addprefix $(LIBSUBDIR)/, \ - libvpx.so libvpx.so.$(SO_VERSION_MAJOR) \ - libvpx.so.$(SO_VERSION_MAJOR).$(SO_VERSION_MINOR)) +LIBVPX_SO_SYMLINKS := + + endif endif endif @@ -271,7 +271,7 @@ $(if $(LIBVPX_SO_IMPLIB), $(BUILD_PFX)$(LIBVPX_SO_IMPLIB)) $(BUILD_PFX)$(LIBVPX_SO): $(LIBVPX_OBJS) $(EXPORT_FILE) $(BUILD_PFX)$(LIBVPX_SO): extralibs += -lm -$(BUILD_PFX)$(LIBVPX_SO): SONAME = libvpx.so.$(SO_VERSION_MAJOR) +$(BUILD_PFX)$(LIBVPX_SO): SONAME = libvpx.dll $(BUILD_PFX)$(LIBVPX_SO): EXPORTS_FILE = $(EXPORT_FILE) libvpx.def: $(call enabled,CODEC_EXPORTS) EOF cd - patch -Np1 < ../configure.patch rm ../configure.patch CFLAGS="$VPX_CFLAGS" \ CROSS="$ARCH-w64-mingw32-" ./configure --target="$VPX_TARGET" \ --prefix="$VPX_PREFIX_DIR" \ --enable-shared \ --disable-static \ --enable-runtime-cpu-detect \ --disable-examples \ --disable-tools \ --disable-docs \ --disable-unit-tests make make install mkdir -p "$VPX_PREFIX_DIR/bin" mv "$VPX_PREFIX_DIR/lib/"*.dll "$VPX_PREFIX_DIR/bin/" mv ./libvpx*.dll.a "$VPX_PREFIX_DIR/lib/" echo -n $VPX_VERSION > $VPX_PREFIX_DIR/done cd .. rm -rf ./libvpx* else echo "Using cached build of VPX `cat $VPX_PREFIX_DIR/done`" fi # Toxcore TOXCORE_PREFIX_DIR="$DEP_DIR/libtoxcore" TOXCORE_VERSION=0.2.13 TOXCORE_HASH="67114fa57504c58b695f5dce8ef85124d555f2c3c353d0d2615e6d4845114ab8" TOXCORE_FILENAME="c-toxcore-$TOXCORE_VERSION.tar.gz" if [ ! -f "$TOXCORE_PREFIX_DIR/done" ] then rm -rf "$TOXCORE_PREFIX_DIR" mkdir -p "$TOXCORE_PREFIX_DIR" curl $CURL_OPTIONS https://github.com/TokTok/c-toxcore/archive/v$TOXCORE_VERSION.tar.gz -o $TOXCORE_FILENAME check_sha256 "$TOXCORE_HASH" "$TOXCORE_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf "$TOXCORE_FILENAME" rm "$TOXCORE_FILENAME" cd c-toxcore* mkdir -p build cd build export PKG_CONFIG_PATH="$OPUS_PREFIX_DIR/lib/pkgconfig:$SODIUM_PREFIX_DIR/lib/pkgconfig:$VPX_PREFIX_DIR/lib/pkgconfig" export PKG_CONFIG_LIBDIR="/usr/$ARCH-w64-mingw32" echo " SET(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_C_COMPILER $ARCH-w64-mingw32-gcc) SET(CMAKE_CXX_COMPILER $ARCH-w64-mingw32-g++) SET(CMAKE_RC_COMPILER $ARCH-w64-mingw32-windres) SET(CMAKE_FIND_ROOT_PATH /usr/$ARCH-w64-mingw32 $OPUS_PREFIX_DIR $SODIUM_PREFIX_DIR $VPX_PREFIX_DIR) " > toolchain.cmake cmake -DCMAKE_INSTALL_PREFIX=$TOXCORE_PREFIX_DIR \ -DBOOTSTRAP_DAEMON=OFF \ -DCMAKE_BUILD_TYPE=Release \ -DENABLE_STATIC=OFF \ -DENABLE_SHARED=ON \ -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \ .. make make install echo -n $TOXCORE_VERSION > $TOXCORE_PREFIX_DIR/done unset PKG_CONFIG_PATH unset PKG_CONFIG_LIBDIR cd .. cd .. rm -rf ./c-toxcore* else echo "Using cached build of Toxcore `cat $TOXCORE_PREFIX_DIR/done`" fi set +u if [[ -n "$TRAVIS_CI_STAGE" ]] || [[ "$BUILD_TYPE" == "debug" ]] then set -u # mingw-w64-debug-scripts MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR="$DEP_DIR/mingw-w64-debug-scripts" MINGW_W64_DEBUG_SCRIPTS_VERSION="c6ae689137844d1a6fd9c1b9a071d3f82a44c593" MINGW_W64_DEBUG_SCRIPTS_HASH="1343bee72f3d9fad01ac7101d6e9cffee1e76db82f2ef9a69f7c7e988ec4b301" if [ ! -f "$MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR/done" ] then rm -rf "$MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR" mkdir -p "$MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR" git clone https://github.com/nurupo/mingw-w64-debug-scripts mingw-w64-debug-scripts cd mingw-w64-debug-scripts git checkout $MINGW_W64_DEBUG_SCRIPTS_VERSION check_sha256_git "$MINGW_W64_DEBUG_SCRIPTS_HASH" sed -i "s|your-app-name.exe|qtox.exe|g" debug-*.bat mkdir -p "$MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR/bin" cp -a debug-*.bat "$MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR/bin/" echo -n $MINGW_W64_DEBUG_SCRIPTS_VERSION > $MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR/done cd .. rm -rf ./mingw-w64-debug-scripts else echo "Using cached build of mingw-w64-debug-scripts `cat $MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR/done`" fi # Expat EXPAT_PREFIX_DIR="$DEP_DIR/libexpat" EXPAT_VERSION="2.2.10" EXPAT_HASH="5dfe538f8b5b63f03e98edac520d7d9a6a4d22e482e5c96d4d06fcc5485c25f2" EXPAT_FILENAME="expat-$EXPAT_VERSION.tar.xz" if [ ! -f "$EXPAT_PREFIX_DIR/done" ] then rm -rf "$EXPAT_PREFIX_DIR" mkdir -p "$EXPAT_PREFIX_DIR" curl $CURL_OPTIONS -O "https://github.com/libexpat/libexpat/releases/download/R_${EXPAT_VERSION//./_}/$EXPAT_FILENAME" check_sha256 "$EXPAT_HASH" "$EXPAT_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf $EXPAT_FILENAME rm $EXPAT_FILENAME cd expat* CFLAGS="-O2 -g0" ./configure --host="$ARCH-w64-mingw32" \ --prefix="$EXPAT_PREFIX_DIR" \ --enable-static \ --disable-shared make make install echo -n $EXPAT_VERSION > $EXPAT_PREFIX_DIR/done cd .. rm -rf ./expat* else echo "Using cached build of Expat `cat $EXPAT_PREFIX_DIR/done`" fi # GDB GDB_PREFIX_DIR="$DEP_DIR/gdb" GDB_VERSION="9.2" GDB_HASH="360cd7ae79b776988e89d8f9a01c985d0b1fa21c767a4295e5f88cb49175c555" GDB_FILENAME="gdb-$GDB_VERSION.tar.xz" if [ ! -f "$GDB_PREFIX_DIR/done" ] then rm -rf "$GDB_PREFIX_DIR" mkdir -p "$GDB_PREFIX_DIR" curl $CURL_OPTIONS -O "http://ftp.gnu.org/gnu/gdb/$GDB_FILENAME" check_sha256 "$GDB_HASH" "$GDB_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf $GDB_FILENAME rm $GDB_FILENAME cd gdb* mkdir build cd build CFLAGS="-O2 -g0" ../configure --host="$ARCH-w64-mingw32" \ --prefix="$GDB_PREFIX_DIR" \ --enable-static \ --disable-shared \ --with-libexpat-prefix="$EXPAT_PREFIX_DIR" make make install cd .. echo -n $GDB_VERSION > $GDB_PREFIX_DIR/done cd .. rm -rf ./gdb* else echo "Using cached build of GDB `cat $GDB_PREFIX_DIR/done`" fi set +u fi set -u # NSIS ShellExecAsUser plugin NSISSHELLEXECASUSER_PREFIX_DIR="$DEP_DIR/nsis_shellexecuteasuser" NSISSHELLEXECASUSER_VERSION=" " NSISSHELLEXECASUSER_HASH="8fc19829e144716a422b15a85e718e1816fe561de379b2b5ae87ef9017490799" if [ ! -f "$NSISSHELLEXECASUSER_PREFIX_DIR/done" ] then rm -rf "$NSISSHELLEXECASUSER_PREFIX_DIR" mkdir -p "$NSISSHELLEXECASUSER_PREFIX_DIR" # Backup: https://web.archive.org/web/20171008011417/http://nsis.sourceforge.net/mediawiki/images/c/c7/ShellExecAsUser.zip curl $CURL_OPTIONS -O http://nsis.sourceforge.net/mediawiki/images/c/c7/ShellExecAsUser.zip check_sha256 "$NSISSHELLEXECASUSER_HASH" "ShellExecAsUser.zip" unzip ShellExecAsUser.zip 'ShellExecAsUser.dll' mkdir "$NSISSHELLEXECASUSER_PREFIX_DIR/bin" mv ShellExecAsUser.dll "$NSISSHELLEXECASUSER_PREFIX_DIR/bin" rm ShellExecAsUser* echo -n $NSISSHELLEXECASUSER_VERSION > $NSISSHELLEXECASUSER_PREFIX_DIR/done else echo "Using cached build of NSIS ShellExecAsUser plugin `cat $NSISSHELLEXECASUSER_PREFIX_DIR/done`" fi # Install ShellExecAsUser plugin cp "$NSISSHELLEXECASUSER_PREFIX_DIR/bin/ShellExecAsUser.dll" /usr/share/nsis/Plugins/x86-ansi/ # mingw-ldd MINGW_LDD_PREFIX_DIR="$DEP_DIR/mingw-ldd" MINGW_LDD_VERSION=v0.2.0 MINGW_LDD_HASH="d4cf712da18fa822b4934144d44cd254e18c9c0ca987363503bb3b6aeb3134db" MINGW_LDD_FILENAME="$MINGW_LDD_VERSION.tar.gz" if [ ! -f "$MINGW_LDD_PREFIX_DIR/done" ] then rm -rf "$MINGW_LDD_PREFIX_DIR" mkdir -p "$MINGW_LDD_PREFIX_DIR" curl $CURL_OPTIONS "https://github.com/nurupo/mingw-ldd/archive/$MINGW_LDD_FILENAME" -o "$MINGW_LDD_FILENAME" check_sha256 "$MINGW_LDD_HASH" "$MINGW_LDD_FILENAME" bsdtar --no-same-owner --no-same-permissions -xf "$MINGW_LDD_FILENAME" rm "$MINGW_LDD_FILENAME" cd mingw-ldd* mkdir "$MINGW_LDD_PREFIX_DIR/bin" cp -a "mingw_ldd/mingw_ldd.py" "$MINGW_LDD_PREFIX_DIR/bin/mingw-ldd.py" echo -n $MINGW_LDD_VERSION > $MINGW_LDD_PREFIX_DIR/done cd .. rm -rf ./mingw-ldd* else echo "Using cached build of mingw-ldd `cat $MINGW_LDD_PREFIX_DIR/done`" fi # Stop here if running the second stage on Travis CI set +u if [[ "$TRAVIS_CI_STAGE" == "stage2" ]] then # Strip to reduce cache size strip_all store_apt_cache # Chmod since everything is root:root chmod 777 -R "$WORKSPACE_DIR" exit 0 fi set -u strip_all # qTox QTOX_PREFIX_DIR="$WORKSPACE_DIR/$ARCH/qtox/$BUILD_TYPE" rm -rf "$QTOX_PREFIX_DIR" mkdir -p "$QTOX_PREFIX_DIR" rm -rf ./qtox mkdir -p qtox cd qtox cp -a $QTOX_SRC_DIR/. . rm -rf ./build mkdir -p build cd build PKG_CONFIG_PATH="" PKG_CONFIG_LIBDIR="" CMAKE_FIND_ROOT_PATH="" for PREFIX_DIR in $DEP_DIR/*; do if [ -d $PREFIX_DIR/lib/pkgconfig ] then export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$PREFIX_DIR/lib/pkgconfig" export PKG_CONFIG_LIBDIR="$PKG_CONFIG_LIBDIR:$PREFIX_DIR/lib/pkgconfig" fi CMAKE_FIND_ROOT_PATH="$CMAKE_FIND_ROOT_PATH $PREFIX_DIR" done echo " SET(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_C_COMPILER $ARCH-w64-mingw32-gcc) SET(CMAKE_CXX_COMPILER $ARCH-w64-mingw32-g++) SET(CMAKE_RC_COMPILER $ARCH-w64-mingw32-windres) SET(CMAKE_FIND_ROOT_PATH /usr/$ARCH-w64-mingw32 $CMAKE_FIND_ROOT_PATH) " > toolchain.cmake # Run tests using Wine set +u if [[ -n "$TRAVIS_CI_STAGE" ]] then echo "SET(TEST_CROSSCOMPILING_EMULATOR /usr/bin/wine)" >> toolchain.cmake fi set -u # Spell check on windows currently not supported, disable if [[ "$BUILD_TYPE" == "release" ]] then cmake -DCMAKE_TOOLCHAIN_FILE=./toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DSPELL_CHECK=OFF \ -DUPDATE_CHECK=ON \ -DSTRICT_OPTIONS=ON \ .. elif [[ "$BUILD_TYPE" == "debug" ]] then cmake -DCMAKE_TOOLCHAIN_FILE=./toolchain.cmake \ -DCMAKE_BUILD_TYPE=Debug \ -DSPELL_CHECK=OFF \ -DUPDATE_CHECK=ON \ -DSTRICT_OPTIONS=ON \ .. fi make cp qtox.exe $QTOX_PREFIX_DIR cp $QT_PREFIX_DIR/bin/Qt5Core.dll \ $QT_PREFIX_DIR/bin/Qt5Gui.dll \ $QT_PREFIX_DIR/bin/Qt5Network.dll \ $QT_PREFIX_DIR/bin/Qt5Svg.dll \ $QT_PREFIX_DIR/bin/Qt5Widgets.dll \ $QT_PREFIX_DIR/bin/Qt5Xml.dll \ $QTOX_PREFIX_DIR cp -r $QT_PREFIX_DIR/plugins/imageformats \ $QT_PREFIX_DIR/plugins/platforms \ $QT_PREFIX_DIR/plugins/iconengines \ $QTOX_PREFIX_DIR cp {$OPENSSL_PREFIX_DIR,$SQLCIPHER_PREFIX_DIR,$FFMPEG_PREFIX_DIR,$OPENAL_PREFIX_DIR,$QRENCODE_PREFIX_DIR,$EXIF_PREFIX_DIR,$OPUS_PREFIX_DIR,$SODIUM_PREFIX_DIR,$VPX_PREFIX_DIR,$TOXCORE_PREFIX_DIR}/bin/*.dll $QTOX_PREFIX_DIR cp /usr/lib/gcc/$ARCH-w64-mingw32/*-posix/libgcc_s_*.dll $QTOX_PREFIX_DIR cp /usr/lib/gcc/$ARCH-w64-mingw32/*-posix/libstdc++-6.dll $QTOX_PREFIX_DIR cp /usr/$ARCH-w64-mingw32/lib/libwinpthread-1.dll $QTOX_PREFIX_DIR # Setup wine if [[ "$ARCH" == "i686" ]] then export WINEARCH=win32 elif [[ "$ARCH" == "x86_64" ]] then export WINEARCH=win64 fi winecfg # dll checks python3 $MINGW_LDD_PREFIX_DIR/bin/mingw-ldd.py $QTOX_PREFIX_DIR/qtox.exe --dll-lookup-dirs $QTOX_PREFIX_DIR ~/.wine/drive_c/windows/system32 > /tmp/$ARCH-qtox-ldd find "$QTOX_PREFIX_DIR" -name '*.dll' > /tmp/$ARCH-qtox-dll-find # dlls loded at run time that don't showup as a link time dependency echo "$QTOX_PREFIX_DIR/libssl-1_1.dll $QTOX_PREFIX_DIR/libssl-1_1-x64.dll $QTOX_PREFIX_DIR/iconengines/qsvgicon.dll $QTOX_PREFIX_DIR/imageformats/qgif.dll $QTOX_PREFIX_DIR/imageformats/qico.dll $QTOX_PREFIX_DIR/imageformats/qjpeg.dll $QTOX_PREFIX_DIR/imageformats/qsvg.dll $QTOX_PREFIX_DIR/platforms/qdirect2d.dll $QTOX_PREFIX_DIR/platforms/qminimal.dll $QTOX_PREFIX_DIR/platforms/qoffscreen.dll $QTOX_PREFIX_DIR/platforms/qwindows.dll" > /tmp/$ARCH-qtox-dll-whitelist # Check that all dlls are in place if grep 'not found' /tmp/$ARCH-qtox-ldd then cat /tmp/$ARCH-qtox-ldd echo "Error: Missing some dlls." exit 1 fi # Check that no extra dlls get bundled while IFS= read -r line do # skip over whitelisted dlls if grep "$line" /tmp/$ARCH-qtox-dll-whitelist then continue fi if ! grep "$line" /tmp/$ARCH-qtox-ldd then echo "Error: extra dll included: $line. If this is a mistake and the dll is actually needed (e.g. it's loaded at run-time), please add it to the whitelist." exit 1 fi done < /tmp/$ARCH-qtox-dll-find # Run tests (only on Travis) set +u if [[ -n "$TRAVIS_CI_STAGE" ]] then # Add libgcc_s_*.dll, libwinpthread-1.dll, QtTest.dll, etc. into PATH env var of wine export WINEPATH=`cd $QTOX_PREFIX_DIR ; winepath -w $(pwd)`\;`winepath -w $QT_PREFIX_DIR/bin/` export CTEST_OUTPUT_ON_FAILURE=1 make test fi set -u cd .. # Setup gdb if [[ "$BUILD_TYPE" == "debug" ]] then # Copy over qTox source code so that dbg could pick it up mkdir -p "$QTOX_PREFIX_DIR/$PWD/src" cp -r "$PWD/src" "$QTOX_PREFIX_DIR/$PWD" # Get debug scripts cp -r $MINGW_W64_DEBUG_SCRIPTS_PREFIX_DIR/bin/* "$QTOX_PREFIX_DIR/" cp -r $GDB_PREFIX_DIR/bin/gdb.exe "$QTOX_PREFIX_DIR/" # Check that all dlls are in place python3 $MINGW_LDD_PREFIX_DIR/bin/mingw-ldd.py $QTOX_PREFIX_DIR/gdb.exe --dll-lookup-dirs $QTOX_PREFIX_DIR ~/.wine/drive_c/windows/system32 > /tmp/$ARCH-gdb-ldd if grep 'not found' /tmp/$ARCH-gdb-ldd then cat /tmp/$ARCH-gdb-ldd echo "Error: Missing some dlls." exit 1 fi fi # Strip set +e if [[ "$BUILD_TYPE" == "release" ]] then $ARCH-w64-mingw32-strip -s $QTOX_PREFIX_DIR/*.exe fi $ARCH-w64-mingw32-strip -s $QTOX_PREFIX_DIR/*.dll $ARCH-w64-mingw32-strip -s $QTOX_PREFIX_DIR/*/*.dll set -e # Create zip cd $QTOX_PREFIX_DIR zip qtox-"$ARCH"-"$BUILD_TYPE".zip -r * cd - # Create installer if [[ "$BUILD_TYPE" == "release" ]] then cd windows # The installer creation script expects all the files to be in qtox/* mkdir qtox cp -r $QTOX_PREFIX_DIR/* ./qtox rm ./qtox/*.zip # Select the installer script for the correct architecture if [[ "$ARCH" == "i686" ]] then makensis qtox.nsi elif [[ "$ARCH" == "x86_64" ]] then makensis qtox64.nsi fi cp setup-qtox.exe $QTOX_PREFIX_DIR/setup-qtox-"$ARCH"-"$BUILD_TYPE".exe cd .. fi cd .. rm -rf ./qtox # Cache APT packages for future runs store_apt_cache # Chmod since everything is root:root chmod 777 -R "$WORKSPACE_DIR" qTox/windows/generate-icon.sh000066400000000000000000000047731415623743500166040ustar00rootroot00000000000000#!/bin/bash # Copyright © 2017-2019 The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # Generates qtox.ico from qtox.svg # # Dependencies: # base64 8.25 # ImageMagick 7.0.4 # icoutils 0.31.0 # Black, gray and transparent colors from Windows 16-color palette base64 -d <<< R0lGODlhBAABAPEAAAAAAAAAAICAgMDAwCH5BAEAAAAALAAAAAAEAAEAAAIDjAYFADs= > pal.gif # Generate 32 bpp images convert -background none -depth 8 qtox.svg qtox_256_256_32.png convert -resize 64x64 qtox_256_256_32.png qtox_64_64_32.png convert -resize 48x48 qtox_256_256_32.png qtox_48_48_32.png convert -resize 32x32 qtox_256_256_32.png qtox_32_32_32.png convert -resize 24x24 qtox_256_256_32.png qtox_24_24_32.png convert -resize 16x16 qtox_256_256_32.png qtox_16_16_32.png # Generate 8 bpp images convert +dither qtox_48_48_32.png png8:qtox_48_48_8.png convert +dither qtox_32_32_32.png png8:qtox_32_32_8.png convert +dither qtox_16_16_32.png png8:qtox_16_16_8.png # Generate 1 bpp images convert +dither -remap pal.gif qtox_32_32_8.png png8:qtox_32_32_1.png convert +dither -remap pal.gif qtox_16_16_8.png png8:qtox_16_16_1.png # Hack for Windows XP file properties page convert -modulate 99.5 -strip qtox_256_256_32.png qtox_256_256_32.png convert -modulate 99.5 qtox_64_64_32.png qtox_64_64_32.png convert -modulate 99.5 qtox_48_48_32.png qtox_48_48_32.png convert -modulate 99.5 qtox_32_32_32.png qtox_32_32_32.png convert -modulate 99.5 qtox_24_24_32.png qtox_24_24_32.png convert -modulate 99.5 qtox_16_16_32.png qtox_16_16_32.png # Build .ico file from .png images icotool -c -t 32 \ qtox_32_32_1.png \ qtox_16_16_1.png \ qtox_48_48_8.png \ qtox_32_32_8.png \ qtox_16_16_8.png \ --raw=qtox_256_256_32.png \ qtox_64_64_32.png \ qtox_48_48_32.png \ qtox_32_32_32.png \ qtox_24_24_32.png \ qtox_16_16_32.png \ > qtox.ico # Show debug information icotool -l qtox.ico qTox/windows/qtox-nsi-version.sh000077500000000000000000000027311415623743500173240ustar00rootroot00000000000000#!/bin/bash # Copyright © 2016-2017 Zetok Zalbavar # Copyright © 2019 by The qTox Project Contributors # # This file is part of qTox, a Qt-based graphical interface for Tox. # qTox is libre software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qTox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qTox. If not, see # script to change qTox version in `.nsi` files to supplied one # # requires: # * files `qtox.nsi` and `qtox64.nsi` in working dir # * GNU sed # usage: # # ./$script $version # # $version has to be composed of at least one number/dot set -eu -o pipefail # change version in .nsi files in the right line change_version() { for nsi in *.nsi do sed -i -r "/DisplayVersion/ s/\"[0-9\\.]+\"$/\"$@\"/" "$nsi" done } # exit if supplied arg is not a version is_version() { if [[ ! $@ =~ [0-9\\.]+ ]] then echo "Not a version: $@" exit 1 fi } main() { is_version "$@" change_version "$@" } main "$@" qTox/windows/qtox.ico000066400000000000000000001433571415623743500152210ustar00rootroot00000000000000 000 >h@@ (BN00 %va   Ɨ hN 9!( @ @  @@@`( @@@@@@?(0` DBDNMN0/0&$&omoyxyB@BLKL313$"$|z|^\^hghOMO'$'1/1fefMKM%"%/-/xvx<:<-+-+)+~}~onoece`_`)')|{|^]^ONO'%'zyzRPR\[\424%#%xwxnlnA?A202#!#vuv][]XWX?=?0.0ede[Y[LJLGFG.,.878,*,a`aRQR979*(*434757(&(b`blkl]\]))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))I@@@@@@@@@@$)))))))))))))))))))))))))) D5 ))))))))))))))))))))))))13 ))))))))))))))))))))))))$8 ))))))))))))))))))))))))$6 ))))))))))))))))))))))))? ))))))))))))))))))))))))) )))))))))))))))))))))))) -( ))))))))))))))))))))))))) ?)))))))))))))))))))))))))) .J ))))))))))))))))))))))))))) 1< ))))))))))))))))))))))))))))) I"2$ ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) 44 ))))))))))))))))))))))))))))$G$))))))))))))))))))))))))))) << ))))))))))))))))))))))))))  ))))))))))))))))))))))))))))))))))))))))))))))))))))))))M ))))))))))))))))))))))))))))))A>))))))))))))))))))))))))))))) , )))))))))))))))))))))))))))))))  --  )))))))))))))))))))))))))))))))))) / )))))))))))))))))))))))))))))))))))))5)))))))))))))))))))))))))))))))))))) ;& ))))))))))))))))))))))))))))))  )))))))))))))))))))))))) $<(F+ )))))))))))))))) E:*C))))))))))) 50)))))))))) ? )))))))) K )))))))))L%))))))))70)))))))) !))))))) 9' )))))) H= )))))))B)))))))))E1))))))))))I # ))))))))) EHHE )))))))))))) )))))))))))))))))))))))))))))))))))))))))))???????@`?0???????( @USUIHI*(*RQR<:<ECEmlm#!#KJK,*,535><>GEGonoxwx%#%YWY.,.+*+trtIGI0.0979KIKsrs)')][]ZYZ/./;9;lklJIJ+)+424FDF$"$-+-UTUpop&$&868A?Aihiusu>=>rqrGFG(&(\Z\1/1YXY:8:babCAC%%6((2 %$%%2 %%'%% %1,)%&/ "%%11%%%%%%1-1"11%)+#% % 7%%*"%.%%%%4%%!%%3%05%%@`( @?@USU*(*<:<212rpronoDCD%#%IGI'%'a`a?>?HGH)')fdf;9;DBD+)+424FDF:9:$"$-+-UTU*)*?=?<;<EDE&$&868(&(1/1    " " #     # ! $  "   ?(@ @%#%%#%q%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$%#%:%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%E%#%5%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%:%#% %#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#% %#%F%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%G%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$$"$$"$$"$$"$$"$$"$$"$$"$$"$$"$$"$$"$$"$$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%1/1=<=>=>>=>>=>>=>>=>>=>>=>>=>>=>>=>>=>>=><:<-+-%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%"%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%GFGhXWWWWWWWWWWWWYv757$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%*(*󞝞wQTTTTTTTTTTTTTTPlkl%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$.,.뿾hSTTTTTTTTTTTTTTQ&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$.,.뿿hSTTTTTTTTTTTTTTQ&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$.,.뿿hSTTTTTTTTTTTTTTQ&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$-,-칹jSTTTTTTTTTTTTTTQ&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%*(*wRTTTTTTTTTTTTTTQmlm%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&qpqQTTTTTTTTTTTTTTTOMO$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$CBCXTTTTTTTTTTTTTSf313$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%)')QTTTTTTTTTTTTRdcd&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$656۵lRTTTTTTTTTTR|.,.$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$<:<ҭpRSTTTTTTSRz424$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$313wuw]TTTTTT^nmn/-/$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$'%'EDEĻjSTTSkDBD&$&$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$434߄YTTTTY535$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$424থsQTTTTTTQr424$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%)')QTTTTTTTTQ)')%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$@>@ZTTTTTTTTTTZ@>@$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%dddQTTTTTTTTTTQddd%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&QTTTTTTTTTTQ~&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&QTTTTTTTTTTQ~&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%dddQTTTTTTTTTTQddd%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$@>@YTTTTTTTTTTY@?@$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%)')QTTTTTTTTQ)')%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$424॥sQTTTTTTQr535$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$424ZQQQQY535$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$)')@>@ddd~ddd@?@)')$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%$%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$%#%&$&&$&%#%$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%"%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%J%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&656,*,$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%F%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%KIKzMLM*(*$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%"% %#%>%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%*(*󕔕|T757%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%8%#%H%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$MLMTQjA?A%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%B%#%%#%s%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$%#%&$&(&((&(979_SRf=;=$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%p%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$-+-CBCdcd}|zYTTRv/-/$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&#&%#%%#%%#%%#%%#%%#%%#%%#%%#%(&(NMNsWRQRRRTTTTS_^_%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%*(*nlnWRTTTTTTTTTTRk0.0$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&cbcTSTTTTTTTTTTTTUJHJ$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$;9;_STTTTTTTTTTTTTQnmn%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%hghQTTTTTTTTTTTTTTR~(&(%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%)()yRTTTTTTTTTTTTTTRp,*,%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%"%-+-kSTTTTTTTTTTTTTTSj.,.$"$%#%%#%%#%%#%%#%%#%y%#%%#%%#%%#%$"$-+-kSTTTTTTTTTTTTTTSk-+-%"%%#%%#%%#%%#%%#%z%#%h%#%%#%%#%%#%%#%*(*xRTTTTTTTTTTTTTTRx*(*%#%%#%%#%%#%%#%%#%j%#%L%#%%#%%#%%#%%#%%#%mlmQTTTTTTTTTTTTTTQpop%#%%#%%#%%#%%#%%#%%#%N%#%+%#%%#%%#%%#%%#%$"$@>@ZSTTTTTTTTTTTTTYCAC$"$%#%%#%%#%%#%%#%%#%,%#% %#%%#%%#%%#%%#%%#%(&(~}~RTTTTTTTTTTTTQ)')%#%%#%%#%%#%%#%%#%%$% %#%|%#%%#%%#%%#%%#%$"$424߮pRTTTTTTTTTTRk757$"$%#%%#%%#%%#%%#%%#%~%#%.%#%%#%%#%%#%%#%%#%$"$989֤tRSTTTTTTSRo=;=%#%%#%%#%%#%%#%%#%%#%%#%0###%#%%#%%#%%#%%#%%#%%#%$"$0/0ihidTQQQQSbono313$"$%#%%#%%#%%#%%#%%#%%#%&"&%#%#%#%%#%%#%%#%%#%%#%%#%$"$&$&424PNPnlnnmnQPQ535&$&$"$%#%%#%%#%%#%%#%%#%%#%%#%%%#%N%#%%#%%#%%#%%#%%#%%#%%#%$"$$"$%#%&$&&$&%#%$"$$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%Q%#%d%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%g#!#%#%%#%W%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%\$$$%#%3%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%6%#% %#%S%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%X$#$ %#% %#%>%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%@%#% ?????(0` $$#$%#%O%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%^%#%$#$%#%s%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%{%#%%#%L%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%N%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%(&(,*,,*,,*,,*,,*,,*,,*,,*,,*,,*,'%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$979׎nnnnnnnnno}|}0.0$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&utuORRRRRRRRRRPXWX$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%(%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%'QTTTTTTTTTTQddd$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%'QTTTTTTTTTTQdcd$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&QTTTTTTTTTTQ`_`$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%edeQTTTTTTTTTTTMKM$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$A?AYTTTTTTTTTSc424$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%)')QTTTTTTTTRgfg&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$202㜛xRSTTTTSR-+-$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$1/1utu^TTTT^nmn.,.$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$(&(^]^YTTY][]'%'$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&NMNcTTTTcOMO&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$?=?^RTTTTR^?=?$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%'QTTTTTTQ'%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$.,.뻻jSTTTTTTSj.,.$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$0/0eSTTTTTTSe0/0$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%+)+򣢣uRTTTTTTRu+)+%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%]\]RTTTTTTR^\^%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%-+-퍌RSTTSQ-+-%"%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$/-/mkmdWWdnln/-/$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'%#%%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$&$&313A?AA?A313&$&$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$$"$#!#$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%$%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%'$'<:<0.0%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%8%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$GFGrRPR)')$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%3%#%U%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$$"$)')zSwuw-+-$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%P%#%(%#%w%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$'%'.,.424434wvwQRxwx)')%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%u%#%%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$*(*LJLja`[TTURQR%#%%#%%#%%#%%#%%#%%#%%#%%#%&#&%#%%#%%#%%#%%#%$"$0.0焃UQSSSTTTRp-+-%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%)')QTTTTTTTTTWDBD$"$%#%%#%%#%%#%%#%%#%%#%%#%$"$DBDWTTTTTTTTTTQb`b$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%kjkQTTTTTTTTTTQyxy&$&%#%%#%%#%%#%%#%%#%%#%%#%&$&QTTTTTTTTTTQ&$&%#%%#%%#%%#%%#%%#%%#%%#%&$&{y{QTTTTTTTTTTQ{z{&$&%#%%#%%#%%#%%#%w%#%%#%%#%$"$[Y[RTTTTTTTTTTR\[\$"$%#%%#%%#%%#%y%#%O%#%%#%%#%$"$757aSTTTTTTTTS_878$"$%#%%#%%#%%#%P%#% %#%%#%%#%%#%&$&a`aTSTTTTTTTSede&$&%#%%#%%#%%#%%#%!%$%%#%%#%%#%%#%%#%*(*nmnVRSTTSRUutu+)+%#%%#%%#%%#%%#%$#$%#%4%#%%#%%#%%#%%#%(&(LKLz^WW^xONO)')$"$%#%%#%%#%%#%%#%6%#%v%#%%#%%#%%#%%#%$"$*(*757B@BB@B757*(*$"$%#%%#%%#%%#%%#%%#%y%"% %#%%#%%#%%#%%#%%#%%#%$"$$"$$"$$"$%#%%#%%#%%#%%#%%#%%#%%#% %#% %#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%H%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%K$#$%#%%#%M%#%%#%%#%%#%%#%%#%%#%%#%%#%P%#%???( @ %#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#% %#% %#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#% %#%=%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$%#%%#%%#%%#%%#%%#%$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%=%#%_%#%%#%%#%%#%%#%%#%%#%%#%/./babononmnnmnnmnnmnono\Z\,*,%#%%#%%#%%#%%#%%#%%#%%#%%#%_%#%d%#%%#%%#%%#%%#%%#%%#%$"$USUPQQQQQQSFDF$"$%#%%#%%#%%#%%#%%#%%#%%#%d%#%c%#%%#%%#%%#%%#%%#%%#%$"$\Z\QTTTTTTSKJK$"$%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%$"$UTURTTTTTTUGEG$"$%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%$"$><>ZSTTTTSa535$"$%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%(&(wvwRSTTSRhgh&$&%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%$"$-+-qpq]TT]kjk+)+%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$.,.돎SS.,.$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%%#%(&(rqrUTTTsqs(&(%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%$"$;9;[STTS[;9;$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%$"$IGITTTTTTIGI$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%$"$;9;[STTS[;9;$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%%#%(&(sqsRQQRtrt(&(%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%c%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%+)+ZYZ||ZYZ+*+%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%c%#%T%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%(&((&($"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%S%#%#%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%&$&A?A424%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%"%#%^%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$#!#ECEŲmIGI%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%[$#$%#%0%#%m%#%%#%%#%%#%%#%%#%$"$*(*<:=>$"$%#%%#%%#%%#%%#%%#%$"$IHITTTTTTTSRQR$"$%#%%#%%#%%#%%#%%#%$"$YWYRTTTTTTRYXY$"$%#%%#%%#%%#%%#%%#%$"$JIJTTTTTTTTKIK$"$%#%%#%%#%%#%y%#%$"$0.0賳mRTTTTRk1/1$"$%#%%#%z%#%7%#%%#%%#%ECEdQRRQbGFG%#%%#%%#%%#%8%#%%#%%#%%#%%#%979lklzznmn:8:%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$%#%)'))')%#%$"$%#%%#%%#%%#% %#%"%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%#%#% %#%X%#%%#%%#%%#%%#%%#%%#%Z%#% (0 %#%0%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%3%#%%#%%#%%#%%#%%#%%#%%#%$"$$"$$"$$"$$"$$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%,*,PNPWUWWUWWUWWVWMKM*(*%#%%#%%#%%#%%#%%#%%#%%#%#%#%%#%%#%%#%%#%$"$GFGTRRRRW>=>$"$%#%%#%%#%%#%%#%%#%#%#%#%#%%#%%#%%#%%#%$"$JIJTTTTTWA?A$"$%#%%#%%#%%#%%#%%#%#%#%#%#%%#%%#%%#%%#%$"$;9;[STTS`535$"$%#%%#%%#%%#%%#%%#%#%#%#%#%%#%%#%%#%%#%%#%(&(popTTTUgeg'%'%#%%#%%#%%#%%#%%#%%#%#%#%#%#%%#%%#%%#%%#%%#%%#%*(*hfhVVede)')%#%%#%%#%%#%%#%%#%%#%%#%#%#%#%#%%#%%#%%#%%#%%#%%#%.,.돎TT.,.%"%%#%%#%%#%%#%%#%%#%%#%#%#%#%#%%#%%#%%#%%#%%#%$"$QOQRTTRQOQ$"$%#%%#%%#%%#%%#%%#%%#%#%#%#%#%%#%%#%%#%%#%%#%$"$XVXQTTQXVX$"$%#%%#%%#%%#%%#%%#%%#%#%#%#%#%%#%%#%%#%%#%%#%$"$646ݼiPPi646$"$%#%%#%%#%%#%%#%%#%%#%#%#%"%#%%#%%#%%#%%#%%#%%#%%#%878fefede878%#%%#%%#%%#%%#%%#%%#%%#%%#%"%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$&$&.,.%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%`%#%%#%%#%%#%%#%%#%%#%$"$$"$SRS`^`)')%#%%#%%#%%#%%#%%#%%#%]%#%%#%3%#%^%#%j%#%%#%%#%(&(B@B\[\U_]_&$&%#%%#%%#%j%#%_%#%2%#%%#%%#%%#%(&(kjkXQRSe202$"$%#%%#%%#% %#%$"$=;=ZSTTTVDBD$"$%#%%#% %#% %#%$"$HGHTTTTTTIGI$"$%#%%#% %#%%#%$"$868^STTS^868$"$%#%%#%%#%i%#%&$&`_`XQQWcbc'%'%#%%#%j%#%%#%%#%(&(DBDjijkikECE(&(%#%%#%%#%%#%6%#%%#%$"$%#%%#%$"$%#%%#%%#%7%#%#%#%%#%%#%%#%%#%%#%%#%$?????(  %#%C%#%%#%%#%%#%$"$$"$$"$$"$%#%%#%%#%%#%%#%E%#%%#%%#%%#%*(*@?@DBDDCD?>?)')%#%%#%%#%%#%%#%%#%%#%%#%$"$<;<[UU^868$"$%#%%#%%#%%#%%#%%#%%#%%#%$"$868\SS`424$"$%#%%#%%#%%#%%#%%#%%#%%#%%#%(&(edeYYa`a'%'%#%%#%%#%%#%%#%%#%%#%%#%%#%%#%$"$<:ȁ`L!K ;6TKNNQ0LWw3`_i|42Fc' h&s\3ngD @ )))'vy{ÇC΢@JJq[aQ)2^5a.d8aTV9n ]ACd2 f-. 3a?⍨Tf4$^bg&WEEEx ;71Lh@O莝E@& L Wb :PAmb|V@?,nb8ZQ(d2ud} ,nAn߄œGL1,nHǀ :^ ;P@zzG pXyܘ1'<,ɓ؁< }hf&)H59𻱳xlag$`0268N vœ@31L@ vW,sA<4` | t!^bPܤ x',^jrm2 AѴе_`;J[o23*`J4-9;LAIƤ`vaaGW* $X,.k/ag!W;u2L~!rwZmSA܉+ q"{&"<Q*<>!h4 ǞuNӿ,T)n/*.8Rرc\P/&a<ĝu-=]~yQQQ$y>`Nsvׯ_ﶶ;w[YiYGYE}wvwAp:u[sG;łߖz۶G'<}ݫH˺qH;=TX[_{+3xmD{e6gk9.߯{$gk?3|sa(1^ڭss*k@ v‡~IS&{!Q:d)98;imEGgnΡjOJh'C&$&ݲK>h-&ףiҸªW730LFYh1sDE/MKK;OLMMτF/咸t[~aDdtpaa1`]gu_|`Wzpă?9}ƞ^e( ;~s}"2:%*HT7qf͚Flٲ%jYN,sMaa {_J٢p5jOΘ1c{>OXX)$1i~\^uN^7wȐ!9?V Qn~\A_$I3fxS~+AAh}WrF/;v=h4Ytɫ]+^?"_~cWڹ+TÀEGD~yı } : .@-,]{J0aGkW#."*{Pt:ݱ3^q??w>*2*O10*ˠHr|r<Ws|{jjO`*k4Lm.*4iSsV1~=V۰aÜڵV15{ޢcL7>>SNݏ=_Q<-TYnQWLT86j ;::{¢$M* D_ DPcԎ?aW4;wTk._VYa>&Æ b5_3&_;]eĞ(᜷VcjFׅ3hwcWT0 SsUdT!5QvTpqjY=WQn־QvT Ra@oЫs[4p칊 <j & Jsm,m4k= <j 5^j=ї *TcVQVT7jKxTSX},zNs~Go d.*ߩ(sa=,Sx'NUi(Mzz|E@;UEّ2DEU]]At:@#G SNVMMM\LP]^^^ vQ9+p5T*+9Tr vQr)Ϗ TwQ*+8ZyT@#Gh1+8~x0voC3D~sHő(ƢgQ ڐEձ*Պߟ PzWZ2*G+ۼyjovwN<xtp9{o{s$++Y6iQ =t;wJ7?79DaZZlI ϴ8T>dY=scs`۽{wXMu;7@;322k׮ T,M @_}c{QeUbtPiӟ=|-RTT+--tp ?8zY555^T[oSe5 @(G+zS ˹ihT)..1߀Gv[N_[e2voF v#G|qw[Uy^!U+W US]c:uiS΢;w: oG@TBkjjmܸ>˄o@˅]}TnpIڣ]:rV߼yst{cP hv7.i]&OsynD{t:ils8O4 {wIS&jZȿŷfffAp9Vj'cbbta'KNN>rSG9l*pTQW|k:t8(c4pB\ՠ8v9TWtInյho oo~'c^z~<{Gpr$Xnu1WЀ8$xK7 k## c͛7Gd$_ _Ppcƴnžۂ))) cr@OQ9ɼ5뛬̛نȗkN磲j`.XWPt9LOJ hkxpf}mmǞƒ 8u$S n۝.$̍@Fd6qr'Ft=zmhh`ɲ۰灎C|6l4O ÎtGx_y L |rԝs~רhN4?b+ 8ܖzۆ^reV,.8 mf8cE( +99ʯŋ;o߶Oőع9 mڼ&spG? ,--󚚚`~{SS= l9lJm\0Lg>!^p(qnҀfr4+@M7a(mZq7V׆649@:+mBV Հ/àU4 'x"ܤv* Bه4;e QҬ$(YE q (QE UiF X %ρrA0xˠKLIrRٺuS;Nyyy#tƕWyqQ"cȼ.]l?avڝXbU9{8Rq+@#ͩ<$IߓLзoߊvvvvĒ ?[eZn4͙;gunݎ+9O?\ @@qqq.ʜ qyȥK34'vr)*-2 >$ϛ'PKw'Plll-vqsTǺ/;ћPUHD @@e2 t"D #ew);q tH @ށ( RTYm1j9y*9K8 RT*))it-vr)*UWWbh.yyy-k[c 眽4e]8<*A۷ }?ގ\~;[7|ߜoܸ1ѣ)9QsΖ| v LPfgggG`Q;w /((HA @`,.xuh7^%˲;2*\t]C\\TT1_dz**V7nr+WNKpgN|bcVةS 7p;&N8;Lzrҽ#;6Tndۿ=9$㹌[l\;*6m4qƌwbXFFF?|HYȵ@emȚ ͙y_= ۡ q5_A=QSSV⵵#Goذa Esc}{ U{^^ǟ`rNk6i hIT߹s o,~ˈF%*//x>0mM6ُW*ҢE={nf}΢@o.? @]u]n_Ǐ1r2BCC+VXC ӟ~l;9