diff --git a/.appveyor.yml b/.appveyor.yml index 6f1ca0f99..f76b3b321 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -24,66 +24,25 @@ clone_depth: 50 #same as travis, see https://www.appveyor.com/blog/2014/06/04 image: Visual Studio 2017 cache: - - c:\openssl-release - - c:\protobuf-release - - c:\zlib-release -# TODO: set dependency on ps skript file (in ./ci) / "-> appveyor.yml" maybe not ideal -# that way when we update specific cached tools there (like protobuf or zlib), cache will be newly created automatically -# https://www.appveyor.com/docs/build-cache/#cleaning-up-cache - + - c:\Tools\vcpkg\installed environment: - openssl_ver: 1.0.2o - protobuf_ver: 3.6.0 - zlib_ver: 1.2.11 - matrix: - target_arch: win64 qt_ver: 5.9\msvc2017_64 cmake_generator: Visual Studio 15 2017 Win64 cmake_toolset: v141,host=x64 - vc_arch: amd64 + vcpkg_arch: x64 - target_arch: win32 qt_ver: 5.9\msvc2015 # Qt doesn't provide a msvc2017_32 cmake_generator: Visual Studio 15 2017 cmake_toolset: v141 - vc_arch: amd64_x86 - + vcpkg_arch: x86 install: - - ps: | - if (Test-Path c:\openssl-release) { - echo "using openssl from cache" - } else { - if ($env:target_arch -eq "win64") { # 64bit filename - # echo "downloading 64bit version of openssl" - Invoke-WebRequest "https://indy.fulgan.com/SSL/openssl-$env:openssl_ver-x64_86-win64.zip" -OutFile c:\openssl-$env:openssl_ver.zip - } else { # 32bit filename - # echo "downloading 32bit version of openssl" - Invoke-WebRequest "https://indy.fulgan.com/SSL/openssl-$env:openssl_ver-i386-win32.zip" -OutFile c:\openssl-$env:openssl_ver.zip - } - Expand-Archive -Path c:\openssl-$env:openssl_ver.zip -DestinationPath c:\openssl-release - Set-Location -Path C:\openssl-release - } - if (Test-Path c:\protobuf-release) { - echo "using protobuf from cache" - } else { - Invoke-WebRequest "https://github.com/protocolbuffers/protobuf/releases/download/v$env:protobuf_ver/protobuf-cpp-$env:protobuf_ver.zip" -OutFile c:\protobuf-cpp-$env:protobuf_ver.zip - Expand-Archive -Path c:\protobuf-cpp-$env:protobuf_ver.zip -DestinationPath c:\ - Set-Location -Path C:\protobuf-$env:protobuf_ver\cmake - cmake . -G "$env:cmake_generator" -T "$env:cmake_toolset" -Dprotobuf_BUILD_TESTS=0 -Dprotobuf_MSVC_STATIC_RUNTIME=0 -DCMAKE_INSTALL_PREFIX=c:/protobuf-release - msbuild INSTALL.vcxproj /p:Configuration=Release - } - if (Test-Path c:\zlib-release) { - echo "using zlib from cache" - } else { - Invoke-WebRequest "https://github.com/madler/zlib/archive/v$env:zlib_ver.zip" -OutFile c:\zlib-$env:zlib_ver.zip - Expand-Archive -Path c:\zlib-$env:zlib_ver.zip -DestinationPath c:\ - Set-Location -Path C:\zlib-$env:zlib_ver - cmake . -G "$env:cmake_generator" -T "$env:cmake_toolset" -DCMAKE_INSTALL_PREFIX=c:/zlib-release - msbuild INSTALL.vcxproj /p:Configuration=Release - } + - vcpkg remove --outdated --recurse + - vcpkg install openssl protobuf liblzma zlib --triplet %vcpkg_arch%-windows services: - mysql @@ -92,13 +51,10 @@ build_script: - ps: | New-Item -ItemType directory -Path $env:APPVEYOR_BUILD_FOLDER\build Set-Location -Path $env:APPVEYOR_BUILD_FOLDER\build - $zlibdir = "c:\zlib-release" - $openssldir = "C:\openssl-release" - $protodir = "c:\protobuf-release" - $protoc = "c:\protobuf-release\bin\protoc.exe" + $vcpkgbindir = "C:\Tools\vcpkg\installed\$env:vcpkg_arch-windows\bin" $mysqldll = "c:\Program Files\MySQL\MySQL Server 5.7\lib\libmysql.dll" cmake --version - cmake .. -G "$env:cmake_generator" -T "$env:cmake_toolset" "-DCMAKE_PREFIX_PATH=c:/Qt/$env:qt_ver;$protodir;$zlibdir;$openssldir" "-DWITH_SERVER=1" "-DPROTOBUF_PROTOC_EXECUTABLE=$protoc" "-DMYSQLCLIENT_LIBRARIES=$mysqldll" + cmake .. -G "$env:cmake_generator" -T "$env:cmake_toolset" "-DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake" "-DCMAKE_PREFIX_PATH=c:/Qt/$env:qt_ver;$vcpkgbindir" "-DWITH_SERVER=1" "-DMYSQLCLIENT_LIBRARIES=$mysqldll" - msbuild PACKAGE.vcxproj /p:Configuration=Release - ps: | $exe = dir -name *.exe @@ -110,7 +66,7 @@ build_script: (New-Object PSObject | Add-Member -PassThru NoteProperty bin $new_name | Add-Member -PassThru NoteProperty cmake $cmake_name | Add-Member -PassThru NoteProperty commit $env:APPVEYOR_REPO_COMMIT) | ConvertTo-JSON | Out-File -FilePath "latest-$env:target_arch" -Encoding ASCII Push-AppveyorArtifact "latest-$env:target_arch" $version = $matches['content'] - + test: off diff --git a/.ci/Fedora29/Dockerfile b/.ci/Fedora29/Dockerfile new file mode 100644 index 000000000..0107d5980 --- /dev/null +++ b/.ci/Fedora29/Dockerfile @@ -0,0 +1,19 @@ +FROM fedora:29 + +RUN dnf install -y \ + @development-tools \ + ccache \ + cmake \ + desktop-file-utils \ + file \ + gcc-c++ \ + hicolor-icon-theme \ + libappstream-glib \ + protobuf-devel \ + qt5-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel-5.11.1-2.fc29 \ + rpm-build \ + sqlite-devel \ + wget \ + zlib-devel \ + xz-devel \ + && dnf clean all diff --git a/.ci/UbuntuBionic/Dockerfile b/.ci/UbuntuBionic/Dockerfile new file mode 100644 index 000000000..96e120a8d --- /dev/null +++ b/.ci/UbuntuBionic/Dockerfile @@ -0,0 +1,22 @@ +FROM ubuntu:bionic + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + clang-format \ + file \ + g++ \ + git \ + ccache \ + cmake \ + liblzma-dev \ + libprotobuf-dev \ + libqt5multimedia5-plugins \ + libqt5svg5-dev \ + libqt5sql5-mysql \ + libqt5websockets5-dev \ + protobuf-compiler \ + qt5-default \ + qttools5-dev \ + qttools5-dev-tools \ + qtmultimedia5-dev \ + && rm -rf /var/lib/apt/lists/* diff --git a/.ci/travis-compile.sh b/.ci/travis-compile.sh index 8dbde5c28..2d647962d 100644 --- a/.ci/travis-compile.sh +++ b/.ci/travis-compile.sh @@ -1,59 +1,149 @@ #!/bin/bash +# This script is to be used in .travis.yaml from the project root directory, do not use it from somewhere else. + +# Read arguments +while [[ "$@" ]]; do + case "$1" in + '--format') + CHECK_FORMAT=1 + shift + ;; + '--install') + MAKE_INSTALL=1 + shift + ;; + '--package') + MAKE_PACKAGE=1 + shift + if [[ $# != 0 && $1 != -* ]]; then + PACKAGE_NAME="$1" + shift + if [[ $# != 0 && $1 != -* ]]; then + PACKAGE_TYPE="$1" + shift + fi + fi + ;; + '--server') + MAKE_SERVER=1 + shift + ;; + '--test') + MAKE_TEST=1 + shift + ;; + '--debug') + BUILDTYPE="Debug" + shift + ;; + '--release') + BUILDTYPE="Release" + shift + ;; + *) + if [[ $1 == -* ]]; then + echo "unrecognized option: $1" + exit 3 + fi + BUILDTYPE="$1" + shift + ;; + esac +done + +# Check formatting using clang-format +if [[ $CHECK_FORMAT ]]; then + echo "Checking your code using clang-format..." + diff="$(./clangify.sh --diff --cf-version)" + err=$? + case $err in + 1) + cat <&2 + ;; + esac +fi + set -e +# Setup ./servatrice/check_schema_version.sh - mkdir -p build cd build -prefix="" -if [[ $TRAVIS_OS_NAME == "osx" ]]; then - export PATH="/usr/local/opt/ccache/bin:$PATH" - prefix="-DCMAKE_PREFIX_PATH=$(echo /usr/local/opt/qt5/)" +# Add cmake flags +if [[ $MAKE_SERVER ]]; then + flags+=" -DWITH_SERVER=1" fi -if [[ $TRAVIS_OS_NAME == "linux" ]]; then - prefix="-DCMAKE_PREFIX_PATH=$(echo /opt/qt5*/lib/cmake/)" +if [[ $MAKE_TEST ]]; then + flags+=" -DTEST=1" + BUILDTYPE="Debug" # test requires buildtype Debug +fi +if [[ $BUILDTYPE ]]; then + flags+=" -DCMAKE_BUILD_TYPE=$BUILDTYPE" +fi +if [[ $PACKAGE_TYPE ]]; then + flags+=" -DCPACK_GENERATOR=$PACKAGE_TYPE" fi +# Add qt install location when using brew +if [[ $(uname) == "Darwin" ]]; then + PATH="/usr/local/opt/ccache/bin:$PATH" + flags+=" -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5/" +fi + +# Compile cmake --version +cmake .. $flags +make -j2 -if [[ $BUILDTYPE == "Debug" ]]; then - cmake .. -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=$BUILDTYPE $prefix -DTEST=1 - make -j2 +if [[ $MAKE_TEST ]]; then make test - - if [[ $TRAVIS_OS_NAME == "osx" ]]; then - make install - fi - - if [[ $TRAVIS_OS_NAME == "linux" ]]; then - cd .. - clang-format -version - clang-format -i \ - common/*.h \ - common/*.cpp \ - cockatrice/src/*.h \ - cockatrice/src/*.cpp \ - oracle/src/*.h \ - oracle/src/*.cpp \ - servatrice/src/*.h \ - servatrice/src/*.cpp - - git clean -f - git diff --quiet || ( - echo "*****************************************************"; - echo "*** This PR is not clean against our code style ***"; - echo "*** Run clang-format and fix up any differences ***"; - echo "*** Check our CONTRIBUTING.md file for details! ***"; - echo "*** Thank you ♥ ***"; - echo "*****************************************************"; - ) - git diff --exit-code - fi fi -if [[ $BUILDTYPE == "Release" ]]; then - cmake .. -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=$BUILDTYPE $prefix - make package -j2 +if [[ $MAKE_INSTALL ]]; then + make install +fi + +if [[ $MAKE_PACKAGE ]]; then + make package + if [[ $PACKAGE_NAME ]]; then + found=$(find . -maxdepth 1 -type f -name "Cockatrice-*.*" -print -quit) + path=${found%/*} + file=${found##*/} + if [[ ! $file ]]; then + echo "could not find package" >&2 + exit 1 + fi + mv "$path/$file" "$path/${file%.*}-$PACKAGE_NAME.${file##*.}" + fi fi diff --git a/.ci/travis-dependencies.sh b/.ci/travis-dependencies.sh deleted file mode 100644 index 002ca50a6..000000000 --- a/.ci/travis-dependencies.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -if [[ $TRAVIS_OS_NAME == "osx" ]] ; then - brew update - brew install ccache protobuf qt -fi -if [[ $TRAVIS_OS_NAME == "linux" ]] ; then - echo Skipping... packages are installed with the Travis apt addon for sudo disabled container builds -fi diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5a7f7bec7..41defe1ea 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -21,6 +21,20 @@ If you have any questions on IDEs, feel free to chat with us on [Gitter](https:/ # Code Style Guide # +### Formatting and continuous integration (ci) ### + +We currently use Travis CI to check your code for formatting issues, if your pull request was rejected because of this it would show a message in the logs. Click on "Details" next to the failed Travis CI build and then click on the failed build (most likely the fastest one) to see the log. + +The message will look somewhat similar to this: +``` +************************************************************ +*** Your code does not meet our formatting guidelines. *** +*** Please correct it then commit and push your changes. *** +*** See our CONTRIBUTING.md file for more information. *** +************************************************************ +``` +The CONTRIBUTING.md file mentioned is this file. Please read [this section](#Formatting) for full information on our formatting guidelines. + ### Compatibility ### Cockatrice is currently compiled on all platforms using C++11. You'll notice C++03 code throughout the codebase. Please feel free to help convert it over! @@ -28,7 +42,17 @@ Cockatrice is currently compiled on all platforms using C++11. You'll For consistency, we use Qt data structures where possible. For example, `QString` over `std::string` and `QList` over `std::vector`. -### Header files ### +### Formatting ### + +The handy tool `clang-format` can format your code for you, it is available for almost any environment. A special `.clang-format` configuration file is included in the project and is used to format your code. + +We've also included a bash script, `clangify.sh`, that will use clang-format to format all files in one go. Use `./clangify.sh --help` to show a full help page. + +To run clang-format on a single source file simply use the command `clang-format -i ` to format it in place. (some systems install clang-format with a specific version number appended, `find /usr/bin -name clang-format*` should find it for you) + +See [the clang-format documentation](https://clang.llvm.org/docs/ClangFormat.html) for more information about the tool. + +#### Header files #### Use header files with the extension `.h` and source files with the extension `.cpp`. @@ -38,28 +62,28 @@ Use header guards in the form of `FILE_NAME_H`. Simple functions, such as getters, may be written inline in the header file, but other functions should be written in the source file. -Keep library includes and project includes grouped together. So this is okay: +Group library includes after project includes, and in alphabetic order. Like this: ```c++ -// Good -#include -#include -#include "card.h" -#include "deck.h" - // Good #include "card.h" #include "deck.h" #include #include -// Bad: +// Bad #include #include "card.h" #include #include "deck.h" + +// Bad +#include "card.h" +#include "deck.h" +#include +#include ``` -### Naming ### +#### Naming #### Use `UpperCamelCase` for classes, structs, enums, etc. and `lowerCamelCase` for function and variable names. @@ -89,16 +113,16 @@ Bar& bar2 = *bar1; Use `nullptr` instead of `NULL` (or `0`) for null pointers. If you find any usage of the old keywords, we encourage you to fix it. -### Braces ### +#### Braces #### Braces should go on their own line except for control statements, the use of braces around single line statements is preferred. See the following example: ```c++ int main() -{ // function or class: own line - if (someCondition) { // control statement: same line - doSomething(); // single line statement, braces preferred - } else if (someOtherCondition1) { // else goes after closing brace +{ // function or class: own line + if (someCondition) { // control statement: same line + doSomething(); // single line statement, braces preferred + } else if (someOtherCondition1) { // else goes on the same line as a closing brace for (int i = 0; i < 100; i++) { doSomethingElse(); } @@ -110,20 +134,19 @@ int main() } ``` -### Indentation ### +#### Indentation and Spacing #### Always indent using 4 spaces, do not use tabs. Opening and closing braces should be on the same indentation layer, member access specifiers in classes or structs should not be indented. -### Lines ### +All operators and braces should be separated by spaces, do not add a space next to the inside of a brace. -Do not have trailing whitespace in your lines, if possible. Most IDEs check for this nowadays and clean it up for you. +If multiple lines of code that follow eachother have single line comments behind them, place all of them on the same indentation level. This indentation level should be equal to the longest line of code for each of these comments, without added spacing. -Lines should be 120 characters or less, but you can exceed this if you find it necessary. +#### Lines #### -### Automatic Formatting ### +Do not have trailing whitespace in your lines. Most IDEs check for this nowadays and clean it up for you. -The handy tool `clang-format` can format your code for you, a special `.clang-format` configuration file is included [here](https://github.com/Cockatrice/Cockatrice/blob/master/.clang-format). -See [the clang-format documentation](https://clang.llvm.org/docs/ClangFormat.html) for more information. +Lines should be 120 characters or less. Please break up lines that are too long into smaller parts, for example at spaces or after opening a brace. ### Memory Management ### @@ -177,35 +200,32 @@ You can find more information on how we use Protobuf on [our wiki!](https://gith # Translations # -**Basic workflow for translations:** +Basic workflow for translations: 1. Developer adds a `tr("foo")` string in the code; 2. Every few days, a maintainer updates the `*_en.ts files` with the new strings; 3. Transifex picks up the new files from github every 24 hours; - 4. Translators translate the new untraslated strings on Transifex; + 4. Translators translate the new untranslated strings on Transifex; 5. Before a release, a maintainer fetches the updated translations from Transifex. -### Translations (for developers) ### +### Using Translations (for developers) ### -**Step 1: Adding translatable strings to the code (`tr("foo")`)** - -All the user-interface strings inside Cockatrice's source code must be written in -english language.
+All the user-interface strings inside Cockatrice's source code must be written in english. Translations to other languages are managed using [Transifex](https://www.transifex.com/projects/p/cockatrice/). -If you're about to propose a change that adds or modifies any translatable string -in the code, you don't need to take care of adding the new strings to the -translation files. Every few days, or when a lot of new strings have been added, -someone from the development team will take care of extracing all the new strings, -adding them to the english translation files and making them available to -translators on Transifex. +Adding a new string to translate is as easy as adding the string in the 'tr("")' function, the string will be picked up as translatable automatically and translated as needed. +For example setting the text of this label in a way that the string "My name is:" can be translated: +```c++ +nameLabel.setText(tr("My name is:")); +``` -### Translations (for maintainers) ### +If you're about to propose a change that adds or modifies any translatable string in the code, you don't need to take care of adding the new strings to the translation files. +Every few days, or when a lot of new strings have been added, someone from the development team will take care of extracting all the new strings and adding them to the english translation files and making them available to translators on Transifex. -**Step 2: Updating `*_en.ts` files with new strings** +### Maintaining Translations (for maintainers) ### -When new translatable strings have been added to the code, it would be nice to +When new translatable strings have been added to the code, a maintainer should make them available to translators on Transifex. Every few days, or when a lot -of new strings have been added, a maintainer should take care of extracing all +of new strings have been added, a maintainer should take care of extracting all the new strings and add them to the english translation files. To update the english translation files, re-run cmake enabling the appropriate @@ -227,20 +247,18 @@ You should then notice that the following files have uncommitted changes: cockatrice/translations/cockatrice_en.ts oracle/translations/oracle_en.ts -It's now suggested to disable the parameter using: +It is recommended to disable the parameter afterwards using: ```sh cmake .. -DUPDATE_TRANSLATIONS=OFF ``` Now you are ready to propose your change. -**Step 3: Automatic pushing to Transifex** - -Once your change gets merged, Transifex will pick up the modified files automatically (checks every 24 hours) +Once your change gets merged, Transifex will pick up the modified files automatically (checked every 24 hours) and update the interface where translators will be able to translate the new strings. -**Step 5: Fetching new translations from Transifex** +### Releasing Translations (for maintainers) ### -Before rushing out a new release, it would be nice to fetch the most up to date +Before rushing out a new release, a maintainer should fetch the most up to date translations from Transifex and commit them into the Cockatrice source code. This can be done manually from the Transifex web interface, but it's quite time consuming. @@ -252,10 +270,9 @@ As an alternative, you can install the Transifex CLI: You'll then be able to use a git-like cli command to push and pull translations from Transifex to the source code and vice versa. -### Translations (for translators) ### - -**Step 4: Editing translations at Transifex** +### Adding Translations (for translators) ### +As a translator you can help translate the new strings on [Transifex](https://www.transifex.com/projects/p/cockatrice/). Please have a look at the specific [FAQ for translators](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ). @@ -295,11 +312,11 @@ git tag -d $TAG_NAME **NOTE:** Unfortunately, due to the method of how Travis and AppVeyor work, to publish a stable release you will need to make a copy of the release notes locally and then paste them into the GitHub GUI once the binaries have been uploaded by them. These CI services will automatically overwrite the name of the release (to "Cockatrice $TAG_NAME"), the status of the release (to "Pre-release"), and the release body (to "Beta build of Cockatrice"). -**NOTE 2:** In the first lines of https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt there's an hardcoded version number used when compiling custom (not tagged) versions. While on tagged versions these numbers are overriden by the version numbers coming from the tag title, it's a good practice to keep them aligned with the real ones. -The preferred flow of operations is: +**NOTE 2:** In the first lines of https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt there's an hardcoded version number used when compiling custom (not tagged) versions. While on tagged versions these numbers are overridden by the version numbers coming from the tag title, it's good practice to keep them aligned with the real ones. +The preferred flow of operation is: * just before a release, update the version number in CMakeLists.txt to "next release version"; * tag the release following the previously described syntax in order to get it built by CI; * wait for CI to upload the binaries, double check if everything is in order * after the release is complete, update the version number again to "next targeted beta version", typically increasing `PROJECT_VERSION_PATCH` by one. -**NOTE 3:** When releasing a new stable version, all the previous beta versions should be deleted. This is needed for Cockatrice to pick up the stable release also for users that chose the "beta" release channel. +**NOTE 3:** When releasing a new stable version, all the previous beta versions should be deleted. This is needed for Cockatrice to update users of the "beta" release channel to the latest version like other users. diff --git a/.gitignore b/.gitignore index aa0867979..a58ddf74b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ mysql.cnf .idea/ *.aps cmake-build-debug/ +preferences diff --git a/.travis.yml b/.travis.yml index 6378ecc2f..ba664c7c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,54 +1,114 @@ language: cpp compiler: gcc -cache: ccache matrix: include: - #Ubuntu - - name: Ubuntu (Debug) + #Ubuntu Xenial (Debug only) + - name: Ubuntu Xenial (Debug) if: tag IS NOT present os: linux dist: xenial group: stable - env: BUILDTYPE=Debug - - name: Ubuntu (Release) + cache: ccache + addons: + apt: + packages: + - libprotobuf-dev + - protobuf-compiler + - liblzma-dev + - qt5-default + - qttools5-dev + - qttools5-dev-tools + - qtmultimedia5-dev + - libqt5multimedia5-plugins + - libqt5svg5-dev + - libqt5sql5-mysql + - libqt5websockets5-dev + script: bash ./.ci/travis-compile.sh --format --server --test --debug + + #Ubuntu Bionic (on docker) + - name: Ubuntu Bionic (Debug) + if: tag IS NOT present + services: docker + env: NAME=UbuntuBionic + cache: + directories: + - $HOME/$NAME/ + before_install: docker build -t "cockatrice_${NAME,,}" .ci/$NAME && mkdir -p $HOME/$NAME/.ccache + script: docker run --mount "type=bind,source=$(pwd),target=/src" -w="/src" + --mount "type=bind,source=$HOME/$NAME/.ccache,target=/.ccache" -e "CCACHE_DIR=/.ccache" + "cockatrice_${NAME,,}" + bash .ci/travis-compile.sh --server --debug + + - name: Ubuntu Bionic (Release) if: (branch = master AND NOT type = pull_request) OR tag IS present - os: linux - dist: xenial - group: stable - env: BUILDTYPE=Release + services: docker + env: NAME=UbuntuBionic + cache: + directories: + - $HOME/$NAME/ + before_install: docker build -t "cockatrice_${NAME,,}" .ci/$NAME && mkdir -p $HOME/$NAME/.ccache + script: docker run --mount "type=bind,source=$(pwd),target=/src" -w="/src" + --mount "type=bind,source=$HOME/$NAME/.ccache,target=/.ccache" -e "CCACHE_DIR=/.ccache" + "cockatrice_${NAME,,}" + bash .ci/travis-compile.sh --server --package "$NAME" --release + + #Fedora 29 (on docker) + - name: Fedora 29 (Debug) + if: tag IS NOT present + services: docker + env: NAME=Fedora29 + cache: + directories: + - $HOME/$NAME/ + before_install: docker build -t "cockatrice_${NAME,,}" .ci/$NAME && mkdir -p $HOME/$NAME/.ccache + script: docker run --mount "type=bind,source=$(pwd),target=/src" -w="/src" + --mount "type=bind,source=$HOME/$NAME/.ccache,target=/.ccache" -e "CCACHE_DIR=/.ccache" + "cockatrice_${NAME,,}" + bash .ci/travis-compile.sh --server --debug + + - name: Fedora 29 (Release) + if: (branch = master AND NOT type = pull_request) OR tag IS present + services: docker + env: NAME=Fedora29 + cache: + directories: + - $HOME/$NAME/ + before_install: docker build -t "cockatrice_${NAME,,}" .ci/$NAME && mkdir -p $HOME/$NAME/.ccache + script: docker run --mount "type=bind,source=$(pwd),target=/src" -w="/src" + --mount "type=bind,source=$HOME/$NAME/.ccache,target=/.ccache" -e "CCACHE_DIR=/.ccache" + "cockatrice_${NAME,,}" + bash .ci/travis-compile.sh --server --package "$NAME" "RPM" --release + #macOS - name: macOS (Debug) if: tag IS NOT present os: osx - osx_image: xcode8 - env: BUILDTYPE=Debug + osx_image: xcode10.1 + cache: ccache + addons: + homebrew: + packages: + - ccache + - protobuf + - qt + - xz + script: bash ./.ci/travis-compile.sh --server --install --debug + - name: macOS (Release) if: (branch = master AND NOT type = pull_request) OR tag IS present os: osx - osx_image: xcode8 - env: BUILDTYPE=Release - -#install dependencies for container-based "linux" builds -addons: - apt: - packages: - - libprotobuf-dev - - protobuf-compiler - - qt5-default - - qttools5-dev - - qttools5-dev-tools - - qtmultimedia5-dev - - libqt5multimedia5-plugins - - libqt5svg5-dev - - libqt5sql5-mysql - - libqt5websockets5-dev - - -before_install: bash ./.ci/travis-dependencies.sh - -script: bash ./.ci/travis-compile.sh - + osx_image: xcode9.2 + cache: ccache + addons: + homebrew: + packages: + - ccache + - protobuf + - qt + - xz + update: true + script: bash ./.ci/travis-compile.sh --server --package "$TRAVIS_OS_NAME" --release # Builds for pull requests skip the deployment step altogether deploy: @@ -67,7 +127,7 @@ deploy: on: tags: true repo: Cockatrice/Cockatrice - condition: $BUILDTYPE = Release && $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}-beta(\.([2-9]|[1-9][0-9]))?$ # regex to match semver naming convention for beta pre-releases + condition: $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}-beta(\.([2-9]|[1-9][0-9]))?$ # regex to match semver naming convention for beta pre-releases # Deploy configuration for "stable" releases - provider: releases @@ -82,7 +142,7 @@ deploy: on: tags: true repo: Cockatrice/Cockatrice - condition: $BUILDTYPE = Release && $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}$ # regex to match semver naming convention for stable full releases + condition: $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}$ # regex to match semver naming convention for stable full releases notifications: diff --git a/CMakeLists.txt b/CMakeLists.txt index d5f75540e..f3d8ee588 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ endif() # A project name is needed for CPack # Version can be overriden by git tags, see cmake/getversion.cmake -PROJECT("Cockatrice" VERSION 2.6.1) +PROJECT("Cockatrice" VERSION 2.6.3) # Use c++11 for all targets set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ ISO Standard") @@ -99,7 +99,8 @@ option(WARNING_AS_ERROR "Treat warnings as errors in debug builds" ON) IF(MSVC) # Visual Studio: # Maximum optimization - set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD") + # Disable warning C4251 + set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD /wd4251") # Generate complete debugging information #set(CMAKE_CXX_FLAGS_DEBUG "/Zi") ELSEIF (CMAKE_COMPILER_IS_GNUCXX) @@ -171,7 +172,7 @@ IF(MSVC) ENDIF() # Package builder -set(CPACK_PACKAGE_CONTACT "Gavin Bisesi ") +set(CPACK_PACKAGE_CONTACT "Zach Halpern ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_NAME}) set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team") set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md") diff --git a/Dockerfile b/Dockerfile index 6c1e50f50..0c8951edd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,21 @@ -FROM ubuntu:trusty -MAINTAINER Gavin Bisesi +FROM ubuntu:bionic +MAINTAINER Zach Halpern -RUN apt-get update && apt-get install -y software-properties-common -RUN apt-add-repository ppa:ubuntu-sdk-team/ppa -RUN add-apt-repository -y ppa:smspillaz/cmake-master RUN apt-get update && apt-get install -y\ - build-essential g++\ + build-essential\ cmake\ git\ libprotobuf-dev\ + libqt5sql5-mysql\ + libqt5websockets5-dev\ protobuf-compiler\ qt5-default\ qtbase5-dev\ qttools5-dev-tools\ - qttools5-dev\ - libqt5sql5-mysql + qttools5-dev -ENV dir /home/servatrice/code -WORKDIR $dir -RUN mkdir oracle -COPY LICENSE LICENSE -COPY CMakeLists.txt CMakeLists.txt -COPY cmake/ cmake/ -COPY common/ common/ -COPY servatrice/ servatrice/ -COPY README.md README.md +COPY . /home/servatrice/code/ +WORKDIR /home/servatrice/code WORKDIR build RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 &&\ @@ -35,4 +26,4 @@ WORKDIR /home/servatrice EXPOSE 4747 -ENTRYPOINT [ "servatrice" ] +CMD [ "servatrice", "--log-to-console" ] diff --git a/README.md b/README.md index e0b8e8d11..82f11d28a 100644 --- a/README.md +++ b/README.md @@ -82,13 +82,14 @@ Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Tra **Detailed compiling instructions are on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)** -Dependencies: +Dependencies: *(for minimum requirements search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))* - [Qt](https://www.qt.io/developers/) - [protobuf](https://github.com/google/protobuf) - [CMake](https://www.cmake.org/) -Oracle can optionally use zlib to load zipped files: +Oracle can optionally use zlib and xz to load compressed files: - [zlib](https://www.zlib.net/) +- [xz](https://tukaani.org/xz/) To compile: diff --git a/clangify.sh b/clangify.sh index bad86ca42..671c69b98 100755 --- a/clangify.sh +++ b/clangify.sh @@ -1,22 +1,209 @@ #!/bin/bash # This script will run clang-format on all modified, non-3rd-party C++/Header files. +# Never, ever, should this recieve a path with a newline in it. Don't bother proofing it for that. -set -e -if hash clang-format 2>/dev/null && hash git 2>/dev/null; then - files_to_clean=($(git diff --name-only $(git merge-base origin/master HEAD))) +# go to the project root directory, this file should be located in the project root directory +cd "${BASH_SOURCE%/*}/" || exit 2 # could not find path, this could happen with special links etc. - printf "%s\n" ${files_to_clean[@]} | \ - xargs -I{} find '{}' \( -name "*.cpp" -o -name "*.h" \) \ - -not -path "./cockatrice/src/qt-json/*" \ - -not -path "./servatrice/src/smtp/*" \ - -not -path "./common/sfmt/*" \ - -not -path "./oracle/src/zip/*" \ - -not -path "./build*/*" \ - -exec clang-format -style=file -i {} \; - echo "Successfully formatted following files:" - printf "%s\n" ${files_to_clean[@]} -else - echo "Please install clang-format and git to use this program" +# defaults +include=("common" \ +"cockatrice/src" \ +"oracle/src" \ +"servatrice/src") +exclude=("servatrice/src/smtp" \ +"common/sfmt" \ +"common/lib" \ +"oracle/src/zip" \ +"oracle/src/lzma" \ +"oracle/src/qt-json") +exts=("cpp" "h") +cf_cmd="clang-format" +branch="origin/master" + +# parse options +while [[ $@ ]]; do + case "$1" in + '-b'|'--branch') + branch=$2 + set_branch=1 + shift 2 + ;; + '-c'|'--color-diff') + color=" --color=always" + mode=diff + shift + ;; + '-d'|'--diff') + mode=diff + shift + ;; + '-h'|'--help') + cat <s are given, all source files in those directories of the project root +path are formatted. To only format changed files in these directories use the +--branch option in combination. has to be a path relative to the project +root path or a full path inside $PWD. +. can not be specified as a dir, if you really want to format everything use */. + +USAGE: $0 [option] [--branch ] [ ...] + +DEFAULTS: +Current includes are: + ${include[@]/%/ + } +Default excludes are: + ${exclude[@]/%/ + } +OPTIONS: + -b, --branch + Compare to this git branch and format only files that differ. + If unspecified it defaults to origin/master. + To not compare to a branch this has to be explicitly set to "". + When not comparing to a branch, git will not be used at all and every + source file in the entire project will be parsed. + + -c, --color-diff + Display a colored diff. Implies --diff. + Only available on systems which support 'diff --color'. + + -d, --diff + Display a diff. Implies --test. + + -h, --help + Display this message and exit. + + -n, --names + Display a list of filenames that require formatting. Implies --test. + + -t, --test + Do not edit files in place. Set exit code to 1 if changes are required. + + --cf-version + Print the version of clang-format being used before continuing. + +EXIT CODES: + 0 on a successful format or if no files require formatting. + 1 if a file requires formatting. + 2 if given incorrect arguments. + 3 if clang-format could not be found. + +EXAMPLES: + $0 --test \$PWD || echo "code requires formatting" + Tests if the source files in the current directory are correctly + formatted and prints an error message if formatting is required. + + $0 --branch $USER/patch-2 ${include[0]} + Formats all changed files compared to the git branch "$USER/patch-2" + in the directory ${include[0]}. +EOM + exit 0 + ;; + '-n'|'--names') + mode=name + shift + ;; + '-t'|'--test') + mode=code + shift + ;; + '--cf-version') + print_version=1 + shift + ;; + '--') + shift + ;; + *) + if next_dir=$(cd "$1" && pwd); then + if [[ ${next_dir#$PWD/} == /* ]]; then + echo "error in parsing arguments of $0: $next_dir is not in $PWD" >&2 + exit 2 # input error + elif ! [[ $set_include ]]; then + include=() # remove default includes + set_include=1 + fi + include+=("${next_dir#$PWD/}") + else + echo "error in parsing arguments of $0: $PWD/$1 is not a directory" >&2 + exit 2 # input error + fi + if ! [[ $set_branch ]]; then + unset branch # unset branch if not set explicitly + fi + shift + ;; + esac +done + +# check availability of clang-format +if ! hash $cf_cmd 2>/dev/null; then + echo "could not find $cf_cmd" >&2 + # find any clang-format-x.x in /usr/bin + cf_cmd=$(find /usr/bin -regex '.*/clang-format-[0-9]+\.[0-9]+' -print -quit) + if [[ $cf_cmd ]]; then + echo "found $cf_cmd instead" >&2 + else + exit 3 # special exit code for missing dependency + fi fi + +if [[ $branch ]]; then + # get all dirty files through git + if ! base=$(git merge-base ${branch} HEAD); then + echo "could not find git merge base" >&2 + exit 2 # input error + fi + declare -a reg + for ex in ${exts[@]}; do + reg+=(${include[@]/%/.*\\.$ex\$}) + done + names=$(git diff --name-only $base | grep ${reg[@]/#/-e ^}) +else + names=$(find ${include[@]} -type f -false ${exts[@]/#/-o -name *\\.}) +fi + +# filter excludes +names=$(<<<"$names" grep -v ${exclude[@]/#/-e ^}) + +if ! [[ $names ]]; then + exit 0 # nothing to format means format is successful! +fi + +# optionally print version +[[ $print_version ]] && $cf_cmd -version + +# format +case $mode in + diff) + declare -i code=0 + for name in ${names[@]}; do + if ! $cf_cmd "$name" | diff "$name" - -p $color; then + code=1 + fi + done + exit $code + ;; + name) + declare -i code=0 + for name in ${names[@]}; do + if ! $cf_cmd "$name" | diff "$name" - -q >/dev/null; then + echo "$name" + code=1 + fi + done + exit $code + ;; + code) + for name in ${names[@]}; do + $cf_cmd "$name" | diff "$name" - -q >/dev/null || exit 1 + done + ;; + *) + $cf_cmd -i $names + ;; +esac diff --git a/cmake/FindVCredistRuntime.cmake b/cmake/FindVCredistRuntime.cmake index 2786315f2..a9d074456 100644 --- a/cmake/FindVCredistRuntime.cmake +++ b/cmake/FindVCredistRuntime.cmake @@ -1,36 +1,36 @@ -# Find the MS Visual Studio VC redistributable package - -if (WIN32) - set(VCREDISTRUNTIME_FOUND "NO") - - if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64-bit - set(REDIST_ARCH x64) - else() - set(REDIST_ARCH x86) - endif() - - set(REDIST_FILE vc_redist.${REDIST_ARCH}.exe) - - set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) - include(InstallRequiredSystemLibraries) - - # Check if the list contains minimum one element, to get the path from - list(LENGTH CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS libsCount) - if (libsCount GREATER 0) - list(GET CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS 0 _path) - - get_filename_component(_path ${_path} DIRECTORY) - get_filename_component(_path ${_path}/../../ ABSOLUTE) - - if (EXISTS "${_path}/${REDIST_FILE}") # VS 2017 - set(VCREDISTRUNTIME_FOUND "YES") - set(VCREDISTRUNTIME_FILE ${_path}/${REDIST_FILE}) - endif() - endif() - - if(VCREDISTRUNTIME_FOUND) - message(STATUS "Found VCredist ${VCREDISTRUNTIME_FILE}") - else() - message(WARNING "Could not find VCredist package. It's not required for compiling, but needs to be available at runtime.") - endif() -endif() +# Find the MS Visual Studio VC redistributable package + +if (WIN32) + set(VCREDISTRUNTIME_FOUND "NO") + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64-bit + set(REDIST_ARCH x64) + else() + set(REDIST_ARCH x86) + endif() + + set(REDIST_FILE vcredist_${REDIST_ARCH}.exe) + + set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) + include(InstallRequiredSystemLibraries) + + # Check if the list contains minimum one element, to get the path from + list(LENGTH CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS libsCount) + if (libsCount GREATER 0) + list(GET CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS 0 _path) + + get_filename_component(_path ${_path} DIRECTORY) + get_filename_component(_path ${_path}/../../ ABSOLUTE) + + if (EXISTS "${_path}/${REDIST_FILE}") # VS 2017 + set(VCREDISTRUNTIME_FOUND "YES") + set(VCREDISTRUNTIME_FILE ${_path}/${REDIST_FILE}) + endif() + endif() + + if(VCREDISTRUNTIME_FOUND) + message(STATUS "Found VCredist ${VCREDISTRUNTIME_FILE}") + else() + message(WARNING "Could not find VCredist package. It's not required for compiling, but needs to be available at runtime.") + endif() +endif() diff --git a/cmake/FindWin32SslRuntime.cmake b/cmake/FindWin32SslRuntime.cmake index 9cca2f7bc..37ffd0b0e 100644 --- a/cmake/FindWin32SslRuntime.cmake +++ b/cmake/FindWin32SslRuntime.cmake @@ -1,69 +1,71 @@ -# Find the OpenSSL runtime libraries (.dll) for Windows that -# will be needed by Qt in order to access https urls. - -if (WIN32) - # Get standard installation paths for OpenSSL under Windows - - # http://www.slproweb.com/products/Win32OpenSSL.html - - if( CMAKE_SIZEOF_VOID_P EQUAL 8 ) - # target win64 - set(_OPENSSL_ROOT_HINTS - ${OPENSSL_ROOT_DIR} - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" - ENV OPENSSL_ROOT_DIR - ) - file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) - set(_OPENSSL_ROOT_PATHS - "${_programfiles}/OpenSSL-Win64" - "C:/OpenSSL-Win64/" - ) - unset(_programfiles) - else( CMAKE_SIZEOF_VOID_P EQUAL 8 ) - # target win32 - set(_OPENSSL_ROOT_HINTS - ${OPENSSL_ROOT_DIR} - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" - ENV OPENSSL_ROOT_DIR - ) - file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) - set(_OPENSSL_ROOT_PATHS - "${_programfiles}/OpenSSL" - "${_programfiles}/OpenSSL-Win32" - "C:/OpenSSL/" - "C:/OpenSSL-Win32/" - ) - unset(_programfiles) - endif( CMAKE_SIZEOF_VOID_P EQUAL 8 ) - -else () - set(_OPENSSL_ROOT_HINTS - ${OPENSSL_ROOT_DIR} - ENV OPENSSL_ROOT_DIR - ) -endif () - -set(_OPENSSL_ROOT_HINTS_AND_PATHS - HINTS ${_OPENSSL_ROOT_HINTS} - PATHS ${_OPENSSL_ROOT_PATHS} - ) - -# For OpenSSL < 1.1, they are named libeay32 and ssleay32 and even if the dll is 64bit, it's still suffixed as *32.dll -# For OpenSSL >= 1.1, they are named libcrypto and libssl with no suffix -FIND_FILE(WIN32SSLRUNTIME_LIBEAY NAMES libeay32.dll libcrypto.dll ${_OPENSSL_ROOT_HINTS_AND_PATHS}) -FIND_FILE(WIN32SSLRUNTIME_SSLEAY NAMES ssleay32.dll libssl.dll ${_OPENSSL_ROOT_HINTS_AND_PATHS}) - - -IF(WIN32SSLRUNTIME_LIBEAY AND WIN32SSLRUNTIME_SSLEAY) - SET(WIN32SSLRUNTIME_LIBRARIES "${WIN32SSLRUNTIME_LIBEAY}" "${WIN32SSLRUNTIME_SSLEAY}") - SET(WIN32SSLRUNTIME_FOUND "YES") - message(STATUS "Found OpenSSL ${WIN32SSLRUNTIME_LIBRARIES}") -ELSE() - SET(WIN32SSLRUNTIME_FOUND "NO") - message(WARNING "Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime.") -ENDIF() - -MARK_AS_ADVANCED( - WIN32SSLRUNTIME_LIBEAY - WIN32SSLRUNTIME_SSLEAY - ) +# Find the OpenSSL runtime libraries (.dll) for Windows that +# will be needed by Qt in order to access https urls. + +if (WIN32) + # Get standard installation paths for OpenSSL under Windows + + # http://www.slproweb.com/products/Win32OpenSSL.html + + if( CMAKE_SIZEOF_VOID_P EQUAL 8 ) + # target win64 + set(_OPENSSL_ROOT_HINTS + ${OPENSSL_ROOT_DIR} + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" + ENV OPENSSL_ROOT_DIR + ) + file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) + set(_OPENSSL_ROOT_PATHS + "C:/Tools/vcpkg/installed/x64-windows/bin" + "${_programfiles}/OpenSSL-Win64" + "C:/OpenSSL-Win64/" + ) + unset(_programfiles) + else( CMAKE_SIZEOF_VOID_P EQUAL 8 ) + # target win32 + set(_OPENSSL_ROOT_HINTS + ${OPENSSL_ROOT_DIR} + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" + ENV OPENSSL_ROOT_DIR + ) + file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) + set(_OPENSSL_ROOT_PATHS + "C:/Tools/vcpkg/installed/x86-windows/bin" + "${_programfiles}/OpenSSL" + "${_programfiles}/OpenSSL-Win32" + "C:/OpenSSL/" + "C:/OpenSSL-Win32/" + ) + unset(_programfiles) + endif( CMAKE_SIZEOF_VOID_P EQUAL 8 ) + +else () + set(_OPENSSL_ROOT_HINTS + ${OPENSSL_ROOT_DIR} + ENV OPENSSL_ROOT_DIR + ) +endif () + +set(_OPENSSL_ROOT_HINTS_AND_PATHS + HINTS ${_OPENSSL_ROOT_HINTS} + PATHS ${_OPENSSL_ROOT_PATHS} + ) + +# For OpenSSL < 1.1, they are named libeay32 and ssleay32 and even if the dll is 64bit, it's still suffixed as *32.dll +# For OpenSSL >= 1.1, they are named libcrypto and libssl with no suffix +FIND_FILE(WIN32SSLRUNTIME_LIBEAY NAMES libeay32.dll libcrypto.dll ${_OPENSSL_ROOT_HINTS_AND_PATHS}) +FIND_FILE(WIN32SSLRUNTIME_SSLEAY NAMES ssleay32.dll libssl.dll ${_OPENSSL_ROOT_HINTS_AND_PATHS}) + + +IF(WIN32SSLRUNTIME_LIBEAY AND WIN32SSLRUNTIME_SSLEAY) + SET(WIN32SSLRUNTIME_LIBRARIES "${WIN32SSLRUNTIME_LIBEAY}" "${WIN32SSLRUNTIME_SSLEAY}") + SET(WIN32SSLRUNTIME_FOUND "YES") + message(STATUS "Found OpenSSL ${WIN32SSLRUNTIME_LIBRARIES}") +ELSE() + SET(WIN32SSLRUNTIME_FOUND "NO") + message(WARNING "Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime.") +ENDIF() + +MARK_AS_ADVANCED( + WIN32SSLRUNTIME_LIBEAY + WIN32SSLRUNTIME_SSLEAY + ) diff --git a/cmake/Info.plist b/cmake/Info.plist index eecb35bab..a4916941b 100644 --- a/cmake/Info.plist +++ b/cmake/Info.plist @@ -34,5 +34,7 @@ ${MACOSX_BUNDLE_COPYRIGHT} NSHighResolutionCapable + NSRequiresAquaSystemAppearance + - \ No newline at end of file + diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index 9373c6cd6..fdc015200 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -247,20 +247,20 @@ ${If} $PortableMode = 0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMajor" "@CPACK_PACKAGE_VERSION_MAJOR@" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMinor" "@CPACK_PACKAGE_VERSION_MINOR@" - IfFileExists "$INSTDIR\vc_redist.x86.exe" VcRedist86Exists PastVcRedist86Check + IfFileExists "$INSTDIR\vcredist_x86.exe" VcRedist86Exists PastVcRedist86Check VcRedist86Exists: - ExecWait '"$INSTDIR\vc_redist.x86.exe" /passive /norestart' + ExecWait '"$INSTDIR\vcredist_x86.exe" /passive /norestart' DetailPrint "Sleep to ensure unlock of vc_redist file after installation..." Sleep 3000 - Delete "$INSTDIR\vc_redist.x86.exe" + Delete "$INSTDIR\vcredist_x86.exe" PastVcRedist86Check: - IfFileExists "$INSTDIR\vc_redist.x64.exe" VcRedist64Exists PastVcRedist64Check + IfFileExists "$INSTDIR\vcredist_x64.exe" VcRedist64Exists PastVcRedist64Check VcRedist64Exists: - ExecWait '"$INSTDIR\vc_redist.x64.exe" /passive /norestart' + ExecWait '"$INSTDIR\vcredist_x64.exe" /passive /norestart' DetailPrint "Sleep to ensure unlock of vc_redist file after installation..." Sleep 3000 - Delete "$INSTDIR\vc_redist.x64.exe" + Delete "$INSTDIR\vcredist_x64.exe" PastVcRedist64Check: ${Else} diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 064e17701..8b4bd9215 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -100,7 +100,6 @@ SET(cockatrice_SOURCES src/localserver.cpp src/localserverinterface.cpp src/localclient.cpp - src/qt-json/json.cpp src/soundengine.cpp src/pending_command.cpp src/pictureloader.cpp @@ -114,15 +113,18 @@ SET(cockatrice_SOURCES src/settings/messagesettings.cpp src/settings/gamefilterssettings.cpp src/settings/layoutssettings.cpp + src/settings/downloadsettings.cpp src/update_downloader.cpp src/logger.cpp src/releasechannel.cpp src/userconnection_information.cpp src/spoilerbackgroundupdater.cpp src/handle_public_servers.cpp + src/carddbparser/carddatabaseparser.cpp src/carddbparser/cockatricexml3.cpp + src/carddbparser/cockatricexml4.cpp ${VERSION_STRING_CPP} -) + ) add_subdirectory(sounds) add_subdirectory(themes) @@ -149,8 +151,8 @@ if(APPLE) ENDIF(APPLE) # Qt5 -find_package(Qt5 COMPONENTS Concurrent Multimedia Network PrintSupport Svg Widgets REQUIRED) -set(COCKATRICE_QT_MODULES Qt5::Concurrent Qt5::Multimedia Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Widgets) +find_package(Qt5 COMPONENTS Concurrent Multimedia Network PrintSupport Svg WebSockets Widgets REQUIRED) +set(COCKATRICE_QT_MODULES Qt5::Concurrent Qt5::Multimedia Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Widgets Qt5::WebSockets) # Qt5LinguistTools find_package(Qt5LinguistTools) @@ -254,6 +256,8 @@ if(WIN32) set(plugin_dest_dir Plugins) set(qtconf_dest_dir .) + install(DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}/" DESTINATION ./ FILES_MATCHING PATTERN "*.dll") + # qt5 plugins: audio, iconengines, imageformats, platforms, printsupport install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime FILES_MATCHING REGEX "(audio|iconengines|imageformats|platforms|printsupport)/.*[^d]\\.dll") diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 3619cc0d5..aff2f002b 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -351,6 +351,7 @@ resources/tips/images/cockatrice_register.png resources/tips/images/cockatrice_wiki.png resources/tips/images/coin_flip.png + resources/tips/images/counter_expression.png resources/tips/images/face_down.png resources/tips/images/filter_games.png resources/tips/images/github_logo.png diff --git a/cockatrice/resources/tips/images/counter_expression.png b/cockatrice/resources/tips/images/counter_expression.png new file mode 100644 index 000000000..0e6bbcefe Binary files /dev/null and b/cockatrice/resources/tips/images/counter_expression.png differ diff --git a/cockatrice/resources/tips/tips_of_the_day.xml b/cockatrice/resources/tips/tips_of_the_day.xml index 5625fbe07..aa2cb463f 100644 --- a/cockatrice/resources/tips/tips_of_the_day.xml +++ b/cockatrice/resources/tips/tips_of_the_day.xml @@ -83,4 +83,10 @@ face_down.png 2018-03-01 + + Counter expressions + When setting a counter value, you can type a math expression in the box and the counter will be set to the result.<br>The "x" variable contains the current counter value. + counter_expression.png + 2019-02-02 + \ No newline at end of file diff --git a/cockatrice/src/abstractcounter.cpp b/cockatrice/src/abstractcounter.cpp index 64c19253a..d90951382 100644 --- a/cockatrice/src/abstractcounter.cpp +++ b/cockatrice/src/abstractcounter.cpp @@ -1,9 +1,11 @@ #include "abstractcounter.h" +#include "expression.h" #include "pb/command_inc_counter.pb.h" #include "pb/command_set_counter.pb.h" #include "player.h" #include "settingscache.h" #include +#include #include #include #include @@ -17,7 +19,7 @@ AbstractCounter::AbstractCounter(Player *_player, bool _useNameForShortcut, QGraphicsItem *parent) : QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value), - useNameForShortcut(_useNameForShortcut), hovered(false), aDec(0), aInc(0), dialogSemaphore(false), + useNameForShortcut(_useNameForShortcut), hovered(false), aDec(nullptr), aInc(nullptr), dialogSemaphore(false), deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea) { setAcceptHoverEvents(true); @@ -46,7 +48,7 @@ AbstractCounter::AbstractCounter(Player *_player, } else menu = nullptr; - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); retranslateUi(); } @@ -114,7 +116,11 @@ void AbstractCounter::setValue(int _value) void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (isUnderMouse() && player->getLocal()) { - if (event->button() == Qt::LeftButton) { + if (event->button() == Qt::MidButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { + if (menu) + menu->exec(event->screenPos()); + event->accept(); + } else if (event->button() == Qt::LeftButton) { Command_IncCounter cmd; cmd.set_counter_id(id); cmd.set_delta(1); @@ -126,10 +132,6 @@ void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) cmd.set_delta(-1); player->sendGameCommand(cmd); event->accept(); - } else if (event->button() == Qt::MidButton) { - if (menu) - menu->exec(event->screenPos()); - event->accept(); } } else event->ignore(); @@ -160,8 +162,12 @@ void AbstractCounter::setCounter() { bool ok; dialogSemaphore = true; - int newValue = QInputDialog::getInt(0, tr("Set counter"), tr("New value for counter '%1':").arg(name), value, - -2000000000, 2000000000, 1, &ok); + QString expression = QInputDialog::getText(nullptr, tr("Set counter"), tr("New value for counter '%1':").arg(name), + QLineEdit::Normal, QString::number(value), &ok); + + Expression exp(value); + int newValue = static_cast(exp.parse(expression)); + if (deleteAfterDialog) { deleteLater(); return; diff --git a/cockatrice/src/abstractcounter.h b/cockatrice/src/abstractcounter.h index 99bcd7ca2..2e592348c 100644 --- a/cockatrice/src/abstractcounter.h +++ b/cockatrice/src/abstractcounter.h @@ -11,6 +11,7 @@ class AbstractCounter : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES(QGraphicsItem) + protected: Player *player; int id; @@ -18,15 +19,17 @@ protected: int value; bool useNameForShortcut, hovered; - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void hoverEnterEvent(QGraphicsSceneHoverEvent *event); - void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; private: QAction *aSet, *aDec, *aInc; QMenu *menu; bool dialogSemaphore, deleteAfterDialog; bool shownInCounterArea; + bool shortcutActive; + private slots: void refreshShortcuts(); void incrementCounter(); @@ -39,14 +42,19 @@ public: bool _shownInCounterArea, int _value, bool _useNameForShortcut = false, - QGraphicsItem *parent = 0); - ~AbstractCounter(); + QGraphicsItem *parent = nullptr); + ~AbstractCounter() override; + + void retranslateUi(); + void setValue(int _value); + void setShortcutsActive(); + void setShortcutsInactive(); + void delCounter(); QMenu *getMenu() const { return menu; } - void retranslateUi(); int getId() const { @@ -64,12 +72,6 @@ public: { return value; } - void setValue(int _value); - void delCounter(); - - void setShortcutsActive(); - void setShortcutsInactive(); - bool shortcutActive; }; #endif diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp index 9b2137791..a75f150c4 100644 --- a/cockatrice/src/carddatabase.cpp +++ b/cockatrice/src/carddatabase.cpp @@ -1,5 +1,7 @@ #include "carddatabase.h" #include "carddbparser/cockatricexml3.h" +#include "carddbparser/cockatricexml4.h" +#include "game_specific_terms.h" #include "pictureloader.h" #include "settingscache.h" #include "spoilerbackgroundupdater.h" @@ -207,30 +209,23 @@ void SetList::guessSortKeys() } } +CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set) +{ +} + CardInfo::CardInfo(const QString &_name, - bool _isToken, - const QString &_manacost, - const QString &_cmc, - const QString &_cardtype, - const QString &_powtough, const QString &_text, - const QStringList &_colors, + bool _isToken, + QVariantHash _properties, const QList &_relatedCards, const QList &_reverseRelatedCards, - bool _upsideDownArt, - const QString &_loyalty, + CardInfoPerSetMap _sets, bool _cipt, int _tableRow, - const SetList &_sets, - const QStringMap &_customPicURLs, - MuidMap _muIds, - QStringMap _collectorNumbers, - QStringMap _rarities) - : name(_name), isToken(_isToken), sets(_sets), manacost(_manacost), cmc(_cmc), cardtype(_cardtype), - powtough(_powtough), text(_text), colors(_colors), relatedCards(_relatedCards), - reverseRelatedCards(_reverseRelatedCards), setsNames(), upsideDownArt(_upsideDownArt), loyalty(_loyalty), - customPicURLs(_customPicURLs), muIds(std::move(_muIds)), collectorNumbers(std::move(_collectorNumbers)), - rarities(std::move(_rarities)), cipt(_cipt), tableRow(_tableRow) + bool _upsideDownArt) + : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards), + reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), tableRow(_tableRow), + upsideDownArt(_upsideDownArt) { pixmapCacheKey = QLatin1String("card_") + name; simpleName = CardInfo::simplifyName(name); @@ -244,76 +239,27 @@ CardInfo::~CardInfo() } CardInfoPtr CardInfo::newInstance(const QString &_name, - bool _isToken, - const QString &_manacost, - const QString &_cmc, - const QString &_cardtype, - const QString &_powtough, const QString &_text, - const QStringList &_colors, + bool _isToken, + QVariantHash _properties, const QList &_relatedCards, const QList &_reverseRelatedCards, - bool _upsideDownArt, - const QString &_loyalty, + CardInfoPerSetMap _sets, bool _cipt, int _tableRow, - const SetList &_sets, - const QStringMap &_customPicURLs, - MuidMap _muIds, - QStringMap _collectorNumbers, - QStringMap _rarities) + bool _upsideDownArt) { - CardInfoPtr ptr(new CardInfo(_name, _isToken, _manacost, _cmc, _cardtype, _powtough, _text, _colors, _relatedCards, - _reverseRelatedCards, _upsideDownArt, _loyalty, _cipt, _tableRow, _sets, - _customPicURLs, std::move(_muIds), std::move(_collectorNumbers), - std::move(_rarities))); + CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards, + _sets, _cipt, _tableRow, _upsideDownArt)); ptr->setSmartPointer(ptr); - for (int i = 0; i < _sets.size(); i++) { - _sets[i]->append(ptr); + for (const CardInfoPerSet &set : _sets) { + set.getPtr()->append(ptr); } return ptr; } -QString CardInfo::getMainCardType() const -{ - QString result = getCardType(); - /* - Legendary Artifact Creature - Golem - Instant // Instant - */ - - int pos; - if ((pos = result.indexOf('-')) != -1) { - result.remove(pos, result.length()); - } - - if ((pos = result.indexOf("—")) != -1) { - result.remove(pos, result.length()); - } - - if ((pos = result.indexOf("//")) != -1) { - result.remove(pos, result.length()); - } - - result = result.simplified(); - /* - Legendary Artifact Creature - Instant - */ - - if ((pos = result.lastIndexOf(' ')) != -1) { - result = result.mid(pos + 1); - } - /* - Creature - Instant - */ - - return result; -} - QString CardInfo::getCorrectedName() const { QString result = name; @@ -321,26 +267,21 @@ QString CardInfo::getCorrectedName() const return result.remove(" // ").remove(':').remove('"').remove('?').replace('/', ' '); } -void CardInfo::addToSet(CardSetPtr set) +void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info) { - if (set.isNull()) { - qDebug() << "addToSet(nullptr)"; - return; - } - - set->append(smartThis); - sets << set; + _set->append(smartThis); + sets.insert(_set->getShortName(), _info); refreshCachedSetNames(); } void CardInfo::refreshCachedSetNames() { - // update the cached list of set names QStringList setList; - for (int i = 0; i < sets.size(); i++) { - if (sets[i]->getEnabled()) { - setList << sets[i]->getShortName(); + // update the cached list of set names + for (const auto &set : sets) { + if (set.getPtr()->getEnabled()) { + setList << set.getPtr()->getShortName(); } } setsNames = setList.join(", "); @@ -369,11 +310,12 @@ QString CardInfo::simplifyName(const QString &name) const QChar CardInfo::getColorChar() const { + QString colors = getColors(); switch (colors.size()) { case 0: return QChar(); case 1: - return colors.first().isEmpty() ? QChar() : colors.first().at(0); + return colors.at(0); default: return QChar('m'); } @@ -386,6 +328,7 @@ CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoa // add new parsers here availableParsers << new CockatriceXml3Parser; + availableParsers << new CockatriceXml4Parser; for (auto &parser : availableParsers) { connect(parser, SIGNAL(addCard(CardInfoPtr)), this, SLOT(addCard(CardInfoPtr)), Qt::DirectConnection); @@ -417,9 +360,7 @@ void CardDatabase::clear() simpleNameCards.clear(); sets.clear(); - for (auto parser : availableParsers) { - parser->clearSetlist(); - } + ICardDatabaseParser::clearSetlist(); loadStatus = NotLoaded; @@ -436,12 +377,8 @@ void CardDatabase::addCard(CardInfoPtr card) // if card already exists just add the new set property if (cards.contains(card->getName())) { CardInfoPtr sameCard = cards[card->getName()]; - for (auto set : card->getSets()) { - QString setName = set->getCorrectedShortName(); - sameCard->setSet(set); - sameCard->setMuId(setName, card->getMuId(setName)); - sameCard->setRarity(setName, card->getRarity(setName)); - sameCard->setSetNumber(setName, card->getCollectorNumber(setName)); + for (const CardInfoPerSet &set : card->getSets()) { + sameCard->addToSet(set.getPtr(), set); } return; } @@ -582,7 +519,7 @@ LoadStatus CardDatabase::loadCardDatabases() // load custom card databases QDir dir(settingsCache->getCustomCardDatabasePath()); - for (QString fileName : + for (const QString &fileName : dir.entryList(QStringList("*.xml"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase)) { loadCardDatabase(dir.absoluteFilePath(fileName)); } @@ -614,20 +551,13 @@ void CardDatabase::refreshCachedReverseRelatedCards() continue; } - QString relatedCardName; - if (card->getPowTough().size() > 0) { - relatedCardName = card->getPowTough() + " " + card->getName(); // "n/n name" - } else { - relatedCardName = card->getName(); // "name" - } - foreach (CardRelation *cardRelation, card->getReverseRelatedCards()) { const QString &targetCard = cardRelation->getName(); if (!cards.contains(targetCard)) { continue; } - auto *newCardRelation = new CardRelation(relatedCardName, cardRelation->getDoesAttach(), + auto *newCardRelation = new CardRelation(card->getName(), cardRelation->getDoesAttach(), cardRelation->getIsCreateAllExclusion(), cardRelation->getIsVariable(), cardRelation->getDefaultCount()); cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation); @@ -635,23 +565,6 @@ void CardDatabase::refreshCachedReverseRelatedCards() } } -QStringList CardDatabase::getAllColors() const -{ - QSet colors; - QHashIterator cardIterator(cards); - while (cardIterator.hasNext()) { - const QStringList &cardColors = cardIterator.next().value()->getColors(); - if (cardColors.isEmpty()) { - colors.insert("X"); - } else { - for (int i = 0; i < cardColors.size(); ++i) { - colors.insert(cardColors[i]); - } - } - } - return colors.toList(); -} - QStringList CardDatabase::getAllMainCardTypes() const { QSet types; @@ -717,8 +630,8 @@ bool CardDatabase::saveCustomTokensToFile() tmpSets.insert(CardDatabase::TOKENS_SETNAME, customTokensSet); CardNameMap tmpCards; - for (CardInfoPtr card : cards) { - if (card->getSets().contains(customTokensSet)) { + for (const CardInfoPtr &card : cards) { + if (card->getSets().contains(CardDatabase::TOKENS_SETNAME)) { tmpCards.insert(card->getName(), card); } } @@ -743,4 +656,46 @@ void CardInfo::resetReverseRelatedCards2Me() cardRelation->deleteLater(); } reverseRelatedCardsToMe = QList(); +} + +// Back-compatibility methods. Remove ASAP +const QString CardInfo::getCardType() const +{ + return getProperty(Mtg::CardType); +} +void CardInfo::setCardType(const QString &value) +{ + setProperty(Mtg::CardType, value); +} +const QString CardInfo::getCmc() const +{ + return getProperty(Mtg::ConvertedManaCost); +} +const QString CardInfo::getColors() const +{ + return getProperty(Mtg::Colors); +} +void CardInfo::setColors(const QString &value) +{ + setProperty(Mtg::Colors, value); +} +const QString CardInfo::getLoyalty() const +{ + return getProperty(Mtg::Loyalty); +} +const QString CardInfo::getMainCardType() const +{ + return getProperty(Mtg::MainCardType); +} +const QString CardInfo::getManaCost() const +{ + return getProperty(Mtg::ManaCost); +} +const QString CardInfo::getPowTough() const +{ + return getProperty(Mtg::PowTough); +} +void CardInfo::setPowTough(const QString &value) +{ + setProperty(Mtg::PowTough, value); } \ No newline at end of file diff --git a/cockatrice/src/carddatabase.h b/cockatrice/src/carddatabase.h index c58c1ad83..c5cf0ea23 100644 --- a/cockatrice/src/carddatabase.h +++ b/cockatrice/src/carddatabase.h @@ -9,10 +9,13 @@ #include #include #include +#include #include +#include class CardDatabase; class CardInfo; +class CardInfoPerSet; class CardSet; class CardRelation; class ICardDatabaseParser; @@ -21,6 +24,7 @@ typedef QMap QStringMap; typedef QMap MuidMap; typedef QSharedPointer CardInfoPtr; typedef QSharedPointer CardSetPtr; +typedef QMap CardInfoPerSetMap; Q_DECLARE_METATYPE(CardInfoPtr) @@ -112,175 +116,162 @@ public: QStringList getUnknownSetsNames(); }; +class CardInfoPerSet +{ +public: + explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer(nullptr)); + ~CardInfoPerSet() = default; + +private: + CardSetPtr set; + // per-set card properties; + QVariantHash properties; + +public: + const CardSetPtr getPtr() const + { + return set; + } + const QStringList getProperties() const + { + return properties.keys(); + } + const QString getProperty(const QString &propertyName) const + { + return properties.value(propertyName).toString(); + } + void setProperty(const QString &_name, const QString &_value) + { + properties.insert(_name, _value); + } +}; + class CardInfo : public QObject { Q_OBJECT private: CardInfoPtr smartThis; + // The card name QString name; - - /* - * The name without punctuation or capitalization, for better card tag name - * recognition. - */ + // The name without punctuation or capitalization, for better card name recognition. QString simpleName; - - bool isToken; - SetList sets; - QString manacost; - QString cmc; - QString cardtype; - QString powtough; + // The key used to identify this card in the cache + QString pixmapCacheKey; + // card text QString text; - QStringList colors; - + // whether this is not a "real" card but a token + bool isToken; + // basic card properties; common for all the sets + QVariantHash properties; // the cards i'm related to QList relatedCards; - // the card i'm reverse-related to QList reverseRelatedCards; - // the cards thare are reverse-related to me QList reverseRelatedCardsToMe; - + // card sets + CardInfoPerSetMap sets; + // cached set names QString setsNames; - - bool upsideDownArt; - QString loyalty; - QStringMap customPicURLs; - MuidMap muIds; - QStringMap collectorNumbers; - QStringMap rarities; + // positioning properties; used by UI bool cipt; int tableRow; - QString pixmapCacheKey; + bool upsideDownArt; public: explicit CardInfo(const QString &_name = QString(), - bool _isToken = false, - const QString &_manacost = QString(), - const QString &_cmc = QString(), - const QString &_cardtype = QString(), - const QString &_powtough = QString(), const QString &_text = QString(), - const QStringList &_colors = QStringList(), + bool _isToken = false, + QVariantHash _properties = QVariantHash(), const QList &_relatedCards = QList(), const QList &_reverseRelatedCards = QList(), - bool _upsideDownArt = false, - const QString &_loyalty = QString(), + CardInfoPerSetMap _sets = CardInfoPerSetMap(), bool _cipt = false, int _tableRow = 0, - const SetList &_sets = SetList(), - const QStringMap &_customPicURLs = QStringMap(), - MuidMap muids = MuidMap(), - QStringMap _collectorNumbers = QStringMap(), - QStringMap _rarities = QStringMap()); + bool _upsideDownArt = false); ~CardInfo() override; static CardInfoPtr newInstance(const QString &_name = QString(), - bool _isToken = false, - const QString &_manacost = QString(), - const QString &_cmc = QString(), - const QString &_cardtype = QString(), - const QString &_powtough = QString(), const QString &_text = QString(), - const QStringList &_colors = QStringList(), + bool _isToken = false, + QVariantHash _properties = QVariantHash(), const QList &_relatedCards = QList(), const QList &_reverseRelatedCards = QList(), - bool _upsideDownArt = false, - const QString &_loyalty = QString(), + CardInfoPerSetMap _sets = CardInfoPerSetMap(), bool _cipt = false, int _tableRow = 0, - const SetList &_sets = SetList(), - const QStringMap &_customPicURLs = QStringMap(), - MuidMap muids = MuidMap(), - QStringMap _collectorNumbers = QStringMap(), - QStringMap _rarities = QStringMap()); + bool _upsideDownArt = false); void setSmartPointer(CardInfoPtr _ptr) { - smartThis = _ptr; + smartThis = std::move(_ptr); } + // basic properties inline const QString &getName() const { return name; } - inline const QString &getSetsNames() const - { - return setsNames; - } const QString &getSimpleName() const { return simpleName; } - bool getIsToken() const - { - return isToken; - } - const SetList &getSets() const - { - return sets; - } - inline const QString &getManaCost() const - { - return manacost; - } - inline const QString &getCmc() const - { - return cmc; - } - inline const QString &getCardType() const - { - return cardtype; - } - inline const QString &getPowTough() const - { - return powtough; - } - const QString &getText() const - { - return text; - } const QString &getPixmapCacheKey() const { return pixmapCacheKey; } - const QString &getLoyalty() const + + const QString &getText() const { - return loyalty; - } - bool getCipt() const - { - return cipt; - } - // void setManaCost(const QString &_manaCost) { manacost = _manaCost; emit cardInfoChanged(smartThis); } - // void setCmc(const QString &_cmc) { cmc = _cmc; emit cardInfoChanged(smartThis); } - void setCardType(const QString &_cardType) - { - cardtype = _cardType; - emit cardInfoChanged(smartThis); - } - void setPowTough(const QString &_powTough) - { - powtough = _powTough; - emit cardInfoChanged(smartThis); + return text; } void setText(const QString &_text) { text = _text; emit cardInfoChanged(smartThis); } - void setColors(const QStringList &_colors) + + bool getIsToken() const { - colors = _colors; + return isToken; + } + const QStringList getProperties() const + { + return properties.keys(); + } + const QString getProperty(const QString &propertyName) const + { + return properties.value(propertyName).toString(); + } + void setProperty(const QString &_name, const QString &_value) + { + properties.insert(_name, _value); emit cardInfoChanged(smartThis); } - const QChar getColorChar() const; - const QStringList &getColors() const + const CardInfoPerSetMap &getSets() const { - return colors; + return sets; } + const QString &getSetsNames() const + { + return setsNames; + } + const QString getSetProperty(const QString &setName, const QString &propertyName) const + { + if (!sets.contains(setName)) + return ""; + return sets[setName].getProperty(propertyName); + } + void setSetProperty(const QString &setName, const QString &_name, const QString &_value) + { + if (!sets.contains(setName)) + return; + + sets[setName].setProperty(_name, _value); + emit cardInfoChanged(smartThis); + } + + // related cards const QList &getRelatedCards() const { return relatedCards; @@ -298,32 +289,12 @@ public: { reverseRelatedCardsToMe.append(cardRelation); } - bool getUpsideDownArt() const + + // positioning + bool getCipt() const { - return upsideDownArt; + return cipt; } - QString getCustomPicURL(const QString &set) const - { - return customPicURLs.value(set); - } - int getMuId(const QString &set) const - { - return muIds.value(set); - } - QString getCollectorNumber(const QString &set) const - { - return collectorNumbers.value(set); - } - QString getRarity(const QString &set) const - { - return rarities.value(set); - } - QStringMap getRarities() const - { - return rarities; - } - QString getMainCardType() const; - QString getCorrectedName() const; int getTableRow() const { return tableRow; @@ -332,27 +303,31 @@ public: { tableRow = _tableRow; } - // void setLoyalty(int _loyalty) { loyalty = _loyalty; emit cardInfoChanged(smartThis); } - // void setCustomPicURL(const QString &_set, const QString &_customPicURL) { customPicURLs.insert(_set, - // _customPicURL); } - void setSet(const CardSetPtr &_set) + bool getUpsideDownArt() const { - sets.append(_set); - refreshCachedSetNames(); + return upsideDownArt; } - void setMuId(const QString &_set, const int &_muId) + const QChar getColorChar() const; + + // Back-compatibility methods. Remove ASAP + const QString getCardType() const; + void setCardType(const QString &value); + const QString getCmc() const; + const QString getColors() const; + void setColors(const QString &value); + const QString getLoyalty() const; + const QString getMainCardType() const; + const QString getManaCost() const; + const QString getPowTough() const; + void setPowTough(const QString &value); + + // methods using per-set properties + QString getCustomPicURL(const QString &set) const { - muIds.insert(_set, _muId); + return getSetProperty(set, "picurl"); } - void setSetNumber(const QString &_set, const QString &_setNumber) - { - collectorNumbers.insert(_set, _setNumber); - } - void setRarity(const QString &_set, const QString &_setNumber) - { - rarities.insert(_set, _setNumber); - } - void addToSet(CardSetPtr set); + QString getCorrectedName() const; + void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet()); void emitPixmapUpdated() { emit pixmapUpdated(); @@ -439,7 +414,6 @@ public: SetList getSetList() const; LoadStatus loadFromFile(const QString &fileName); bool saveCustomTokensToFile(); - QStringList getAllColors() const; QStringList getAllMainCardTypes() const; LoadStatus getLoadStatus() const { @@ -506,4 +480,4 @@ public: return defaultCount; } }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/carddatabasemodel.cpp b/cockatrice/src/carddatabasemodel.cpp index faa8c3a85..9ae855786 100644 --- a/cockatrice/src/carddatabasemodel.cpp +++ b/cockatrice/src/carddatabasemodel.cpp @@ -14,9 +14,7 @@ CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromE cardDatabaseEnabledSetsChanged(); } -CardDatabaseModel::~CardDatabaseModel() -{ -} +CardDatabaseModel::~CardDatabaseModel() = default; QMap CardDatabaseDisplayModel::characterTranslation = {{L'“', L'\"'}, {L'”', L'\"'}, @@ -53,7 +51,7 @@ QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const case PTColumn: return card->getPowTough(); case ColorColumn: - return card->getColors().join(""); + return card->getColors(); default: return QVariant(); } @@ -97,8 +95,8 @@ bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card) if (!showOnlyCardsFromEnabledSets) return true; - for (CardSetPtr set : card->getSets()) { - if (set->getEnabled()) + for (const auto &set : card->getSets()) { + if (set.getPtr()->getEnabled()) return true; } diff --git a/cockatrice/src/carddatabasemodel.h b/cockatrice/src/carddatabasemodel.h index 5400c1f4a..8e8cb67c4 100644 --- a/cockatrice/src/carddatabasemodel.h +++ b/cockatrice/src/carddatabasemodel.h @@ -26,12 +26,12 @@ public: { SortRole = Qt::UserRole }; - CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = 0); - ~CardDatabaseModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr); + ~CardDatabaseModel() override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; CardDatabase *getDatabase() const { return db; @@ -77,7 +77,7 @@ private: static QMap characterTranslation; public: - CardDatabaseDisplayModel(QObject *parent = 0); + explicit CardDatabaseDisplayModel(QObject *parent = nullptr); void setFilterTree(FilterTree *filterTree); void setIsToken(FilterBool _isToken) { @@ -119,15 +119,15 @@ public: invalidate(); } void clearFilterAll(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; static int lessThanNumerically(const QString &left, const QString &right); - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool rowMatchesCardName(CardInfoPtr info) const; - bool canFetchMore(const QModelIndex &parent) const; - void fetchMore(const QModelIndex &parent); + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; private slots: void filterTreeChanged(); /** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */ @@ -138,11 +138,11 @@ class TokenDisplayModel : public CardDatabaseDisplayModel { Q_OBJECT public: - TokenDisplayModel(QObject *parent = 0); - int rowCount(const QModelIndex &parent = QModelIndex()) const; + explicit TokenDisplayModel(QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; #endif diff --git a/cockatrice/src/carddbparser/carddatabaseparser.cpp b/cockatrice/src/carddbparser/carddatabaseparser.cpp new file mode 100644 index 000000000..0048c9584 --- /dev/null +++ b/cockatrice/src/carddbparser/carddatabaseparser.cpp @@ -0,0 +1,27 @@ +#include "carddatabaseparser.h" + +SetNameMap ICardDatabaseParser::sets; + +void ICardDatabaseParser::clearSetlist() +{ + sets.clear(); +} + +CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName, + const QString &longName, + const QString &setType, + const QDate &releaseDate) +{ + if (sets.contains(setName)) { + return sets.value(setName); + } + + CardSetPtr newSet = CardSet::newInstance(setName); + newSet->setLongName(longName); + newSet->setSetType(setType); + newSet->setReleaseDate(releaseDate); + + sets.insert(setName, newSet); + emit addSet(newSet); + return newSet; +} \ No newline at end of file diff --git a/cockatrice/src/carddbparser/carddatabaseparser.h b/cockatrice/src/carddbparser/carddatabaseparser.h index d094b6d79..e8b46f95d 100644 --- a/cockatrice/src/carddbparser/carddatabaseparser.h +++ b/cockatrice/src/carddbparser/carddatabaseparser.h @@ -9,15 +9,27 @@ class ICardDatabaseParser : public QObject { public: - virtual ~ICardDatabaseParser() - { - } + ~ICardDatabaseParser() override = default; + virtual bool getCanParseFile(const QString &name, QIODevice &device) = 0; virtual void parseFile(QIODevice &device) = 0; virtual bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName) = 0; - virtual void clearSetlist() = 0; + static void clearSetlist(); + +protected: + /* + * A cached list of the available sets, needed to cross-reference sets from cards. + * Shared between all parsers + */ + static SetNameMap sets; + + CardSetPtr internalAddSet(const QString &setName, + const QString &longName = "", + const QString &setType = "", + const QDate &releaseDate = QDate()); signals: virtual void addCard(CardInfoPtr card) = 0; + virtual void addSet(CardSetPtr set) = 0; }; Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser") diff --git a/cockatrice/src/carddbparser/cockatricexml3.cpp b/cockatrice/src/carddbparser/cockatricexml3.cpp index d98202ac6..b63352772 100644 --- a/cockatrice/src/carddbparser/cockatricexml3.cpp +++ b/cockatrice/src/carddbparser/cockatricexml3.cpp @@ -61,30 +61,6 @@ void CockatriceXml3Parser::parseFile(QIODevice &device) } } -CardSetPtr CockatriceXml3Parser::internalAddSet(const QString &setName, - const QString &longName, - const QString &setType, - const QDate &releaseDate) -{ - if (sets.contains(setName)) { - return sets.value(setName); - } - - CardSetPtr newSet = CardSet::newInstance(setName); - newSet->setLongName(longName); - newSet->setSetType(setType); - newSet->setReleaseDate(releaseDate); - - sets.insert(setName, newSet); - emit addSet(newSet); - return newSet; -} - -void CockatriceXml3Parser::clearSetlist() -{ - sets.clear(); -} - void CockatriceXml3Parser::loadSetsFromXml(QXmlStreamReader &xml) { while (!xml.atEnd()) { @@ -120,6 +96,44 @@ void CockatriceXml3Parser::loadSetsFromXml(QXmlStreamReader &xml) } } +QString CockatriceXml3Parser::getMainCardType(QString &type) +{ + QString result = type; + /* + Legendary Artifact Creature - Golem + Instant // Instant + */ + + int pos; + if ((pos = result.indexOf('-')) != -1) { + result.remove(pos, result.length()); + } + + if ((pos = result.indexOf("—")) != -1) { + result.remove(pos, result.length()); + } + + if ((pos = result.indexOf("//")) != -1) { + result.remove(pos, result.length()); + } + + result = result.simplified(); + /* + Legendary Artifact Creature + Instant + */ + + if ((pos = result.lastIndexOf(' ')) != -1) { + result = result.mid(pos + 1); + } + /* + Creature + Instant + */ + + return result; +} + void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) { while (!xml.atEnd()) { @@ -128,55 +142,77 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) } if (xml.name() == "card") { - QString name, manacost, cmc, type, pt, text, loyalty; - QStringList colors; + QString name = QString(""); + QString text = QString(""); + QVariantHash properties = QVariantHash(); + QString colors = QString(""); QList relatedCards, reverseRelatedCards; - QStringMap customPicURLs; - MuidMap muids; - QStringMap collectorNumbers, rarities; - SetList sets; + CardInfoPerSetMap sets = CardInfoPerSetMap(); int tableRow = 0; bool cipt = false; bool isToken = false; bool upsideDown = false; + while (!xml.atEnd()) { if (xml.readNext() == QXmlStreamReader::EndElement) { break; } - + // variable - assigned properties if (xml.name() == "name") { name = xml.readElementText(); - } else if (xml.name() == "manacost") { - manacost = xml.readElementText(); - } else if (xml.name() == "cmc") { - cmc = xml.readElementText(); - } else if (xml.name() == "type") { - type = xml.readElementText(); - } else if (xml.name() == "pt") { - pt = xml.readElementText(); } else if (xml.name() == "text") { text = xml.readElementText(); + } else if (xml.name() == "color") { + colors.append(xml.readElementText()); + } else if (xml.name() == "token") { + isToken = static_cast(xml.readElementText().toInt()); + // generic properties + } else if (xml.name() == "manacost") { + properties.insert("manacost", xml.readElementText()); + } else if (xml.name() == "cmc") { + properties.insert("cmc", xml.readElementText()); + } else if (xml.name() == "type") { + QString type = xml.readElementText(); + properties.insert("type", type); + properties.insert("maintype", getMainCardType(type)); + } else if (xml.name() == "pt") { + properties.insert("pt", xml.readElementText()); + } else if (xml.name() == "loyalty") { + properties.insert("loyalty", xml.readElementText()); + // positioning info + } else if (xml.name() == "tablerow") { + tableRow = xml.readElementText().toInt(); + } else if (xml.name() == "cipt") { + cipt = (xml.readElementText() == "1"); + } else if (xml.name() == "upsidedown") { + upsideDown = (xml.readElementText() == "1"); + // sets } else if (xml.name() == "set") { + // NOTE: attributes must be read before readElementText() QXmlStreamAttributes attrs = xml.attributes(); QString setName = xml.readElementText(); - sets.append(internalAddSet(setName)); + CardInfoPerSet setInfo(internalAddSet(setName)); if (attrs.hasAttribute("muId")) { - muids[setName] = attrs.value("muId").toString().toInt(); + setInfo.setProperty("muid", attrs.value("muId").toString()); + } + + if (attrs.hasAttribute("muId")) { + setInfo.setProperty("uuid", attrs.value("uuId").toString()); } if (attrs.hasAttribute("picURL")) { - customPicURLs[setName] = attrs.value("picURL").toString(); + setInfo.setProperty("picurl", attrs.value("picURL").toString()); } if (attrs.hasAttribute("num")) { - collectorNumbers[setName] = attrs.value("num").toString(); + setInfo.setProperty("num", attrs.value("num").toString()); } if (attrs.hasAttribute("rarity")) { - rarities[setName] = attrs.value("rarity").toString(); + setInfo.setProperty("rarity", attrs.value("rarity").toString()); } - } else if (xml.name() == "color") { - colors << xml.readElementText(); + sets.insert(setName, setInfo); + // relatd cards } else if (xml.name() == "related" || xml.name() == "reverse-related") { bool attach = false; bool exclude = false; @@ -213,16 +249,6 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) } else { relatedCards << relation; } - } else if (xml.name() == "tablerow") { - tableRow = xml.readElementText().toInt(); - } else if (xml.name() == "cipt") { - cipt = (xml.readElementText() == "1"); - } else if (xml.name() == "upsidedown") { - upsideDown = (xml.readElementText() == "1"); - } else if (xml.name() == "loyalty") { - loyalty = xml.readElementText(); - } else if (xml.name() == "token") { - isToken = static_cast(xml.readElementText().toInt()); } else if (xml.name() != "") { qDebug() << "[CockatriceXml3Parser] Unknown card property" << xml.name() << ", trying to continue anyway"; @@ -230,9 +256,9 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) } } - CardInfoPtr newCard = CardInfo::newInstance( - name, isToken, manacost, cmc, type, pt, text, colors, relatedCards, reverseRelatedCards, upsideDown, - loyalty, cipt, tableRow, sets, customPicURLs, muids, collectorNumbers, rarities); + properties.insert("colors", colors); + CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, + reverseRelatedCards, sets, cipt, tableRow, upsideDown); emit addCard(newCard); } } @@ -262,37 +288,60 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in return xml; } - xml.writeStartElement("card"); - xml.writeTextElement("name", info->getName()); - - const SetList &sets = info->getSets(); QString tmpString; - QString tmpSet; - for (int i = 0; i < sets.size(); i++) { + + xml.writeStartElement("card"); + + // variable - assigned properties + xml.writeTextElement("name", info->getName()); + xml.writeTextElement("text", info->getText()); + if (info->getIsToken()) { + xml.writeTextElement("token", "1"); + } + + // generic properties + xml.writeTextElement("manacost", info->getProperty("manacost")); + xml.writeTextElement("cmc", info->getProperty("cmc")); + xml.writeTextElement("type", info->getProperty("type")); + + int colorSize = info->getColors().size(); + for (int i = 0; i < colorSize; ++i) { + xml.writeTextElement("color", info->getColors().at(i)); + } + + tmpString = info->getProperty("pt"); + if (!tmpString.isEmpty()) { + xml.writeTextElement("pt", tmpString); + } + + tmpString = info->getProperty("loyalty"); + if (!tmpString.isEmpty()) { + xml.writeTextElement("loyalty", tmpString); + } + + // sets + const CardInfoPerSetMap sets = info->getSets(); + for (CardInfoPerSet set : sets) { xml.writeStartElement("set"); + xml.writeAttribute("rarity", set.getProperty("rarity")); + xml.writeAttribute("muId", set.getProperty("muid")); + xml.writeAttribute("uuId", set.getProperty("uuid")); - tmpSet = sets[i]->getShortName(); - xml.writeAttribute("rarity", info->getRarity(tmpSet)); - xml.writeAttribute("muId", QString::number(info->getMuId(tmpSet))); - - tmpString = info->getCollectorNumber(tmpSet); + tmpString = set.getProperty("num"); if (!tmpString.isEmpty()) { - xml.writeAttribute("num", info->getCollectorNumber(tmpSet)); + xml.writeAttribute("num", tmpString); } - tmpString = info->getCustomPicURL(tmpSet); + tmpString = set.getProperty("picurl"); if (!tmpString.isEmpty()) { xml.writeAttribute("picURL", tmpString); } - xml.writeCharacters(tmpSet); + xml.writeCharacters(set.getPtr()->getShortName()); xml.writeEndElement(); } - const QStringList &colors = info->getColors(); - for (int i = 0; i < colors.size(); i++) { - xml.writeTextElement("color", colors[i]); - } + // related cards const QList related = info->getRelatedCards(); for (auto i : related) { xml.writeStartElement("related"); @@ -338,23 +387,12 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in xml.writeCharacters(i->getName()); xml.writeEndElement(); } - xml.writeTextElement("manacost", info->getManaCost()); - xml.writeTextElement("cmc", info->getCmc()); - xml.writeTextElement("type", info->getCardType()); - if (!info->getPowTough().isEmpty()) { - xml.writeTextElement("pt", info->getPowTough()); - } + + // positioning xml.writeTextElement("tablerow", QString::number(info->getTableRow())); - xml.writeTextElement("text", info->getText()); - if (info->getMainCardType() == "Planeswalker") { - xml.writeTextElement("loyalty", info->getLoyalty()); - } if (info->getCipt()) { xml.writeTextElement("cipt", "1"); } - if (info->getIsToken()) { - xml.writeTextElement("token", "1"); - } if (info->getUpsideDownArt()) { xml.writeTextElement("upsidedown", "1"); } diff --git a/cockatrice/src/carddbparser/cockatricexml3.h b/cockatrice/src/carddbparser/cockatricexml3.h index 286ee3af1..109832cf4 100644 --- a/cockatrice/src/carddbparser/cockatricexml3.h +++ b/cockatrice/src/carddbparser/cockatricexml3.h @@ -11,27 +11,18 @@ class CockatriceXml3Parser : public ICardDatabaseParser Q_INTERFACES(ICardDatabaseParser) public: CockatriceXml3Parser() = default; - ~CockatriceXml3Parser() = default; - bool getCanParseFile(const QString &name, QIODevice &device); - void parseFile(QIODevice &device); - bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName); - void clearSetlist(); + ~CockatriceXml3Parser() override = default; + bool getCanParseFile(const QString &name, QIODevice &device) override; + void parseFile(QIODevice &device) override; + bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName) override; private: - /* - * A cached list of the available sets, needed to cross-reference sets from cards. - */ - SetNameMap sets; - - CardSetPtr internalAddSet(const QString &setName, - const QString &longName = "", - const QString &setType = "", - const QDate &releaseDate = QDate()); void loadCardsFromXml(QXmlStreamReader &xml); void loadSetsFromXml(QXmlStreamReader &xml); + QString getMainCardType(QString &type); signals: - void addCard(CardInfoPtr card); - void addSet(CardSetPtr set); + void addCard(CardInfoPtr card) override; + void addSet(CardSetPtr set) override; }; #endif \ No newline at end of file diff --git a/cockatrice/src/carddbparser/cockatricexml4.cpp b/cockatrice/src/carddbparser/cockatricexml4.cpp new file mode 100644 index 000000000..a3b2adf00 --- /dev/null +++ b/cockatrice/src/carddbparser/cockatricexml4.cpp @@ -0,0 +1,362 @@ +#include "cockatricexml4.h" + +#include +#include +#include + +#define COCKATRICE_XML4_TAGNAME "cockatrice_carddatabase" +#define COCKATRICE_XML4_TAGVER 4 + +bool CockatriceXml4Parser::getCanParseFile(const QString &fileName, QIODevice &device) +{ + qDebug() << "[CockatriceXml4Parser] Trying to parse: " << fileName; + + if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) { + qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong extension"; + return false; + } + + QXmlStreamReader xml(&device); + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::StartElement) { + if (xml.name() == COCKATRICE_XML4_TAGNAME) { + int version = xml.attributes().value("version").toString().toInt(); + if (version == COCKATRICE_XML4_TAGVER) { + return true; + } else { + qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong version" << version; + return false; + } + + } else { + qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong element tag" << xml.name(); + return false; + } + } + } + + return true; +} + +void CockatriceXml4Parser::parseFile(QIODevice &device) +{ + QXmlStreamReader xml(&device); + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::StartElement) { + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) { + break; + } + + if (xml.name() == "sets") { + loadSetsFromXml(xml); + } else if (xml.name() == "cards") { + loadCardsFromXml(xml); + } else if (xml.name() != "") { + qDebug() << "[CockatriceXml4Parser] Unknown item" << xml.name() << ", trying to continue anyway"; + xml.skipCurrentElement(); + } + } + } + } +} + +void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml) +{ + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) { + break; + } + + if (xml.name() == "set") { + QString shortName, longName, setType; + QDate releaseDate; + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) { + break; + } + + if (xml.name() == "name") { + shortName = xml.readElementText(); + } else if (xml.name() == "longname") { + longName = xml.readElementText(); + } else if (xml.name() == "settype") { + setType = xml.readElementText(); + } else if (xml.name() == "releasedate") { + releaseDate = QDate::fromString(xml.readElementText(), Qt::ISODate); + } else if (xml.name() != "") { + qDebug() << "[CockatriceXml4Parser] Unknown set property" << xml.name() + << ", trying to continue anyway"; + xml.skipCurrentElement(); + } + } + + internalAddSet(shortName, longName, setType, releaseDate); + } + } +} + +QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &xml) +{ + QVariantHash properties = QVariantHash(); + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) { + break; + } + + if (xml.name() != "") { + properties.insert(xml.name().toString(), xml.readElementText()); + } + } + return properties; +} + +void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) +{ + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) { + break; + } + + if (xml.name() == "card") { + QString name = QString(""); + QString text = QString(""); + QVariantHash properties = QVariantHash(); + QList relatedCards, reverseRelatedCards; + CardInfoPerSetMap sets = CardInfoPerSetMap(); + int tableRow = 0; + bool cipt = false; + bool isToken = false; + bool upsideDown = false; + + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) { + break; + } + // variable - assigned properties + if (xml.name() == "name") { + name = xml.readElementText(); + } else if (xml.name() == "text") { + text = xml.readElementText(); + } else if (xml.name() == "token") { + isToken = static_cast(xml.readElementText().toInt()); + // generic properties + } else if (xml.name() == "prop") { + properties = loadCardPropertiesFromXml(xml); + // positioning info + } else if (xml.name() == "tablerow") { + tableRow = xml.readElementText().toInt(); + } else if (xml.name() == "cipt") { + cipt = (xml.readElementText() == "1"); + } else if (xml.name() == "upsidedown") { + upsideDown = (xml.readElementText() == "1"); + // sets + } else if (xml.name() == "set") { + // NOTE: attributes but be read before readElementText() + QXmlStreamAttributes attrs = xml.attributes(); + QString setName = xml.readElementText(); + CardInfoPerSet setInfo(internalAddSet(setName)); + for (QXmlStreamAttribute attr : attrs) { + setInfo.setProperty(attr.name().toString(), attr.value().toString()); + } + sets.insert(setName, setInfo); + // relatd cards + } else if (xml.name() == "related" || xml.name() == "reverse-related") { + bool attach = false; + bool exclude = false; + bool variable = false; + int count = 1; + QXmlStreamAttributes attrs = xml.attributes(); + QString cardName = xml.readElementText(); + if (attrs.hasAttribute("count")) { + if (attrs.value("count").toString().indexOf("x=") == 0) { + variable = true; + count = attrs.value("count").toString().remove(0, 2).toInt(); + } else if (attrs.value("count").toString().indexOf("x") == 0) { + variable = true; + } else { + count = attrs.value("count").toString().toInt(); + } + + if (count < 1) { + count = 1; + } + } + + if (attrs.hasAttribute("attach")) { + attach = true; + } + + if (attrs.hasAttribute("exclude")) { + exclude = true; + } + + auto *relation = new CardRelation(cardName, attach, exclude, variable, count); + if (xml.name() == "reverse-related") { + reverseRelatedCards << relation; + } else { + relatedCards << relation; + } + } else if (xml.name() != "") { + qDebug() << "[CockatriceXml4Parser] Unknown card property" << xml.name() + << ", trying to continue anyway"; + xml.skipCurrentElement(); + } + } + + CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, + reverseRelatedCards, sets, cipt, tableRow, upsideDown); + emit addCard(newCard); + } + } +} + +static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set) +{ + if (set.isNull()) { + qDebug() << "&operator<< set is nullptr"; + return xml; + } + + xml.writeStartElement("set"); + xml.writeTextElement("name", set->getShortName()); + xml.writeTextElement("longname", set->getLongName()); + xml.writeTextElement("settype", set->getSetType()); + xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate)); + xml.writeEndElement(); + + return xml; +} + +static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info) +{ + if (info.isNull()) { + qDebug() << "operator<< info is nullptr"; + return xml; + } + + QString tmpString; + + xml.writeStartElement("card"); + + // variable - assigned properties + xml.writeTextElement("name", info->getName()); + xml.writeTextElement("text", info->getText()); + if (info->getIsToken()) { + xml.writeTextElement("token", "1"); + } + + // generic properties + xml.writeStartElement("prop"); + for (QString propName : info->getProperties()) { + xml.writeTextElement(propName, info->getProperty(propName)); + } + xml.writeEndElement(); + + // sets + for (CardInfoPerSet set : info->getSets()) { + xml.writeStartElement("set"); + for (QString propName : set.getProperties()) { + xml.writeAttribute(propName, set.getProperty(propName)); + } + + xml.writeCharacters(set.getPtr()->getShortName()); + xml.writeEndElement(); + } + + // related cards + const QList related = info->getRelatedCards(); + for (auto i : related) { + xml.writeStartElement("related"); + if (i->getDoesAttach()) { + xml.writeAttribute("attach", "attach"); + } + if (i->getIsCreateAllExclusion()) { + xml.writeAttribute("exclude", "exclude"); + } + + if (i->getIsVariable()) { + if (1 == i->getDefaultCount()) { + xml.writeAttribute("count", "x"); + } else { + xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount())); + } + } else if (1 != i->getDefaultCount()) { + xml.writeAttribute("count", QString::number(i->getDefaultCount())); + } + xml.writeCharacters(i->getName()); + xml.writeEndElement(); + } + const QList reverseRelated = info->getReverseRelatedCards(); + for (auto i : reverseRelated) { + xml.writeStartElement("reverse-related"); + if (i->getDoesAttach()) { + xml.writeAttribute("attach", "attach"); + } + + if (i->getIsCreateAllExclusion()) { + xml.writeAttribute("exclude", "exclude"); + } + + if (i->getIsVariable()) { + if (1 == i->getDefaultCount()) { + xml.writeAttribute("count", "x"); + } else { + xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount())); + } + } else if (1 != i->getDefaultCount()) { + xml.writeAttribute("count", QString::number(i->getDefaultCount())); + } + xml.writeCharacters(i->getName()); + xml.writeEndElement(); + } + + // positioning + xml.writeTextElement("tablerow", QString::number(info->getTableRow())); + if (info->getCipt()) { + xml.writeTextElement("cipt", "1"); + } + if (info->getUpsideDownArt()) { + xml.writeTextElement("upsidedown", "1"); + } + + xml.writeEndElement(); // card + + return xml; +} + +bool CockatriceXml4Parser::saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + return false; + } + + QXmlStreamWriter xml(&file); + + xml.setAutoFormatting(true); + xml.writeStartDocument(); + xml.writeStartElement(COCKATRICE_XML4_TAGNAME); + xml.writeAttribute("version", QString::number(COCKATRICE_XML4_TAGVER)); + + if (sets.count() > 0) { + xml.writeStartElement("sets"); + for (CardSetPtr set : sets) { + xml << set; + } + xml.writeEndElement(); + } + + if (cards.count() > 0) { + xml.writeStartElement("cards"); + for (CardInfoPtr card : cards) { + xml << card; + } + xml.writeEndElement(); + } + + xml.writeEndElement(); // cockatrice_carddatabase + xml.writeEndDocument(); + + return true; +} \ No newline at end of file diff --git a/cockatrice/src/carddbparser/cockatricexml4.h b/cockatrice/src/carddbparser/cockatricexml4.h new file mode 100644 index 000000000..cc37c4f37 --- /dev/null +++ b/cockatrice/src/carddbparser/cockatricexml4.h @@ -0,0 +1,28 @@ +#ifndef COCKATRICE_XML4_H +#define COCKATRICE_XML4_H + +#include + +#include "carddatabaseparser.h" + +class CockatriceXml4Parser : public ICardDatabaseParser +{ + Q_OBJECT + Q_INTERFACES(ICardDatabaseParser) +public: + CockatriceXml4Parser() = default; + ~CockatriceXml4Parser() override = default; + bool getCanParseFile(const QString &name, QIODevice &device) override; + void parseFile(QIODevice &device) override; + bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName) override; + +private: + QVariantHash loadCardPropertiesFromXml(QXmlStreamReader &xml); + void loadCardsFromXml(QXmlStreamReader &xml); + void loadSetsFromXml(QXmlStreamReader &xml); +signals: + void addCard(CardInfoPtr card) override; + void addSet(CardSetPtr set) override; +}; + +#endif \ No newline at end of file diff --git a/cockatrice/src/cardfilter.cpp b/cockatrice/src/cardfilter.cpp index 0ccb1086a..ce72d7946 100644 --- a/cockatrice/src/cardfilter.cpp +++ b/cockatrice/src/cardfilter.cpp @@ -1,47 +1,47 @@ #include "cardfilter.h" -const char *CardFilter::typeName(Type t) +const QString CardFilter::typeName(Type t) { switch (t) { case TypeAnd: - return "AND"; + return tr("AND", "Logical conjunction operator used in card filter"); case TypeOr: - return "OR"; + return tr("OR", "Logical disjunction operator used in card filter"); case TypeAndNot: - return "AND NOT"; + return tr("AND NOT", "Negated logical conjunction operator used in card filter"); case TypeOrNot: - return "OR NOT"; + return tr("OR NOT", "Negated logical disjunction operator used in card filter"); default: - return ""; + return QString(""); } } -const char *CardFilter::attrName(Attr a) +const QString CardFilter::attrName(Attr a) { switch (a) { case AttrName: - return "Name"; + return tr("Name"); case AttrType: - return "Type"; + return tr("Type"); case AttrColor: - return "Color"; + return tr("Color"); case AttrText: - return "Text"; + return tr("Text"); case AttrSet: - return "Set"; + return tr("Set"); case AttrManaCost: - return "Mana Cost"; + return tr("Mana Cost"); case AttrCmc: - return "CMC"; + return tr("CMC"); case AttrRarity: - return "Rarity"; + return tr("Rarity"); case AttrPow: - return "Power"; + return tr("Power"); case AttrTough: - return "Toughness"; + return tr("Toughness"); case AttrLoyalty: - return "Loyalty"; + return tr("Loyalty"); default: - return ""; + return QString(""); } } diff --git a/cockatrice/src/cardfilter.h b/cockatrice/src/cardfilter.h index b9b18b920..514ebd3e1 100644 --- a/cockatrice/src/cardfilter.h +++ b/cockatrice/src/cardfilter.h @@ -1,10 +1,13 @@ #ifndef CARDFILTER_H #define CARDFILTER_H +#include #include -class CardFilter +class CardFilter : public QObject { + Q_OBJECT + public: enum Type { @@ -54,8 +57,8 @@ public: return a; } - static const char *typeName(Type t); - static const char *attrName(Attr a); + static const QString typeName(Type t); + static const QString attrName(Attr a); }; #endif diff --git a/cockatrice/src/cardframe.cpp b/cockatrice/src/cardframe.cpp index eecbc3286..fd1d08730 100644 --- a/cockatrice/src/cardframe.cpp +++ b/cockatrice/src/cardframe.cpp @@ -1,3 +1,5 @@ +#include + #include "cardframe.h" #include "cardinfopicture.h" @@ -16,6 +18,7 @@ CardFrame::CardFrame(const QString &cardName, QWidget *parent) : QTabWidget(pare pic->setObjectName("pic"); text = new CardInfoText(); text->setObjectName("text"); + connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &))); tab1 = new QWidget(this); tab2 = new QWidget(this); @@ -93,10 +96,10 @@ void CardFrame::setCard(CardInfoPtr card) disconnect(info.data(), nullptr, this, nullptr); } - info = card; + info = std::move(card); if (info) { - connect(info.data(), SIGNAL(destroyed()), this, SLOT(clear())); + connect(info.data(), SIGNAL(destroyed()), this, SLOT(clearCard())); } text->setCard(info); @@ -115,7 +118,7 @@ void CardFrame::setCard(AbstractCardItem *card) } } -void CardFrame::clear() +void CardFrame::clearCard() { setCard((CardInfoPtr) nullptr); } diff --git a/cockatrice/src/cardframe.h b/cockatrice/src/cardframe.h index cf8b2ad00..5023fd70c 100644 --- a/cockatrice/src/cardframe.h +++ b/cockatrice/src/cardframe.h @@ -37,7 +37,7 @@ public slots: void setCard(CardInfoPtr card); void setCard(const QString &cardName); void setCard(AbstractCardItem *card); - void clear(); + void clearCard(); void setViewMode(int mode); }; diff --git a/cockatrice/src/cardinfotext.cpp b/cockatrice/src/cardinfotext.cpp index 9f3157513..448b276b1 100644 --- a/cockatrice/src/cardinfotext.cpp +++ b/cockatrice/src/cardinfotext.cpp @@ -1,137 +1,83 @@ #include "cardinfotext.h" - #include "carditem.h" +#include "game_specific_terms.h" #include "main.h" + #include #include #include CardInfoText::CardInfoText(QWidget *parent) : QFrame(parent), info(nullptr) { - nameLabel1 = new QLabel; - nameLabel2 = new QLabel; - nameLabel2->setWordWrap(true); - nameLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction); - manacostLabel1 = new QLabel; - manacostLabel2 = new QLabel; - manacostLabel2->setWordWrap(true); - manacostLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction); - colorLabel1 = new QLabel; - colorLabel2 = new QLabel; - colorLabel2->setWordWrap(true); - colorLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction); - cardtypeLabel1 = new QLabel; - cardtypeLabel2 = new QLabel; - cardtypeLabel2->setWordWrap(true); - cardtypeLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction); - powtoughLabel1 = new QLabel; - powtoughLabel2 = new QLabel; - powtoughLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction); - loyaltyLabel1 = new QLabel; - loyaltyLabel2 = new QLabel; - loyaltyLabel1->setTextInteractionFlags(Qt::TextBrowserInteraction); + nameLabel = new QLabel; + nameLabel->setOpenExternalLinks(false); + connect(nameLabel, SIGNAL(linkActivated(const QString &)), this, SIGNAL(linkActivated(const QString &))); textLabel = new QTextEdit(); textLabel->setReadOnly(true); QGridLayout *grid = new QGridLayout(this); - int row = 0; - grid->addWidget(nameLabel1, row, 0); - grid->addWidget(nameLabel2, row++, 1); - grid->addWidget(manacostLabel1, row, 0); - grid->addWidget(manacostLabel2, row++, 1); - grid->addWidget(colorLabel1, row, 0); - grid->addWidget(colorLabel2, row++, 1); - grid->addWidget(cardtypeLabel1, row, 0); - grid->addWidget(cardtypeLabel2, row++, 1); - grid->addWidget(powtoughLabel1, row, 0); - grid->addWidget(powtoughLabel2, row++, 1); - grid->addWidget(loyaltyLabel1, row, 0); - grid->addWidget(loyaltyLabel2, row++, 1); - grid->addWidget(textLabel, row, 0, -1, 2); - grid->setRowStretch(row, 1); + grid->addWidget(nameLabel, 0, 0); + grid->addWidget(textLabel, 1, 0, -1, 2); + grid->setRowStretch(1, 1); grid->setColumnStretch(1, 1); retranslateUi(); } -// Reset every label which is optionally hidden -void CardInfoText::resetLabels() -{ - nameLabel1->show(); - nameLabel2->show(); - manacostLabel1->show(); - manacostLabel2->show(); - colorLabel1->show(); - colorLabel2->show(); - cardtypeLabel1->show(); - cardtypeLabel2->show(); - powtoughLabel1->show(); - powtoughLabel2->show(); - loyaltyLabel1->show(); - loyaltyLabel2->show(); - textLabel->show(); -} + void CardInfoText::setCard(CardInfoPtr card) { - if (card) { - resetLabels(); - nameLabel2->setText(card->getName()); - if (!card->getManaCost().isEmpty()) { - manacostLabel2->setText(card->getManaCost()); - } else { - manacostLabel1->hide(); - manacostLabel2->hide(); - } - if (!card->getColors().isEmpty()) { - colorLabel2->setText(card->getColors().join("")); - } else { - colorLabel2->setText("Colorless"); - } - cardtypeLabel2->setText(card->getCardType()); - if (!card->getPowTough().isEmpty()) { - powtoughLabel2->setText(card->getPowTough()); - } else { - powtoughLabel1->hide(); - powtoughLabel2->hide(); - } - if (!card->getLoyalty().isEmpty()) { - loyaltyLabel2->setText(card->getLoyalty()); - } else { - loyaltyLabel1->hide(); - loyaltyLabel2->hide(); - } - textLabel->setText(card->getText()); - } else { - nameLabel1->hide(); - nameLabel2->hide(); - manacostLabel1->hide(); - manacostLabel2->hide(); - colorLabel1->hide(); - colorLabel2->hide(); - cardtypeLabel1->hide(); - cardtypeLabel2->hide(); - powtoughLabel1->hide(); - powtoughLabel2->hide(); - loyaltyLabel1->hide(); - loyaltyLabel2->hide(); - textLabel->hide(); + if (card == nullptr) { + nameLabel->setText(""); + textLabel->setText(""); + return; } + + QString text = ""; + text += QString("") + .arg(tr("Name:"), card->getName().toHtmlEscaped()); + + QStringList cardProps = card->getProperties(); + foreach (QString key, cardProps) { + QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":"; + text += + QString("").arg(keyText, card->getProperty(key).toHtmlEscaped()); + } + + auto relatedCards = card->getRelatedCards(); + auto reverserelatedCards2Me = card->getReverseRelatedCards2Me(); + if (relatedCards.size() || reverserelatedCards2Me.size()) { + text += QString(""; + } + + text += "
%1%2
%1%2
%1").arg(tr("Related cards:")); + + for (int i = 0; i < relatedCards.size(); ++i) { + QString tmp = relatedCards.at(i)->getName().toHtmlEscaped(); + text += "" + tmp + "
"; + } + + for (int i = 0; i < reverserelatedCards2Me.size(); ++i) { + QString tmp = reverserelatedCards2Me.at(i)->getName().toHtmlEscaped(); + text += "" + tmp + "
"; + } + + text += "
"; + nameLabel->setText(text); + textLabel->setText(card->getText()); } void CardInfoText::setInvalidCardName(const QString &cardName) { - nameLabel1->setText(tr("Unknown card:")); - nameLabel1->show(); - nameLabel2->setText(cardName); - nameLabel2->show(); + nameLabel->setText(tr("Unknown card:") + " " + cardName); + textLabel->setText(""); } void CardInfoText::retranslateUi() { - nameLabel1->setText(tr("Name:")); - manacostLabel1->setText(tr("Mana cost:")); - colorLabel1->setText(tr("Color(s):")); - cardtypeLabel1->setText(tr("Card type:")); - powtoughLabel1->setText(tr("P / T:")); - loyaltyLabel1->setText(tr("Loyalty:")); + /* + * There's no way we can really translate the text currently being rendered. + * The best we can do is invalidate the current text. + */ + setInvalidCardName(""); } diff --git a/cockatrice/src/cardinfotext.h b/cockatrice/src/cardinfotext.h index 49445c09f..7ee0a890a 100644 --- a/cockatrice/src/cardinfotext.h +++ b/cockatrice/src/cardinfotext.h @@ -12,23 +12,17 @@ class CardInfoText : public QFrame Q_OBJECT private: - QLabel *nameLabel1, *nameLabel2; - QLabel *manacostLabel1, *manacostLabel2; - QLabel *colorLabel1, *colorLabel2; - QLabel *cardtypeLabel1, *cardtypeLabel2; - QLabel *powtoughLabel1, *powtoughLabel2; - QLabel *loyaltyLabel1, *loyaltyLabel2; + QLabel *nameLabel; QTextEdit *textLabel; - CardInfoPtr info; - void resetLabels(); - public: CardInfoText(QWidget *parent = 0); void retranslateUi(); void setInvalidCardName(const QString &cardName); +signals: + void linkActivated(const QString &link); public slots: void setCard(CardInfoPtr card); }; diff --git a/cockatrice/src/cardinfowidget.cpp b/cockatrice/src/cardinfowidget.cpp index 170540df9..4bf8e94e7 100644 --- a/cockatrice/src/cardinfowidget.cpp +++ b/cockatrice/src/cardinfowidget.cpp @@ -1,6 +1,8 @@ -#include "cardinfowidget.h" +#include + #include "cardinfopicture.h" #include "cardinfotext.h" +#include "cardinfowidget.h" #include "carditem.h" #include "main.h" #include @@ -14,8 +16,9 @@ CardInfoWidget::CardInfoWidget(const QString &cardName, QWidget *parent, Qt::Win pic->setObjectName("pic"); text = new CardInfoText(); text->setObjectName("text"); + connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &))); - QVBoxLayout *layout = new QVBoxLayout(); + auto *layout = new QVBoxLayout(); layout->setObjectName("layout"); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); @@ -26,7 +29,7 @@ CardInfoWidget::CardInfoWidget(const QString &cardName, QWidget *parent, Qt::Win setFrameStyle(QFrame::Panel | QFrame::Raised); QDesktopWidget desktopWidget; int pixmapHeight = desktopWidget.screenGeometry().height() / 3; - int pixmapWidth = pixmapHeight / aspectRatio; + int pixmapWidth = static_cast(pixmapHeight / aspectRatio); pic->setFixedWidth(pixmapWidth); pic->setFixedHeight(pixmapHeight); setFixedWidth(pixmapWidth + 150); @@ -41,7 +44,7 @@ void CardInfoWidget::setCard(CardInfoPtr card) { if (info) disconnect(info.data(), nullptr, this, nullptr); - info = card; + info = std::move(card); if (info) connect(info.data(), SIGNAL(destroyed()), this, SLOT(clear())); diff --git a/cockatrice/src/cardinfowidget.h b/cockatrice/src/cardinfowidget.h index f89ca504e..7e986b75c 100644 --- a/cockatrice/src/cardinfowidget.h +++ b/cockatrice/src/cardinfowidget.h @@ -22,7 +22,7 @@ private: CardInfoText *text; public: - CardInfoWidget(const QString &cardName, QWidget *parent = 0, Qt::WindowFlags f = 0); + explicit CardInfoWidget(const QString &cardName, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); public slots: void setCard(CardInfoPtr card); diff --git a/cockatrice/src/decklistmodel.cpp b/cockatrice/src/decklistmodel.cpp index e8b4b95ff..7b66a927c 100644 --- a/cockatrice/src/decklistmodel.cpp +++ b/cockatrice/src/decklistmodel.cpp @@ -322,9 +322,7 @@ QModelIndex DeckListModel::addCard(const QString &cardName, const QString &zoneN // This is usually called from tab_deck_editor // So we'll create a new CardInfo with the name // and default values for all fields - info = CardInfo::newInstance(cardName, false, nullptr, nullptr, "unknown", nullptr, nullptr, QStringList(), - QList(), QList(), false, 0, false, 0, - SetList(), QStringMap(), MuidMap(), QStringMap(), QStringMap()); + info = CardInfo::newInstance(cardName); } else { return {}; } diff --git a/cockatrice/src/decklistmodel.h b/cockatrice/src/decklistmodel.h index 5e4f4dd30..19a0aecad 100644 --- a/cockatrice/src/decklistmodel.h +++ b/cockatrice/src/decklistmodel.h @@ -7,7 +7,6 @@ class DeckLoader; class CardDatabase; -class QProgressDialog; class QPrinter; class QTextCursor; @@ -21,19 +20,19 @@ public: : AbstractDecklistCardNode(_parent), dataNode(_dataNode) { } - int getNumber() const + int getNumber() const override { return dataNode->getNumber(); } - void setNumber(int _number) + void setNumber(int _number) override { dataNode->setNumber(_number); } - QString getName() const + QString getName() const override { return dataNode->getName(); } - void setName(const QString &_name) + void setName(const QString &_name) override { dataNode->setName(_name); } @@ -54,20 +53,20 @@ signals: void deckHashChanged(); public: - DeckListModel(QObject *parent = 0); - ~DeckListModel(); - int rowCount(const QModelIndex &parent) const; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &index) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - bool setData(const QModelIndex &index, const QVariant &value, int role); - bool removeRows(int row, int count, const QModelIndex &parent); + explicit DeckListModel(QObject *parent = nullptr); + ~DeckListModel() override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &index) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + bool removeRows(int row, int count, const QModelIndex &parent) override; QModelIndex findCard(const QString &cardName, const QString &zoneName) const; QModelIndex addCard(const QString &cardName, const QString &zoneName, bool abAddAnyway = false); - void sort(int column, Qt::SortOrder order); + void sort(int column, Qt::SortOrder order) override; void cleanList(); DeckLoader *getDeckList() const { diff --git a/cockatrice/src/dlg_connect.cpp b/cockatrice/src/dlg_connect.cpp index 84eb1dec4..7de8c3eb1 100644 --- a/cockatrice/src/dlg_connect.cpp +++ b/cockatrice/src/dlg_connect.cpp @@ -14,8 +14,6 @@ #include #include -#define PUBLIC_SERVERS_URL "https://github.com/Cockatrice/Cockatrice/wiki/Public-Servers" - DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) { previousHostButton = new QRadioButton(tr("Known Hosts"), this); @@ -273,15 +271,15 @@ void DlgConnect::newHostSelected(bool state) previousHosts->setDisabled(true); btnRefreshServers->setDisabled(true); hostEdit->clear(); - hostEdit->setPlaceholderText("Server URL"); + hostEdit->setPlaceholderText(tr("Server URL")); hostEdit->setDisabled(false); portEdit->clear(); - portEdit->setPlaceholderText("Communication Port"); + portEdit->setPlaceholderText(tr("Communication Port")); portEdit->setDisabled(false); playernameEdit->clear(); passwordEdit->clear(); saveEdit->clear(); - saveEdit->setPlaceholderText("Unique Server Name"); + saveEdit->setPlaceholderText(tr("Unique Server Name")); saveEdit->setDisabled(false); serverContactLabel->setText(""); serverContactLink->setText(""); diff --git a/cockatrice/src/dlg_edit_tokens.cpp b/cockatrice/src/dlg_edit_tokens.cpp index be910ff97..81ce5875f 100644 --- a/cockatrice/src/dlg_edit_tokens.cpp +++ b/cockatrice/src/dlg_edit_tokens.cpp @@ -17,7 +17,7 @@ #include #include -DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(0) +DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr) { nameLabel = new QLabel(tr("&Name:")); nameEdit = new QLineEdit; @@ -46,7 +46,7 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(0) annotationLabel->setBuddy(annotationEdit); connect(annotationEdit, SIGNAL(textChanged(QString)), this, SLOT(annotationChanged(QString))); - QGridLayout *grid = new QGridLayout; + auto *grid = new QGridLayout; grid->addWidget(nameLabel, 0, 0); grid->addWidget(nameEdit, 0, 1); grid->addWidget(colorLabel, 1, 0); @@ -89,15 +89,15 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(0) aRemoveToken->setIcon(QPixmap("theme:icons/decrement")); connect(aRemoveToken, SIGNAL(triggered()), this, SLOT(actRemoveToken())); - QToolBar *databaseToolBar = new QToolBar; + auto *databaseToolBar = new QToolBar; databaseToolBar->addAction(aAddToken); databaseToolBar->addAction(aRemoveToken); - QVBoxLayout *leftVBox = new QVBoxLayout; + auto *leftVBox = new QVBoxLayout; leftVBox->addWidget(chooseTokenView); leftVBox->addWidget(databaseToolBar); - QHBoxLayout *hbox = new QHBoxLayout; + auto *hbox = new QHBoxLayout; hbox->addLayout(leftVBox); hbox->addWidget(tokenDataGroupBox); @@ -105,7 +105,7 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(0) connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - QVBoxLayout *mainLayout = new QVBoxLayout; + auto *mainLayout = new QVBoxLayout; mainLayout->addLayout(hbox); mainLayout->addWidget(buttonBox); @@ -154,9 +154,10 @@ void DlgEditTokens::actAddToken() } } while (askAgain); - CardInfoPtr card = CardInfo::newInstance(name, true); - card->addToSet(databaseModel->getDatabase()->getSet(CardDatabase::TOKENS_SETNAME)); + CardInfoPtr card = CardInfo::newInstance(name, "", true); card->setCardType("Token"); + card->addToSet(databaseModel->getDatabase()->getSet(CardDatabase::TOKENS_SETNAME)); + databaseModel->getDatabase()->addCard(card); } @@ -172,7 +173,7 @@ void DlgEditTokens::actRemoveToken() void DlgEditTokens::colorChanged(int colorIndex) { if (currentCard) - currentCard->setColors(QStringList() << QString(colorEdit->itemData(colorIndex).toChar())); + currentCard->setColors(QString(colorEdit->itemData(colorIndex).toChar())); } void DlgEditTokens::ptChanged(const QString &_pt) diff --git a/cockatrice/src/dlg_edit_tokens.h b/cockatrice/src/dlg_edit_tokens.h index 6e9b36660..4f3f5eaf4 100644 --- a/cockatrice/src/dlg_edit_tokens.h +++ b/cockatrice/src/dlg_edit_tokens.h @@ -35,7 +35,7 @@ private: QTreeView *chooseTokenView; public: - DlgEditTokens(QWidget *parent = nullptr); + explicit DlgEditTokens(QWidget *parent = nullptr); }; #endif diff --git a/cockatrice/src/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/dlg_load_deck_from_clipboard.cpp index 8d39ea41d..32d1dec46 100644 --- a/cockatrice/src/dlg_load_deck_from_clipboard.cpp +++ b/cockatrice/src/dlg_load_deck_from_clipboard.cpp @@ -32,7 +32,7 @@ DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : QDialog(pa resize(500, 500); actRefresh(); - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); } diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 723e1128f..6b0e8497c 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -45,8 +45,6 @@ GeneralSettingsPage::GeneralSettingsPage() languageBox.setCurrentIndex(i); } - picDownloadCheckBox.setChecked(settingsCache->getPicDownload()); - // updates QList channels = settingsCache->getUpdateReleaseChannels(); foreach (ReleaseChannel *chan, channels) { @@ -55,6 +53,7 @@ GeneralSettingsPage::GeneralSettingsPage() updateReleaseChannelBox.setCurrentIndex(settingsCache->getUpdateReleaseChannel()->getIndex()); updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates()); + newVersionOracleCheckBox.setChecked(settingsCache->getNotifyAboutNewVersion()); // pixmap cache pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); @@ -64,27 +63,16 @@ GeneralSettingsPage::GeneralSettingsPage() pixmapCacheEdit.setValue(settingsCache->getPixmapCacheSize()); pixmapCacheEdit.setSuffix(" MB"); - defaultUrlEdit = new QLineEdit(settingsCache->getPicUrl()); - fallbackUrlEdit = new QLineEdit(settingsCache->getPicUrlFallback()); - showTipsOnStartup.setChecked(settingsCache->getShowTipsOnStartup()); - connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked())); connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); - connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int))); connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int))); connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), settingsCache, SLOT(setUpdateReleaseChannel(int))); connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int))); - connect(&picDownloadCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool))); - connect(defaultUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrl(QString))); - connect(fallbackUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlFallback(QString))); - connect(&defaultUrlRestoreButton, SIGNAL(clicked()), this, SLOT(defaultUrlRestoreButtonClicked())); - connect(&fallbackUrlRestoreButton, SIGNAL(clicked()), this, SLOT(fallbackUrlRestoreButtonClicked())); + connect(&newVersionOracleCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutNewVersion(int))); connect(&showTipsOnStartup, SIGNAL(clicked(bool)), settingsCache, SLOT(setShowTipsOnStartup(bool))); - setEnabledStatus(settingsCache->getPicDownload()); - auto *personalGrid = new QGridLayout; personalGrid->addWidget(&languageLabel, 0, 0); personalGrid->addWidget(&languageBox, 0, 1); @@ -93,19 +81,8 @@ GeneralSettingsPage::GeneralSettingsPage() personalGrid->addWidget(&pixmapCacheLabel, 2, 0); personalGrid->addWidget(&pixmapCacheEdit, 2, 1); personalGrid->addWidget(&updateNotificationCheckBox, 3, 0); - personalGrid->addWidget(&showTipsOnStartup, 4, 0); - personalGrid->addWidget(&picDownloadCheckBox, 5, 0); - personalGrid->addWidget(&urlLinkLabel, 5, 1); - personalGrid->addWidget(&defaultUrlLabel, 6, 0, 1, 1); - personalGrid->addWidget(defaultUrlEdit, 6, 1, 1, 1); - personalGrid->addWidget(&defaultUrlRestoreButton, 6, 2, 1, 1); - personalGrid->addWidget(&fallbackUrlLabel, 7, 0, 1, 1); - personalGrid->addWidget(fallbackUrlEdit, 7, 1, 1, 1); - personalGrid->addWidget(&fallbackUrlRestoreButton, 7, 2, 1, 1); - personalGrid->addWidget(&clearDownloadedPicsButton, 8, 1); - - urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); - urlLinkLabel.setOpenExternalLinks(true); + personalGrid->addWidget(&newVersionOracleCheckBox, 4, 0); + personalGrid->addWidget(&showTipsOnStartup, 5, 0); personalGroupBox = new QGroupBox; personalGroupBox->setLayout(personalGrid); @@ -194,20 +171,6 @@ QString GeneralSettingsPage::languageName(const QString &qmFile) return translator.translate("i18n", DEFAULT_LANG_NAME); } -void GeneralSettingsPage::defaultUrlRestoreButtonClicked() -{ - QString path = PIC_URL_DEFAULT; - defaultUrlEdit->setText(path); - settingsCache->setPicUrl(path); -} - -void GeneralSettingsPage::fallbackUrlRestoreButtonClicked() -{ - QString path = PIC_URL_FALLBACK; - fallbackUrlEdit->setText(path); - settingsCache->setPicUrlFallback(path); -} - void GeneralSettingsPage::deckPathButtonClicked() { QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); @@ -238,30 +201,6 @@ void GeneralSettingsPage::picsPathButtonClicked() settingsCache->setPicsPath(path); } -void GeneralSettingsPage::clearDownloadedPicsButtonClicked() -{ - QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/"; - QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot); - bool outerSuccessRemove = true; - for (int i = 0; i < dirs.length(); i++) { - QString currentPath = picsPath + dirs.at(i) + "/"; - QStringList files = QDir(currentPath).entryList(QDir::Files); - bool innerSuccessRemove = true; - for (int j = 0; j < files.length(); j++) - if (!QDir(currentPath).remove(files.at(j))) { - qDebug() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8(); - outerSuccessRemove = false; - innerSuccessRemove = false; - } - if (innerSuccessRemove) - QDir(picsPath).rmdir(dirs.at(i)); - } - if (outerSuccessRemove) - QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset.")); - else - QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared.")); -} - void GeneralSettingsPage::cardDatabasePathButtonClicked() { QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); @@ -291,7 +230,6 @@ void GeneralSettingsPage::retranslateUi() { personalGroupBox->setTitle(tr("Personal settings")); languageLabel.setText(tr("Language:")); - picDownloadCheckBox.setText(tr("Download card pictures on the fly")); if (settingsCache->getIsPortableBuild()) { pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)")); @@ -305,26 +243,12 @@ void GeneralSettingsPage::retranslateUi() cardDatabasePathLabel.setText(tr("Card database:")); tokenDatabasePathLabel.setText(tr("Token database:")); pixmapCacheLabel.setText(tr("Picture cache size:")); - defaultUrlLabel.setText(tr("Primary download URL:")); - fallbackUrlLabel.setText(tr("Fallback download URL:")); - urlLinkLabel.setText( - QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to set a custom picture url"))); - clearDownloadedPicsButton.setText(tr("Reset/clear downloaded pictures")); updateReleaseChannelLabel.setText(tr("Update channel")); updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client")); - defaultUrlRestoreButton.setText(tr("Reset")); - fallbackUrlRestoreButton.setText(tr("Reset")); + newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice")); showTipsOnStartup.setText(tr("Show tips on startup")); } -void GeneralSettingsPage::setEnabledStatus(bool status) -{ - defaultUrlEdit->setEnabled(status); - fallbackUrlEdit->setEnabled(status); - defaultUrlRestoreButton.setEnabled(status); - fallbackUrlRestoreButton.setEnabled(status); -} - AppearanceSettingsPage::AppearanceSettingsPage() { QString themeName = settingsCache->getThemeName(); @@ -498,6 +422,15 @@ void UserInterfaceSettingsPage::retranslateUi() DeckEditorSettingsPage::DeckEditorSettingsPage() { + picDownloadCheckBox.setChecked(settingsCache->getPicDownload()); + connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int))); + + urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); + urlLinkLabel.setOpenExternalLinks(true); + + connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked())); + connect(&resetDownloadURLs, SIGNAL(clicked()), this, SLOT(resetDownloadedURLsButtonClicked())); + auto *lpGeneralGrid = new QGridLayout; auto *lpSpoilerGrid = new QGridLayout; @@ -515,9 +448,46 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() // Update the GUI depending on if the box is ticked or not setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked()); - // Create the layout - lpGeneralGrid->addWidget(&mcGeneralMessageLabel, 0, 0); + urlList = new QListWidget; + urlList->setSelectionMode(QAbstractItemView::SingleSelection); + urlList->setAlternatingRowColors(true); + urlList->setDragEnabled(true); + urlList->setDragDropMode(QAbstractItemView::InternalMove); + connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this, + SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int))); + for (int i = 0; i < settingsCache->downloads().getCount(); i++) + urlList->addItem(settingsCache->downloads().getDownloadUrlAt(i)); + + auto aAdd = new QAction(this); + aAdd->setIcon(QPixmap("theme:icons/increment")); + connect(aAdd, SIGNAL(triggered()), this, SLOT(actAddURL())); + auto aEdit = new QAction(this); + aEdit->setIcon(QPixmap("theme:icons/pencil")); + connect(aEdit, SIGNAL(triggered()), this, SLOT(actEditURL())); + auto aRemove = new QAction(this); + aRemove->setIcon(QPixmap("theme:icons/decrement")); + connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL())); + + auto *messageToolBar = new QToolBar; + messageToolBar->setOrientation(Qt::Vertical); + messageToolBar->addAction(aAdd); + messageToolBar->addAction(aRemove); + messageToolBar->addAction(aEdit); + messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + + auto *messageListLayout = new QHBoxLayout; + messageListLayout->addWidget(messageToolBar); + messageListLayout->addWidget(urlList); + + // Top Layout + lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0); + lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1); + lpGeneralGrid->addLayout(messageListLayout, 1, 0, 1, 2); + lpGeneralGrid->addWidget(&urlLinkLabel, 2, 0); + lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 2, 1); + + // Spoiler Layout lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0); lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0); lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1); @@ -543,6 +513,94 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() setLayout(lpMainLayout); } +void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked() +{ + settingsCache->downloads().clear(); + urlList->clear(); + urlList->addItems(settingsCache->downloads().getAllURLs()); + QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset.")); +} + +void DeckEditorSettingsPage::clearDownloadedPicsButtonClicked() +{ + QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/"; + QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + bool outerSuccessRemove = true; + for (const auto &dir : dirs) { + QString currentPath = picsPath + dir + "/"; + QStringList files = QDir(currentPath).entryList(QDir::Files); + bool innerSuccessRemove = true; + for (int j = 0; j < files.length(); j++) { + if (!QDir(currentPath).remove(files.at(j))) { + qInfo() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8(); + outerSuccessRemove = false; + innerSuccessRemove = false; + } + qInfo() << "Removed " << currentPath << files.at(j); + } + + if (innerSuccessRemove) { + bool success = QDir(picsPath).rmdir(dir); + if (!success) { + qInfo() << "Failed to remove inner directory" << picsPath; + } else { + qInfo() << "Removed" << currentPath; + } + } + } + if (outerSuccessRemove) { + QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset.")); + } else { + QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared.")); + } +} + +void DeckEditorSettingsPage::actAddURL() +{ + bool ok; + QString msg = QInputDialog::getText(this, tr("Add URL"), tr("URL:"), QLineEdit::Normal, QString(), &ok); + if (ok) { + urlList->addItem(msg); + storeSettings(); + } +} + +void DeckEditorSettingsPage::actRemoveURL() +{ + if (urlList->currentItem() != nullptr) { + delete urlList->takeItem(urlList->currentRow()); + storeSettings(); + } +} + +void DeckEditorSettingsPage::actEditURL() +{ + if (urlList->currentItem()) { + QString oldText = urlList->currentItem()->text(); + bool ok; + QString msg = QInputDialog::getText(this, tr("Edit URL"), tr("URL:"), QLineEdit::Normal, oldText, &ok); + if (ok) { + urlList->currentItem()->setText(msg); + storeSettings(); + } + } +} + +void DeckEditorSettingsPage::storeSettings() +{ + qInfo() << "URL Priority Reset"; + settingsCache->downloads().clear(); + for (int i = 0; i < urlList->count(); i++) { + qInfo() << "Priority" << i << ":" << urlList->item(i)->text(); + settingsCache->downloads().setDownloadUrlAt(i, urlList->item(i)->text()); + } +} + +void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int) +{ + storeSettings(); +} + void DeckEditorSettingsPage::updateSpoilers() { // Disable the button so the user can only press it once at a time @@ -603,14 +661,18 @@ void DeckEditorSettingsPage::setSpoilersEnabled(bool anInput) void DeckEditorSettingsPage::retranslateUi() { + mpGeneralGroupBox->setTitle(tr("URL Download Priority")); mpSpoilerGroupBox->setTitle(tr("Spoilers")); mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically")); mcSpoilerSaveLabel.setText(tr("Spoiler Location:")); - mcGeneralMessageLabel.setText(tr("Hey, something's here finally!")); lastUpdatedLabel.setText(tr("Last Updated") + ": " + getLastUpdateTime()); infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" + tr("Press the button to manually update without relaunching") + "\n\n" + tr("Do not close settings until manual update complete")); + picDownloadCheckBox.setText(tr("Download card pictures on the fly")); + urlLinkLabel.setText(QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL"))); + clearDownloadedPicsButton.setText(tr("Delete Downloaded Images")); + resetDownloadURLs.setText(tr("Reset Download URLs")); } MessagesSettingsPage::MessagesSettingsPage() @@ -649,7 +711,7 @@ MessagesSettingsPage::MessagesSettingsPage() connect(&roomHistory, SIGNAL(stateChanged(int)), settingsCache, SLOT(setRoomHistory(int))); customAlertString = new QLineEdit(); - customAlertString->setPlaceholderText("Word1 Word2 Word3"); + customAlertString->setPlaceholderText(tr("Word1 Word2 Word3")); customAlertString->setText(settingsCache->getHighlightWords()); connect(customAlertString, SIGNAL(textChanged(QString)), settingsCache, SLOT(setHighlightWords(QString))); @@ -689,12 +751,16 @@ MessagesSettingsPage::MessagesSettingsPage() aAdd = new QAction(this); aAdd->setIcon(QPixmap("theme:icons/increment")); + aAdd->setStatusTip(tr("Add New URL")); + connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd())); aEdit = new QAction(this); aEdit->setIcon(QPixmap("theme:icons/pencil")); + aEdit->setStatusTip(tr("Edit URL")); connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit())); aRemove = new QAction(this); aRemove->setIcon(QPixmap("theme:icons/decrement")); + aRemove->setStatusTip(tr("Remove URL")); connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove())); auto *messageToolBar = new QToolBar; @@ -798,7 +864,7 @@ void MessagesSettingsPage::actEdit() void MessagesSettingsPage::actRemove() { - if (messageList->currentItem()) { + if (messageList->currentItem() != nullptr) { delete messageList->takeItem(messageList->currentRow()); storeSettings(); } @@ -1000,7 +1066,7 @@ void DlgSettings::setTab(int index) void DlgSettings::updateLanguage() { - qApp->removeTranslator(translator); + qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) installNewTranslator(); } @@ -1094,7 +1160,7 @@ void DlgSettings::retranslateUi() generalButton->setText(tr("General")); appearanceButton->setText(tr("Appearance")); userInterfaceButton->setText(tr("User Interface")); - deckEditorButton->setText(tr("Deck Editor")); + deckEditorButton->setText(tr("Card Sources")); messagesButton->setText(tr("Chat")); soundButton->setText(tr("Sound")); shortcutsButton->setText(tr("Shortcuts")); diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index d35add0c9..479e66771 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -43,13 +43,9 @@ private slots: void deckPathButtonClicked(); void replaysPathButtonClicked(); void picsPathButtonClicked(); - void clearDownloadedPicsButtonClicked(); void cardDatabasePathButtonClicked(); void tokenDatabasePathButtonClicked(); void languageBoxChanged(int index); - void setEnabledStatus(bool); - void defaultUrlRestoreButtonClicked(); - void fallbackUrlRestoreButtonClicked(); private: QStringList findQmFiles(); @@ -59,14 +55,12 @@ private: QLineEdit *picsPathEdit; QLineEdit *cardDatabasePathEdit; QLineEdit *tokenDatabasePathEdit; - QLineEdit *defaultUrlEdit; - QLineEdit *fallbackUrlEdit; QSpinBox pixmapCacheEdit; QGroupBox *personalGroupBox; QGroupBox *pathsGroupBox; QComboBox languageBox; - QCheckBox picDownloadCheckBox; QCheckBox updateNotificationCheckBox; + QCheckBox newVersionOracleCheckBox; QComboBox updateReleaseChannelBox; QLabel languageLabel; QLabel pixmapCacheLabel; @@ -75,13 +69,7 @@ private: QLabel picsPathLabel; QLabel cardDatabasePathLabel; QLabel tokenDatabasePathLabel; - QLabel defaultUrlLabel; - QLabel fallbackUrlLabel; - QLabel urlLinkLabel; QLabel updateReleaseChannelLabel; - QPushButton clearDownloadedPicsButton; - QPushButton defaultUrlRestoreButton; - QPushButton fallbackUrlRestoreButton; QCheckBox showTipsOnStartup; }; @@ -143,19 +131,30 @@ public: QString getLastUpdateTime(); private slots: + void storeSettings(); + void urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int); void setSpoilersEnabled(bool); void spoilerPathButtonClicked(); void updateSpoilers(); void unlockSettings(); + void actAddURL(); + void actRemoveURL(); + void actEditURL(); + void clearDownloadedPicsButtonClicked(); + void resetDownloadedURLsButtonClicked(); private: + QPushButton clearDownloadedPicsButton; + QPushButton resetDownloadURLs; + QLabel urlLinkLabel; + QCheckBox picDownloadCheckBox; + QListWidget *urlList; QCheckBox mcDownloadSpoilersCheckBox; QLabel msDownloadSpoilersLabel; QGroupBox *mpGeneralGroupBox; QGroupBox *mpSpoilerGroupBox; QLineEdit *mpSpoilerSavePathLineEdit; QLabel mcSpoilerSaveLabel; - QLabel mcGeneralMessageLabel; QLabel lastUpdatedLabel; QLabel infoOnSpoilersLabel; QPushButton *mpSpoilerPathButton; diff --git a/cockatrice/src/dlg_tip_of_the_day.cpp b/cockatrice/src/dlg_tip_of_the_day.cpp index 67ecc400e..37d6465f1 100644 --- a/cockatrice/src/dlg_tip_of_the_day.cpp +++ b/cockatrice/src/dlg_tip_of_the_day.cpp @@ -13,7 +13,7 @@ #define MIN_TIP_IMAGE_HEIGHT 200 #define MIN_TIP_IMAGE_WIDTH 200 #define MAX_TIP_IMAGE_HEIGHT 300 -#define MAX_TIP_IMAGE_WIDTH 300 +#define MAX_TIP_IMAGE_WIDTH 500 DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent) { @@ -149,9 +149,9 @@ void DlgTipOfTheDay::updateTip(int tipId) qDebug() << "Image failed to load from" << imagePath; imageLabel->clear(); } else { - int h = std::min(std::max(image->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT); + int h = std::min(std::max(imageLabel->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT); int w = std::min(std::max(imageLabel->width(), MIN_TIP_IMAGE_WIDTH), MAX_TIP_IMAGE_WIDTH); - imageLabel->setPixmap(image->scaled(h, w, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } date->setText("Tip added on: " + tip.getDate().toString("yyyy.MM.dd") + ""); @@ -163,9 +163,7 @@ void DlgTipOfTheDay::updateTip(int tipId) void DlgTipOfTheDay::resizeEvent(QResizeEvent *event) { - int h = imageLabel->height(); - int w = imageLabel->width(); - imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + imageLabel->setPixmap(image->scaled(imageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); QWidget::resizeEvent(event); } diff --git a/cockatrice/src/filterbuilder.cpp b/cockatrice/src/filterbuilder.cpp index b3275b90b..03a4632c3 100644 --- a/cockatrice/src/filterbuilder.cpp +++ b/cockatrice/src/filterbuilder.cpp @@ -12,7 +12,7 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent) filterCombo = new QComboBox; filterCombo->setObjectName("filterCombo"); for (int i = 0; i < CardFilter::AttrEnd; i++) - filterCombo->addItem(tr(CardFilter::attrName(static_cast(i))), QVariant(i)); + filterCombo->addItem(CardFilter::attrName(static_cast(i)), QVariant(i)); typeCombo = new QComboBox; typeCombo->setObjectName("typeCombo"); diff --git a/cockatrice/src/filtertree.cpp b/cockatrice/src/filtertree.cpp index 93abf774c..1dcb37cf8 100644 --- a/cockatrice/src/filtertree.cpp +++ b/cockatrice/src/filtertree.cpp @@ -187,11 +187,8 @@ bool FilterItem::acceptColor(const CardInfoPtr info) const */ int match_count = 0; for (auto &it : converted_term) { - for (auto i = info->getColors().constBegin(); i != info->getColors().constEnd(); i++) { - if ((*i).contains(it, Qt::CaseInsensitive)) { - match_count++; - } - } + if (info->getColors().contains(it, Qt::CaseInsensitive)) + match_count++; } return match_count == converted_term.length(); @@ -205,9 +202,9 @@ bool FilterItem::acceptText(const CardInfoPtr info) const bool FilterItem::acceptSet(const CardInfoPtr info) const { bool status = false; - for (auto i = info->getSets().constBegin(); i != info->getSets().constEnd(); i++) { - if ((*i)->getShortName().compare(term, Qt::CaseInsensitive) == 0 || - (*i)->getLongName().compare(term, Qt::CaseInsensitive) == 0) { + for (const auto &set : info->getSets()) { + if (set.getPtr()->getShortName().compare(term, Qt::CaseInsensitive) == 0 || + set.getPtr()->getLongName().compare(term, Qt::CaseInsensitive) == 0) { status = true; break; } @@ -299,7 +296,7 @@ bool FilterItem::acceptRarity(const CardInfoPtr info) const /* * The purpose of this loop is to only apply one of the replacement - * policies and then escape. If we attempt to layer them ontop of + * policies and then escape. If we attempt to layer them on top of * each other, we will get awkward results (i.e. comythic rare mythic rareon) * Conditional statement will exit once a case is successful in * replacement OR we go through all possible cases. @@ -334,8 +331,8 @@ bool FilterItem::acceptRarity(const CardInfoPtr info) const } } - for (const QString &rareLevel : info->getRarities()) { - if (rareLevel.compare(converted_term, Qt::CaseInsensitive) == 0) { + for (const auto &set : info->getSets()) { + if (set.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) { return true; } } diff --git a/cockatrice/src/filtertree.h b/cockatrice/src/filtertree.h index 3045d4e14..7227f9442 100644 --- a/cockatrice/src/filtertree.h +++ b/cockatrice/src/filtertree.h @@ -1,12 +1,14 @@ + + #ifndef FILTERTREE_H #define FILTERTREE_H +#include "carddatabase.h" +#include "cardfilter.h" #include #include #include - -#include "carddatabase.h" -#include "cardfilter.h" +#include class FilterTreeNode { @@ -33,11 +35,11 @@ public: } virtual FilterTreeNode *parent() const { - return NULL; + return nullptr; } virtual FilterTreeNode *nodeAt(int /* i */) const { - return NULL; + return nullptr; } virtual void deleteAt(int /* i */) { @@ -52,43 +54,39 @@ public: } virtual int index() const { - return (parent() != NULL) ? parent()->childIndex(this) : -1; + return (parent() != nullptr) ? parent()->childIndex(this) : -1; } - virtual QString text() const + virtual const QString text() const { - return QString(textCStr()); + return QString(""); } virtual bool isLeaf() const { return false; } - virtual const char *textCStr() const - { - return ""; - } virtual void nodeChanged() const { - if (parent() != NULL) + if (parent() != nullptr) parent()->nodeChanged(); } virtual void preInsertChild(const FilterTreeNode *p, int i) const { - if (parent() != NULL) + if (parent() != nullptr) parent()->preInsertChild(p, i); } virtual void postInsertChild(const FilterTreeNode *p, int i) const { - if (parent() != NULL) + if (parent() != nullptr) parent()->postInsertChild(p, i); } virtual void preRemoveChild(const FilterTreeNode *p, int i) const { - if (parent() != NULL) + if (parent() != nullptr) parent()->preRemoveChild(p, i); } virtual void postRemoveChild(const FilterTreeNode *p, int i) const { - if (parent() != NULL) + if (parent() != nullptr) parent()->postRemoveChild(p, i); } }; @@ -100,13 +98,13 @@ protected: public: virtual ~FilterTreeBranch(); - FilterTreeNode *nodeAt(int i) const; - void deleteAt(int i); - int childCount() const + FilterTreeNode *nodeAt(int i) const override; + void deleteAt(int i) override; + int childCount() const override { return childNodes.size(); } - int childIndex(const FilterTreeNode *node) const; + int childIndex(const FilterTreeNode *node) const override; }; class FilterItemList; @@ -125,8 +123,8 @@ public: } const FilterItemList *findTypeList(CardFilter::Type type) const; FilterItemList *typeList(CardFilter::Type type); - FilterTreeNode *parent() const; - const char *textCStr() const + FilterTreeNode *parent() const override; + const QString text() const override { return CardFilter::attrName(attr); } @@ -148,21 +146,21 @@ public: { return p->attr; } - FilterTreeNode *parent() const + FilterTreeNode *parent() const override { return p; } int termIndex(const QString &term) const; FilterTreeNode *termNode(const QString &term); - const char *textCStr() const + const QString text() const override { return CardFilter::typeName(type); } - bool testTypeAnd(const CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeAndNot(const CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeOrNot(const CardInfoPtr info, CardFilter::Attr attr) const; + bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const; + bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const; + bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const; + bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const; }; class FilterItem : public FilterTreeNode @@ -173,10 +171,10 @@ private: public: const QString term; - FilterItem(QString trm, FilterItemList *parent) : p(parent), term(trm) + FilterItem(QString trm, FilterItemList *parent) : p(parent), term(std::move(trm)) { } - virtual ~FilterItem(){}; + virtual ~FilterItem() = default; CardFilter::Attr attr() const { @@ -186,34 +184,30 @@ public: { return p->type; } - FilterTreeNode *parent() const + FilterTreeNode *parent() const override { return p; } - QString text() const + const QString text() const override { return term; } - const char *textCStr() const - { - return term.toStdString().c_str(); - } - bool isLeaf() const + bool isLeaf() const override { return true; } - bool acceptName(const CardInfoPtr info) const; - bool acceptType(const CardInfoPtr info) const; - bool acceptColor(const CardInfoPtr info) const; - bool acceptText(const CardInfoPtr info) const; - bool acceptSet(const CardInfoPtr info) const; - bool acceptManaCost(const CardInfoPtr info) const; - bool acceptCmc(const CardInfoPtr info) const; - bool acceptPowerToughness(const CardInfoPtr info, CardFilter::Attr attr) const; - bool acceptLoyalty(const CardInfoPtr info) const; - bool acceptRarity(const CardInfoPtr info) const; - bool acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) const; + bool acceptName(CardInfoPtr info) const; + bool acceptType(CardInfoPtr info) const; + bool acceptColor(CardInfoPtr info) const; + bool acceptText(CardInfoPtr info) const; + bool acceptSet(CardInfoPtr info) const; + bool acceptManaCost(CardInfoPtr info) const; + bool acceptCmc(CardInfoPtr info) const; + bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const; + bool acceptLoyalty(CardInfoPtr info) const; + bool acceptRarity(CardInfoPtr info) const; + bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const; bool relationCheck(int cardInfo) const; }; @@ -232,47 +226,47 @@ private: LogicMap *attrLogicMap(CardFilter::Attr attr); FilterItemList *attrTypeList(CardFilter::Attr attr, CardFilter::Type type); - bool testAttr(const CardInfoPtr info, const LogicMap *lm) const; + bool testAttr(CardInfoPtr info, const LogicMap *lm) const; - void nodeChanged() const + void nodeChanged() const override { emit changed(); } - void preInsertChild(const FilterTreeNode *p, int i) const + void preInsertChild(const FilterTreeNode *p, int i) const override { emit preInsertRow(p, i); } - void postInsertChild(const FilterTreeNode *p, int i) const + void postInsertChild(const FilterTreeNode *p, int i) const override { emit postInsertRow(p, i); } - void preRemoveChild(const FilterTreeNode *p, int i) const + void preRemoveChild(const FilterTreeNode *p, int i) const override { emit preRemoveRow(p, i); } - void postRemoveChild(const FilterTreeNode *p, int i) const + void postRemoveChild(const FilterTreeNode *p, int i) const override { emit postRemoveRow(p, i); } public: FilterTree(); - ~FilterTree(); + ~FilterTree() override; int findTermIndex(CardFilter::Attr attr, CardFilter::Type type, const QString &term); int findTermIndex(const CardFilter *f); FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term); FilterTreeNode *termNode(const CardFilter *f); FilterTreeNode *attrTypeNode(CardFilter::Attr attr, CardFilter::Type type); - const char *textCStr() const + const QString text() const override { - return "root"; + return QString("root"); } - int index() const + int index() const override { return 0; } - bool acceptsCard(const CardInfoPtr info) const; + bool acceptsCard(CardInfoPtr info) const; void clear(); }; diff --git a/cockatrice/src/filtertreemodel.cpp b/cockatrice/src/filtertreemodel.cpp index 29bf6b58a..f01f7b4a1 100644 --- a/cockatrice/src/filtertreemodel.cpp +++ b/cockatrice/src/filtertreemodel.cpp @@ -128,10 +128,7 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const case Qt::ToolTipRole: case Qt::StatusTipRole: case Qt::WhatsThisRole: - if (!node->isLeaf()) - return tr(node->textCStr()); - else - return node->text(); + return node->text(); case Qt::CheckStateRole: if (node->isEnabled()) return Qt::Checked; diff --git a/cockatrice/src/game_specific_terms.h b/cockatrice/src/game_specific_terms.h new file mode 100644 index 000000000..9e3d03868 --- /dev/null +++ b/cockatrice/src/game_specific_terms.h @@ -0,0 +1,49 @@ +#ifndef GAME_SPECIFIC_TERMS_H +#define GAME_SPECIFIC_TERMS_H + +#include +#include + +/* + * Collection of traslatable property names used in games, + * so we can use Game::Property instead of hardcoding strings. + * Note: Mtg = "Maybe that game" + */ + +namespace Mtg +{ +QString const CardType("type"); +QString const ConvertedManaCost("cmc"); +QString const Colors("colors"); +QString const Loyalty("loyalty"); +QString const MainCardType("maintype"); +QString const ManaCost("manacost"); +QString const PowTough("pt"); +QString const Side("side"); +QString const Layout("layout"); + +inline static const QString getNicePropertyName(QString key) +{ + if (key == CardType) + return QCoreApplication::translate("Mtg", "Card type"); + if (key == ConvertedManaCost) + return QCoreApplication::translate("Mtg", "Converted mana cost"); + if (key == Colors) + return QCoreApplication::translate("Mtg", "Color(s)"); + if (key == Loyalty) + return QCoreApplication::translate("Mtg", "Loyalty"); + if (key == MainCardType) + return QCoreApplication::translate("Mtg", "Main card type"); + if (key == ManaCost) + return QCoreApplication::translate("Mtg", "Mana cost"); + if (key == PowTough) + return QCoreApplication::translate("Mtg", "P / T"); + if (key == Side) + return QCoreApplication::translate("Mtg", "Side"); + if (key == Layout) + return QCoreApplication::translate("Mtg", "Layout"); + return key; +} +}; // namespace Mtg + +#endif \ No newline at end of file diff --git a/cockatrice/src/gamescene.cpp b/cockatrice/src/gamescene.cpp index 1f3c8d695..d45d6873e 100644 --- a/cockatrice/src/gamescene.cpp +++ b/cockatrice/src/gamescene.cpp @@ -40,7 +40,7 @@ void GameScene::addPlayer(Player *player) players << player; addItem(player); connect(player, SIGNAL(sizeChanged()), this, SLOT(rearrange())); - connect(player, SIGNAL(gameConceded()), this, SLOT(rearrange())); + connect(player, SIGNAL(playerCountChanged()), this, SLOT(rearrange())); } void GameScene::removePlayer(Player *player) diff --git a/cockatrice/src/gameview.cpp b/cockatrice/src/gameview.cpp index b96e5f5ba..26f975504 100644 --- a/cockatrice/src/gameview.cpp +++ b/cockatrice/src/gameview.cpp @@ -22,7 +22,7 @@ GameView::GameView(QGraphicsScene *scene, QWidget *parent) : QGraphicsView(scene connect(aCloseMostRecentZoneView, SIGNAL(triggered()), scene, SLOT(closeMostRecentZoneView())); addAction(aCloseMostRecentZoneView); - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); rubberBand = new QRubberBand(QRubberBand::Rectangle, this); } diff --git a/cockatrice/src/handle_public_servers.cpp b/cockatrice/src/handle_public_servers.cpp index 11411ccdc..c13088028 100644 --- a/cockatrice/src/handle_public_servers.cpp +++ b/cockatrice/src/handle_public_servers.cpp @@ -1,6 +1,6 @@ #include "handle_public_servers.h" -#include "qt-json/json.h" #include "settingscache.h" +#include #include #include #include @@ -31,19 +31,16 @@ void HandlePublicServers::actFinishParsingDownloadedData() savedHostList = uci.getServerInfo(); // Downloaded data from GitHub - bool jsonSuccessful; - QString jsonData = QString(reply->readAll()); - - auto jsonMap = QtJson::Json::parse(jsonData, jsonSuccessful).toMap(); - - if (jsonSuccessful) { + QJsonParseError parseError{}; + QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError); + if (parseError.error == QJsonParseError::NoError) { + QVariantMap jsonMap = jsonResponse.toVariant().toMap(); updateServerINISettings(jsonMap); } else { qDebug() << "[PUBLIC SERVER HANDLER]" - << "JSON Parsing Error"; + << "JSON Parsing Error:" << parseError.errorString(); emit sigPublicServersDownloadedUnsuccessfully(errorCode); } - } else { qDebug() << "[PUBLIC SERVER HANDLER]" << "Error Downloading Public Servers" << errorCode; @@ -74,6 +71,10 @@ void HandlePublicServers::updateServerINISettings(QMap jsonMa QString serverPort = serverMap["port"].toString(); QString serverSite = serverMap["site"].toString(); + if (serverMap.contains("websocketPort")) { + serverPort = serverMap["websocketPort"].toString(); + } + bool serverFound = false; for (const auto &iter : savedHostList) { // If the URL/IP matches diff --git a/cockatrice/src/messagelogwidget.cpp b/cockatrice/src/messagelogwidget.cpp index 97b917f00..533a59116 100644 --- a/cockatrice/src/messagelogwidget.cpp +++ b/cockatrice/src/messagelogwidget.cpp @@ -156,7 +156,7 @@ void MessageLogWidget::logAlwaysRevealTopCard(Player *player, CardZone *zone, bo void MessageLogWidget::logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName) { - appendHtmlServerMessage(QString("%1 attaches %2 to %3's %4.") + appendHtmlServerMessage(tr("%1 attaches %2 to %3's %4.") .arg(sanitizeHtml(player->getName())) .arg(cardLink(cardName)) .arg(sanitizeHtml(targetPlayer->getName())) @@ -169,6 +169,12 @@ void MessageLogWidget::logConcede(Player *player) appendHtmlServerMessage(tr("%1 has conceded the game.").arg(sanitizeHtml(player->getName())), true); } +void MessageLogWidget::logUnconcede(Player *player) +{ + soundEngine->playSound("player_concede"); + appendHtmlServerMessage(tr("%1 has unconceded the game.").arg(sanitizeHtml(player->getName())), true); +} + void MessageLogWidget::logConnectionStateChanged(Player *player, bool connectionState) { if (connectionState) { @@ -341,7 +347,7 @@ void MessageLogWidget::logDrawCards(Player *player, int number) mulliganPlayer = player; else { soundEngine->playSound("draw_card"); - appendHtmlServerMessage(tr("%1 draws %2 card(s).") + appendHtmlServerMessage(tr("%1 draws %2 card(s).", "", number) .arg(sanitizeHtml(player->getName())) .arg("" + QString::number(number) + "")); } @@ -354,10 +360,11 @@ void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCar .arg(sanitizeHtml(player->getName())) .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone))); else - appendHtmlServerMessage(tr("%1 is looking at the top %2 card(s) %3.") - .arg(sanitizeHtml(player->getName())) - .arg("" + QString::number(numberCards) + "") - .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone))); + appendHtmlServerMessage( + tr("%1 is looking at the top %3 card(s) %2.", "top card for singular, top %3 cards for plural", numberCards) + .arg(sanitizeHtml(player->getName())) + .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone)) + .arg("" + QString::number(numberCards) + "")); } void MessageLogWidget::logFlipCard(Player *player, QString cardName, bool faceDown) @@ -456,9 +463,11 @@ void MessageLogWidget::logRevealCards(Player *player, int cardId, QString cardName, Player *otherPlayer, - bool faceDown) + bool faceDown, + int amount) { - QPair temp = getFromStr(zone, cardName, cardId, false); + // getFromStr uses cardname.empty() to check if it should contain the start zone, it's not actually used + QPair temp = getFromStr(zone, amount == 1 ? cardName : QString::number(amount), cardId, false); bool cardNameContainsStartZone = false; if (!temp.first.isEmpty()) { cardNameContainsStartZone = true; @@ -467,52 +476,61 @@ void MessageLogWidget::logRevealCards(Player *player, QString fromStr = temp.second; QString cardStr; - if (cardNameContainsStartZone) + if (cardNameContainsStartZone) { cardStr = cardName; - else if (cardName.isEmpty()) - cardStr = tr("a card"); - else + } else if (cardName.isEmpty()) { + if (amount == 0) { + cardStr = tr("cards", "an unknown amount of cards"); + } else { + cardStr = tr("%1 card(s)", "a card for singular, %1 cards for plural", amount) + .arg("" + QString::number(amount) + ""); + } + } else { cardStr = cardLink(cardName); - + } if (cardId == -1) { - if (otherPlayer) + if (otherPlayer) { appendHtmlServerMessage(tr("%1 reveals %2 to %3.") .arg(sanitizeHtml(player->getName())) .arg(zone->getTranslatedName(true, CaseRevealZone)) .arg(sanitizeHtml(otherPlayer->getName()))); - else + } else { appendHtmlServerMessage(tr("%1 reveals %2.") .arg(sanitizeHtml(player->getName())) .arg(zone->getTranslatedName(true, CaseRevealZone))); + } } else if (cardId == -2) { - if (otherPlayer) + if (otherPlayer) { appendHtmlServerMessage(tr("%1 randomly reveals %2%3 to %4.") .arg(sanitizeHtml(player->getName())) .arg(cardStr) .arg(fromStr) .arg(sanitizeHtml(otherPlayer->getName()))); - else + } else { appendHtmlServerMessage( tr("%1 randomly reveals %2%3.").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); + } } else { if (faceDown && player == otherPlayer) { - if (cardName.isEmpty()) + if (cardName.isEmpty()) { appendHtmlServerMessage( tr("%1 peeks at face down card #%2.").arg(sanitizeHtml(player->getName())).arg(cardId)); - else + } else { appendHtmlServerMessage(tr("%1 peeks at face down card #%2: %3.") .arg(sanitizeHtml(player->getName())) .arg(cardId) .arg(cardStr)); - } else if (otherPlayer) + } + } else if (otherPlayer) { appendHtmlServerMessage(tr("%1 reveals %2%3 to %4.") .arg(sanitizeHtml(player->getName())) .arg(cardStr) .arg(fromStr) .arg(sanitizeHtml(otherPlayer->getName()))); - else + } else { appendHtmlServerMessage( tr("%1 reveals %2%3.").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); + } } } @@ -626,20 +644,20 @@ void MessageLogWidget::logSetCardCounter(Player *player, QString cardName, int c QString finalStr; int delta = abs(oldValue - value); if (value > oldValue) - finalStr = tr("%1 places %2 %3 counter(s) on %4 (now %5)."); + finalStr = tr("%1 places %2 %3 on %4 (now %5)."); else - finalStr = tr("%1 removes %2 %3 counter(s) from %4 (now %5)."); + finalStr = tr("%1 removes %2 %3 from %4 (now %5)."); QString colorStr; switch (counterId) { case 0: - colorStr = tr("red", "", delta); + colorStr = tr("red counter(s)", "", delta); break; case 1: - colorStr = tr("yellow", "", delta); + colorStr = tr("yellow counter(s)", "", delta); break; case 2: - colorStr = tr("green", "", delta); + colorStr = tr("green counter(s)", "", delta); break; default:; } @@ -676,13 +694,22 @@ void MessageLogWidget::logSetDoesntUntap(Player *player, CardItem *card, bool do void MessageLogWidget::logSetPT(Player *player, CardItem *card, QString newPT) { - if (currentContext == MessageContext_MoveCard) + if (currentContext == MessageContext_MoveCard) { moveCardPT.insert(card, newPT); - else - appendHtmlServerMessage(tr("%1 sets PT of %2 to %3.") - .arg(sanitizeHtml(player->getName())) - .arg(cardLink(card->getName())) - .arg(QString("%1").arg(sanitizeHtml(newPT)))); + } else { + QString name = card->getName(); + if (name.isEmpty()) { + name = QString("card #%1").arg(sanitizeHtml(QString::number(card->getId()))); + } else { + name = cardLink(name); + } + if (newPT.isEmpty()) { + appendHtmlServerMessage(tr("%1 removes the PT of %2.").arg(sanitizeHtml(player->getName())).arg(name)); + } else { + appendHtmlServerMessage( + tr("%1 sets PT of %2 to %3.").arg(sanitizeHtml(player->getName())).arg(name).arg(newPT)); + } + } } void MessageLogWidget::logSetSideboardLock(Player *player, bool locked) @@ -786,8 +813,8 @@ void MessageLogWidget::connectToPlayer(Player *player) connect(player, SIGNAL(logStopDumpZone(Player *, CardZone *)), this, SLOT(logStopDumpZone(Player *, CardZone *))); connect(player, SIGNAL(logDrawCards(Player *, int)), this, SLOT(logDrawCards(Player *, int))); connect(player, SIGNAL(logUndoDraw(Player *, QString)), this, SLOT(logUndoDraw(Player *, QString))); - connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool)), this, - SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *, bool))); + connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int)), this, + SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int))); connect(player, SIGNAL(logAlwaysRevealTopCard(Player *, CardZone *, bool)), this, SLOT(logAlwaysRevealTopCard(Player *, CardZone *, bool))); } diff --git a/cockatrice/src/messagelogwidget.h b/cockatrice/src/messagelogwidget.h index 2f6e5a02b..449df4aa9 100644 --- a/cockatrice/src/messagelogwidget.h +++ b/cockatrice/src/messagelogwidget.h @@ -48,7 +48,7 @@ private: const QString stackConstant() const; QString sanitizeHtml(QString dirty) const; - QString cardLink(const QString cardName) const; + QString cardLink(QString cardName) const; QPair getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange) const; public slots: @@ -57,6 +57,7 @@ public slots: void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal); void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName); void logConcede(Player *player); + void logUnconcede(Player *player); void logConnectionStateChanged(Player *player, bool connectionState); void logCreateArrow(Player *player, Player *startPlayer, @@ -83,8 +84,13 @@ public slots: void logMulligan(Player *player, int number); void logReplayStarted(int gameId); void logReadyStart(Player *player); - void - logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer, bool faceDown); + void logRevealCards(Player *player, + CardZone *zone, + int cardId, + QString cardName, + Player *otherPlayer, + bool faceDown, + int amount); void logRollDie(Player *player, int sides, int roll); void logSay(Player *player, QString message); void logSetActivePhase(int phase); @@ -108,7 +114,7 @@ public: MessageLogWidget(const TabSupervisor *_tabSupervisor, const UserlistProxy *_userlistProxy, TabGame *_game, - QWidget *parent = 0); + QWidget *parent = nullptr); }; #endif diff --git a/cockatrice/src/phasestoolbar.cpp b/cockatrice/src/phasestoolbar.cpp index ec3a78b93..9c474be42 100644 --- a/cockatrice/src/phasestoolbar.cpp +++ b/cockatrice/src/phasestoolbar.cpp @@ -24,14 +24,14 @@ PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_ connect(activeAnimationTimer, SIGNAL(timeout()), this, SLOT(updateAnimation())); activeAnimationTimer->setSingleShot(false); } else - activeAnimationCounter = 9.0; + activeAnimationCounter = 9; setCacheMode(DeviceCoordinateCache); } QRectF PhaseButton::boundingRect() const { - return QRectF(0, 0, width, width); + return {0, 0, width, width}; } void PhaseButton::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) @@ -39,21 +39,24 @@ void PhaseButton::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*op QRectF iconRect = boundingRect().adjusted(3, 3, -3, -3); QRectF translatedIconRect = painter->combinedTransform().mapRect(iconRect); qreal scaleFactor = translatedIconRect.width() / iconRect.width(); - QPixmap iconPixmap = PhasePixmapGenerator::generatePixmap(round(translatedIconRect.height()), name); + QPixmap iconPixmap = + PhasePixmapGenerator::generatePixmap(static_cast(round(translatedIconRect.height())), name); - painter->setBrush(QColor(220 * (activeAnimationCounter / 10.0), 220 * (activeAnimationCounter / 10.0), - 220 * (activeAnimationCounter / 10.0))); + painter->setBrush(QColor(static_cast(220 * (activeAnimationCounter / 10.0)), + static_cast(220 * (activeAnimationCounter / 10.0)), + static_cast(220 * (activeAnimationCounter / 10.0)))); painter->setPen(Qt::gray); - painter->drawRect(0, 0, width - 1, width - 1); + painter->drawRect(0, 0, static_cast(width - 1), static_cast(width - 1)); painter->save(); painter->resetTransform(); - painter->drawPixmap(iconPixmap.rect().translated(round(3 * scaleFactor), round(3 * scaleFactor)), iconPixmap, - iconPixmap.rect()); + painter->drawPixmap(iconPixmap.rect().translated(static_cast(round(3 * scaleFactor)), + static_cast(round(3 * scaleFactor))), + iconPixmap, iconPixmap.rect()); painter->restore(); - painter->setBrush(QColor(0, 0, 0, 255 * ((10 - activeAnimationCounter) / 15.0))); + painter->setBrush(QColor(0, 0, 0, static_cast(255 * ((10 - activeAnimationCounter) / 15.0)))); painter->setPen(Qt::gray); - painter->drawRect(0, 0, width - 1, width - 1); + painter->drawRect(0, 0, static_cast(width - 1), static_cast(width - 1)); } void PhaseButton::setWidth(double _width) @@ -105,9 +108,9 @@ void PhaseButton::triggerDoubleClickAction() PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) : QGraphicsItem(parent), width(100), height(100), ySpacing(1), symbolSize(8) { - QAction *aUntapAll = new QAction(this); + auto *aUntapAll = new QAction(this); connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll())); - QAction *aDrawCard = new QAction(this); + auto *aDrawCard = new QAction(this); connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard())); PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll); @@ -125,10 +128,10 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) buttonList << untapButton << upkeepButton << drawButton << main1Button << combatStartButton << combatAttackersButton << combatBlockersButton << combatDamageButton << combatEndButton << main2Button << cleanupButton; - for (int i = 0; i < buttonList.size(); ++i) - connect(buttonList[i], SIGNAL(clicked()), this, SLOT(phaseButtonClicked())); + for (auto &i : buttonList) + connect(i, SIGNAL(clicked()), this, SLOT(phaseButtonClicked())); - nextTurnButton = new PhaseButton("nextturn", this, 0, false); + nextTurnButton = new PhaseButton("nextturn", this, nullptr, false); connect(nextTurnButton, SIGNAL(clicked()), this, SLOT(actNextTurn())); rearrangeButtons(); @@ -138,7 +141,7 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) QRectF PhasesToolbar::boundingRect() const { - return QRectF(0, 0, width, height); + return {0, 0, width, height}; } void PhasesToolbar::retranslateUi() @@ -186,8 +189,8 @@ const double PhasesToolbar::marginSize = 3; void PhasesToolbar::rearrangeButtons() { - for (int i = 0; i < buttonList.size(); ++i) - buttonList[i]->setWidth(symbolSize); + for (auto &i : buttonList) + i->setWidth(symbolSize); nextTurnButton->setWidth(symbolSize); double y = marginSize; @@ -208,7 +211,7 @@ void PhasesToolbar::rearrangeButtons() buttonList[10]->setPos(marginSize, y += symbolSize); y += ySpacing; y += ySpacing; - nextTurnButton->setPos(marginSize, y += symbolSize); + nextTurnButton->setPos(marginSize, y + symbolSize); } void PhasesToolbar::setHeight(double _height) @@ -232,14 +235,21 @@ void PhasesToolbar::setActivePhase(int phase) buttonList[i]->setActive(i == phase); } +void PhasesToolbar::triggerPhaseAction(int phase) +{ + if (0 <= phase && phase < buttonList.size()) { + buttonList[phase]->triggerDoubleClickAction(); + } +} + void PhasesToolbar::phaseButtonClicked() { - PhaseButton *button = qobject_cast(sender()); + auto *button = qobject_cast(sender()); if (button->getActive()) button->triggerDoubleClickAction(); Command_SetActivePhase cmd; - cmd.set_phase(buttonList.indexOf(button)); + cmd.set_phase(static_cast(buttonList.indexOf(button))); emit sendGameCommand(cmd, -1); } diff --git a/cockatrice/src/phasestoolbar.h b/cockatrice/src/phasestoolbar.h index baa6ad914..f52f45f9d 100644 --- a/cockatrice/src/phasestoolbar.h +++ b/cockatrice/src/phasestoolbar.h @@ -27,16 +27,16 @@ private: QAction *doubleClickAction; double width; - void updatePixmap(QPixmap &pixmap); + // void updatePixmap(QPixmap &pixmap); private slots: void updateAnimation(); public: - PhaseButton(const QString &_name, - QGraphicsItem *parent = 0, - QAction *_doubleClickAction = 0, - bool _highlightable = true); - QRectF boundingRect() const; + explicit PhaseButton(const QString &_name, + QGraphicsItem *parent = nullptr, + QAction *_doubleClickAction = nullptr, + bool _highlightable = true); + QRectF boundingRect() const override; void setWidth(double _width); void setActive(bool _active); bool getActive() const @@ -48,9 +48,9 @@ signals: void clicked(); protected: - void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; }; class PhasesToolbar : public QObject, public QGraphicsItem @@ -67,8 +67,8 @@ private: void rearrangeButtons(); public: - PhasesToolbar(QGraphicsItem *parent = 0); - QRectF boundingRect() const; + explicit PhasesToolbar(QGraphicsItem *parent = nullptr); + QRectF boundingRect() const override; void retranslateUi(); void setHeight(double _height); double getWidth() const @@ -82,6 +82,7 @@ public: QString getLongPhaseName(int phase) const; public slots: void setActivePhase(int phase); + void triggerPhaseAction(int phase); private slots: void phaseButtonClicked(); void actNextTurn(); @@ -91,7 +92,7 @@ signals: void sendGameCommand(const ::google::protobuf::Message &command, int playerId); protected: - void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); + void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override; }; #endif diff --git a/cockatrice/src/pictureloader.cpp b/cockatrice/src/pictureloader.cpp index 341ad970d..cecca1de0 100644 --- a/cockatrice/src/pictureloader.cpp +++ b/cockatrice/src/pictureloader.cpp @@ -25,32 +25,14 @@ // never cache more than 300 cards at once for a single deck #define CACHED_CARD_PER_DECK_MAX 300 -class PictureToLoad::SetDownloadPriorityComparator -{ -public: - /* - * Returns true if a has higher download priority than b - * Enabled sets have priority over disabled sets - * Both groups follows the user-defined order - */ - inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const - { - if (a->getEnabled()) { - return !b->getEnabled() || a->getSortKey() < b->getSortKey(); - } else { - return !b->getEnabled() && a->getSortKey() < b->getSortKey(); - } - } -}; - PictureToLoad::PictureToLoad(CardInfoPtr _card) : card(std::move(_card)) { - /* #2479 will expand this into a list of Urls */ - urlTemplates.append(settingsCache->getPicUrl()); - urlTemplates.append(settingsCache->getPicUrlFallback()); + urlTemplates = settingsCache->downloads().getAllURLs(); if (card) { - sortedSets = card->getSets(); + for (const auto &set : card->getSets()) { + sortedSets << set.getPtr(); + } qSort(sortedSets.begin(), sortedSets.end(), SetDownloadPriorityComparator()); // The first time called, nextSet will also populate the Urls for the first set. nextSet(); @@ -72,7 +54,7 @@ void PictureToLoad::populateSetUrls() } } - foreach (QString urlTemplate, urlTemplates) { + for (const QString &urlTemplate : urlTemplates) { QString transformedUrl = transformUrl(urlTemplate); if (!transformedUrl.isEmpty()) { @@ -115,10 +97,8 @@ QString PictureToLoad::getSetName() const } } -QStringList PictureLoaderWorker::md5Blacklist = QStringList() - << "db0c48db407a907c16ade38de048a441"; // card back returned - // by gatherer when - // card is not found +// Card back returned by gatherer when card is not found +QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441"; PictureLoaderWorker::PictureLoaderWorker() : QObject(nullptr), downloadRunning(false), loadQueueRunning(false) { @@ -145,12 +125,12 @@ PictureLoaderWorker::~PictureLoaderWorker() void PictureLoaderWorker::processLoadQueue() { - if (loadQueueRunning) + if (loadQueueRunning) { return; + } loadQueueRunning = true; - forever - { + while (true) { mutex.lock(); if (loadQueue.isEmpty()) { mutex.unlock(); @@ -163,23 +143,26 @@ void PictureLoaderWorker::processLoadQueue() QString setName = cardBeingLoaded.getSetName(); QString cardName = cardBeingLoaded.getCard()->getName(); QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName(); + qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Trying to load picture"; - if (cardImageExistsOnDisk(setName, correctedCardName)) + if (cardImageExistsOnDisk(setName, correctedCardName)) { continue; + } if (picDownload) { qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Picture not found on disk, trying to download"; cardsToDownload.append(cardBeingLoaded); cardBeingLoaded.clear(); - if (!downloadRunning) + if (!downloadRunning) { startNextPicDownload(); + } } else { if (cardBeingLoaded.nextSet()) { qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Picture NOT found and download disabled, moving to next " - "set (newset: " + "set (new set: " << setName << " card: " << cardName << ")"; mutex.lock(); loadQueue.prepend(cardBeingLoaded); @@ -188,7 +171,7 @@ void PictureLoaderWorker::processLoadQueue() } else { qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Picture NOT found, download disabled, no more sets to " - "try: BAILING OUT (oldset: " + "try: BAILING OUT (old set: " << setName << " card: " << cardName << ")"; imageLoaded(cardBeingLoaded.getCard(), QImage()); } @@ -221,22 +204,22 @@ bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &corre // Iterates through the list of paths, searching for images with the desired // name with any QImageReader-supported // extension - for (int i = 0; i < picsPaths.length(); i++) { - imgReader.setFileName(picsPaths.at(i)); + for (const auto &picsPath : picsPaths) { + imgReader.setFileName(picsPath); if (imgReader.read(&image)) { qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName << "]: Picture found on disk."; imageLoaded(cardBeingLoaded.getCard(), image); return true; } - imgReader.setFileName(picsPaths.at(i) + ".full"); + imgReader.setFileName(picsPath + ".full"); if (imgReader.read(&image)) { qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName << "]: Picture.full found on disk."; imageLoaded(cardBeingLoaded.getCard(), image); return true; } - imgReader.setFileName(picsPaths.at(i) + ".xlhq"); + imgReader.setFileName(picsPath + ".xlhq"); if (imgReader.read(&image)) { qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName << "]: Picture.xlhq found on disk."; @@ -248,7 +231,7 @@ bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &corre return false; } -QString PictureToLoad::transformUrl(QString urlTemplate) const +QString PictureToLoad::transformUrl(const QString &urlTemplate) const { /* This function takes Url templates and substitutes actual card details into the url. This is used for making Urls with follow a predictable format @@ -259,29 +242,53 @@ QString PictureToLoad::transformUrl(QString urlTemplate) const CardSetPtr set = getCurrentSet(); QMap transformMap = QMap(); - + // name transformMap["!name!"] = card->getName(); transformMap["!name_lower!"] = card->getName().toLower(); transformMap["!corrected_name!"] = card->getCorrectedName(); transformMap["!corrected_name_lower!"] = card->getCorrectedName().toLower(); + // card properties + QRegExp rxCardProp("!prop:([^!]+)!"); + int pos = 0; + while ((pos = rxCardProp.indexIn(transformedUrl, pos)) != -1) { + QString propertyName = rxCardProp.cap(1); + pos += rxCardProp.matchedLength(); + QString propertyValue = card->getProperty(propertyName); + if (propertyValue.isEmpty()) { + qDebug() << "PictureLoader: [card: " << card->getName() << " set: " << getSetName() + << "]: Requested property (" << propertyName << ") for Url template (" << urlTemplate + << ") is not available"; + return QString(); + } else { + transformMap["!prop:" + propertyName + "!"] = propertyValue; + } + } + if (set) { - transformMap["!cardid!"] = QString::number(card->getMuId(set->getShortName())); - transformMap["!collectornumber!"] = card->getCollectorNumber(set->getShortName()); transformMap["!setcode!"] = set->getShortName(); transformMap["!setcode_lower!"] = set->getShortName().toLower(); transformMap["!setname!"] = set->getLongName(); transformMap["!setname_lower!"] = set->getLongName().toLower(); - } else { - transformMap["!cardid!"] = QString(); - transformMap["!collectornumber!"] = QString(); - transformMap["!setcode!"] = QString(); - transformMap["!setcode_lower!"] = QString(); - transformMap["!setname!"] = QString(); - transformMap["!setname_lower!"] = QString(); + + QRegExp rxSetProp("!set:([^!]+)!"); + pos = 0; // Defined above + while ((pos = rxSetProp.indexIn(transformedUrl, pos)) != -1) { + QString propertyName = rxSetProp.cap(1); + pos += rxSetProp.matchedLength(); + QString propertyValue = card->getSetProperty(set->getShortName(), propertyName); + if (propertyValue.isEmpty()) { + qDebug() << "PictureLoader: [card: " << card->getName() << " set: " << getSetName() + << "]: Requested set property (" << propertyName << ") for Url template (" << urlTemplate + << ") is not available"; + return QString(); + } else { + transformMap["!set:" + propertyName + "!"] = propertyValue; + } + } } - foreach (QString prop, transformMap.keys()) { + for (const QString &prop : transformMap.keys()) { if (transformedUrl.contains(prop)) { if (!transformMap[prop].isEmpty()) { transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop])); @@ -372,8 +379,8 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) return; } - const QByteArray &picData = reply->peek(reply->size()); // peek is used to keep the data in the buffer - // for use by QImageReader + // peek is used to keep the data in the buffer for use by QImageReader + const QByteArray &picData = reply->peek(reply->size()); if (imageIsBlackListed(picData)) { qDebug() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() @@ -395,8 +402,9 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) // prior to reading the // QImageReader data // into a QImage object, as that wipes the QImageReader buffer - if (extension == ".jpeg") + if (extension == ".jpeg") { extension = ".jpg"; + } if (imgReader.read(&testImage)) { QString setName = cardBeingDownloaded.getSetName(); @@ -410,8 +418,9 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) QFile newPic(picsPath + "/downloadedPics/" + setName + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + extension); - if (!newPic.open(QIODevice::WriteOnly)) + if (!newPic.open(QIODevice::WriteOnly)) { return; + } newPic.write(picData); newPic.close(); } @@ -436,15 +445,16 @@ void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card) QMutexLocker locker(&mutex); // avoid queueing the same card more than once - if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) + if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) { return; + } - foreach (PictureToLoad pic, loadQueue) { + for (const PictureToLoad &pic : loadQueue) { if (pic.getCard() == card) return; } - foreach (PictureToLoad pic, cardsToDownload) { + for (const PictureToLoad &pic : cardsToDownload) { if (pic.getCard() == card) return; } @@ -466,7 +476,7 @@ void PictureLoaderWorker::picsPathChanged() customPicsPath = settingsCache->getCustomPicsPath(); } -PictureLoader::PictureLoader() : QObject(0) +PictureLoader::PictureLoader() : QObject(nullptr) { worker = new PictureLoaderWorker; connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); @@ -493,20 +503,21 @@ void PictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size) void PictureLoader::getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size) { - if (card == nullptr) + if (card == nullptr) { return; + } - // search for an exact size copy of the picure in cache + // search for an exact size copy of the picture in cache QString key = card->getPixmapCacheKey(); - QString sizekey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height()); - if (QPixmapCache::find(sizekey, &pixmap)) + QString sizeKey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height()); + if (QPixmapCache::find(sizeKey, &pixmap)) return; // load the image and create a copy of the correct size QPixmap bigPixmap; if (QPixmapCache::find(key, &bigPixmap)) { pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - QPixmapCache::insert(sizekey, pixmap); + QPixmapCache::insert(sizeKey, pixmap); return; } @@ -532,8 +543,9 @@ void PictureLoader::imageLoaded(CardInfoPtr card, const QImage &image) void PictureLoader::clearPixmapCache(CardInfoPtr card) { - if (card) + if (card) { QPixmapCache::remove(card->getPixmapCacheKey()); + } } void PictureLoader::clearPixmapCache() @@ -546,13 +558,15 @@ void PictureLoader::cacheCardPixmaps(QList cards) QPixmap tmp; int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX); for (int i = 0; i < max; ++i) { - CardInfoPtr card = cards.at(i); - if (!card) + const CardInfoPtr &card = cards.at(i); + if (!card) { continue; + } QString key = card->getPixmapCacheKey(); - if (QPixmapCache::find(key, &tmp)) + if (QPixmapCache::find(key, &tmp)) { continue; + } getInstance().worker->enqueueImageLoad(card); } diff --git a/cockatrice/src/pictureloader.h b/cockatrice/src/pictureloader.h index b6c7a2c0a..db5757e89 100644 --- a/cockatrice/src/pictureloader.h +++ b/cockatrice/src/pictureloader.h @@ -14,7 +14,23 @@ class QThread; class PictureToLoad { private: - class SetDownloadPriorityComparator; + class SetDownloadPriorityComparator + { + public: + /* + * Returns true if a has higher download priority than b + * Enabled sets have priority over disabled sets + * Both groups follows the user-defined order + */ + inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const + { + if (a->getEnabled()) { + return !b->getEnabled() || a->getSortKey() < b->getSortKey(); + } else { + return !b->getEnabled() && a->getSortKey() < b->getSortKey(); + } + } + }; CardInfoPtr card; QList sortedSets; @@ -24,7 +40,8 @@ private: CardSetPtr currentSet; public: - PictureToLoad(CardInfoPtr _card = CardInfoPtr()); + explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr()); + CardInfoPtr getCard() const { return card; @@ -42,7 +59,7 @@ public: return currentSet; } QString getSetName() const; - QString transformUrl(QString urlTemplate) const; + QString transformUrl(const QString &urlTemplate) const; bool nextSet(); bool nextUrl(); void populateSetUrls(); @@ -52,8 +69,8 @@ class PictureLoaderWorker : public QObject { Q_OBJECT public: - PictureLoaderWorker(); - ~PictureLoaderWorker(); + explicit PictureLoaderWorker(); + ~PictureLoaderWorker() override; void enqueueImageLoad(CardInfoPtr card); @@ -70,8 +87,8 @@ private: PictureToLoad cardBeingDownloaded; bool picDownload, downloadRunning, loadQueueRunning; void startNextPicDownload(); - bool cardImageExistsOnDisk(QString &setName, QString &correctedCardname); - bool imageIsBlackListed(const QByteArray &picData); + bool cardImageExistsOnDisk(QString &, QString &); + bool imageIsBlackListed(const QByteArray &); private slots: void picDownloadFinished(QNetworkReply *reply); void picDownloadFailed(); @@ -96,8 +113,8 @@ public: } private: - PictureLoader(); - ~PictureLoader(); + explicit PictureLoader(); + ~PictureLoader() override; // Singleton - Don't implement copy constructor and assign operator PictureLoader(PictureLoader const &); void operator=(PictureLoader const &); diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp index aabb4cdb5..43a9db388 100644 --- a/cockatrice/src/player.cpp +++ b/cockatrice/src/player.cpp @@ -24,7 +24,8 @@ #include #include #include -#include +#include +#include #include #include "pb/command_attach_card.pb.h" @@ -93,7 +94,7 @@ void PlayerArea::setSize(qreal width, qreal height) Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_parent) : QObject(_parent), game(_parent), shortcutsActive(false), defaultNumberTopCards(1), defaultNumberTopCardsToPlaceBelow(1), lastTokenDestroy(true), lastTokenTableRow(0), id(_id), active(false), - local(_local), mirrored(false), handVisible(false), conceded(false), dialogSemaphore(false), deck(0) + local(_local), mirrored(false), handVisible(false), conceded(false), dialogSemaphore(false), deck(nullptr) { userInfo = new ServerInfo_User; userInfo->CopyFrom(info); @@ -114,7 +115,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare qreal h = deck->boundingRect().width() + 5; - HandCounter *handCounter = new HandCounter(playerArea); + auto *handCounter = new HandCounter(playerArea); handCounter->setPos(base + QPointF(0, h + 10)); qreal h2 = handCounter->boundingRect().height(); @@ -219,8 +220,12 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare connect(aMulligan, SIGNAL(triggered()), this, SLOT(actMulligan())); aMoveTopToPlayFaceDown = new QAction(this); connect(aMoveTopToPlayFaceDown, SIGNAL(triggered()), this, SLOT(actMoveTopCardToPlayFaceDown())); - aMoveTopCardsToGrave = new QAction(this); - connect(aMoveTopCardsToGrave, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToGrave())); + aMoveTopCardToGraveyard = new QAction(this); + connect(aMoveTopCardToGraveyard, SIGNAL(triggered()), this, SLOT(actMoveTopCardToGrave())); + aMoveTopCardToExile = new QAction(this); + connect(aMoveTopCardToExile, SIGNAL(triggered()), this, SLOT(actMoveTopCardToExile())); + aMoveTopCardsToGraveyard = new QAction(this); + connect(aMoveTopCardsToGraveyard, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToGrave())); aMoveTopCardsToExile = new QAction(this); connect(aMoveTopCardsToExile, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToExile())); aMoveTopCardToBottom = new QAction(this); @@ -266,14 +271,16 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare libraryMenu->addAction(aMoveTopCardToBottom); libraryMenu->addAction(aMoveBottomCardToGrave); libraryMenu->addSeparator(); - libraryMenu->addAction(aMoveTopCardsToGrave); + libraryMenu->addAction(aMoveTopCardToGraveyard); + libraryMenu->addAction(aMoveTopCardToExile); + libraryMenu->addAction(aMoveTopCardsToGraveyard); libraryMenu->addAction(aMoveTopCardsToExile); libraryMenu->addSeparator(); libraryMenu->addAction(aOpenDeckInDeckEditor); deck->setMenu(libraryMenu, aDrawCard); } else { - handMenu = 0; - libraryMenu = 0; + handMenu = nullptr; + libraryMenu = nullptr; } graveMenu = playerMenu->addMenu(QString()); @@ -349,19 +356,19 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare playerMenu->addSeparator(); playerMenu->addAction(aCardMenu); - for (int i = 0; i < playerLists.size(); ++i) { - QAction *newAction = playerLists[i]->addAction(QString()); + for (auto &playerList : playerLists) { + QAction *newAction = playerList->addAction(QString()); newAction->setData(-1); connect(newAction, SIGNAL(triggered()), this, SLOT(playerListActionTriggered())); allPlayersActions.append(newAction); - playerLists[i]->addSeparator(); + playerList->addSeparator(); } } else { - countersMenu = 0; - sbMenu = 0; - aCreateAnotherToken = 0; - createPredefinedTokenMenu = 0; - aCardMenu = 0; + countersMenu = nullptr; + sbMenu = nullptr; + aCreateAnotherToken = nullptr; + createPredefinedTokenMenu = nullptr; + aCardMenu = nullptr; } aTap = new QAction(this); @@ -429,11 +436,11 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare connect(aPlayFacedown, SIGNAL(triggered()), this, SLOT(actPlayFacedown())); for (int i = 0; i < 3; ++i) { - QAction *tempAddCounter = new QAction(this); + auto *tempAddCounter = new QAction(this); tempAddCounter->setData(9 + i * 1000); - QAction *tempRemoveCounter = new QAction(this); + auto *tempRemoveCounter = new QAction(this); tempRemoveCounter->setData(10 + i * 1000); - QAction *tempSetCounter = new QAction(this); + auto *tempSetCounter = new QAction(this); tempSetCounter->setData(11 + i * 1000); aAddCounter.append(tempAddCounter); aRemoveCounter.append(tempRemoveCounter); @@ -444,12 +451,12 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare } const QList &players = game->getPlayers().values(); - for (int i = 0; i < players.size(); ++i) - addPlayer(players[i]); + for (auto player : players) + addPlayer(player); rearrangeZones(); retranslateUi(); - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); } @@ -483,11 +490,12 @@ void Player::clear() void Player::addPlayer(Player *player) { - if (player == nullptr || player == this) + if (player == nullptr || player == this) { return; + } - for (int i = 0; i < playerLists.size(); ++i) { - QAction *newAction = playerLists[i]->addAction(player->getName()); + for (auto &playerList : playerLists) { + QAction *newAction = playerList->addAction(player->getName()); newAction->setData(player->getId()); connect(newAction, SIGNAL(triggered()), this, SLOT(playerListActionTriggered())); } @@ -495,37 +503,39 @@ void Player::addPlayer(Player *player) void Player::removePlayer(Player *player) { - if (player == nullptr) + if (player == nullptr) { return; + } - for (int i = 0; i < playerLists.size(); ++i) { - QList actionList = playerLists[i]->actions(); - for (int j = 0; j < actionList.size(); ++j) - if (actionList[j]->data().toInt() == player->getId()) { - playerLists[i]->removeAction(actionList[j]); - actionList[j]->deleteLater(); + for (auto &playerList : playerLists) { + QList actionList = playerList->actions(); + for (auto &j : actionList) + if (j->data().toInt() == player->getId()) { + playerList->removeAction(j); + j->deleteLater(); } } } void Player::playerListActionTriggered() { - QAction *action = static_cast(sender()); - QMenu *menu = static_cast(action->parentWidget()); + auto *action = static_cast(sender()); + auto *menu = static_cast(action->parentWidget()); Command_RevealCards cmd; const int otherPlayerId = action->data().toInt(); - if (otherPlayerId != -1) + if (otherPlayerId != -1) { cmd.set_player_id(otherPlayerId); + } if (menu == mRevealLibrary) { cmd.set_zone_name("deck"); } else if (menu == mRevealTopCard) { int decksize = zones.value("deck")->getCards().size(); bool ok; - int number = - QInputDialog::getInt(0, tr("Reveal top cards of library"), tr("Number of cards: (max. %1)").arg(decksize), - defaultNumberTopCards, 1, decksize, 1, &ok); + int number = QInputDialog::getInt(nullptr, tr("Reveal top cards of library"), + tr("Number of cards: (max. %1)").arg(decksize), defaultNumberTopCards, 1, + decksize, 1, &ok); if (ok) { cmd.set_zone_name("deck"); cmd.set_top_cards(number); @@ -533,13 +543,14 @@ void Player::playerListActionTriggered() cmd.set_card_id(0); } - } else if (menu == mRevealHand) + } else if (menu == mRevealHand) { cmd.set_zone_name("hand"); - else if (menu == mRevealRandomHandCard) { + } else if (menu == mRevealRandomHandCard) { cmd.set_zone_name("hand"); cmd.set_card_id(RANDOM_CARD_FROM_ZONE); - } else + } else { return; + } sendGameCommand(cmd); } @@ -603,9 +614,10 @@ void Player::updateBoundingRect() if (settingsCache->getHorizontalHand()) { qreal handHeight = handVisible ? hand->boundingRect().height() : 0; bRect = QRectF(0, 0, width + table->boundingRect().width(), table->boundingRect().height() + handHeight); - } else + } else { bRect = QRectF(0, 0, width + hand->boundingRect().width() + table->boundingRect().width(), table->boundingRect().height()); + } playerArea->setSize(CARD_HEIGHT + counterAreaWidth + 15, bRect.height()); emit sizeChanged(); @@ -652,7 +664,9 @@ void Player::retranslateUi() aMulligan->setText(tr("Take &mulligan")); aShuffle->setText(tr("&Shuffle")); aMoveTopToPlayFaceDown->setText(tr("Play top card &face down")); - aMoveTopCardsToGrave->setText(tr("Move top cards to &graveyard...")); + aMoveTopCardToGraveyard->setText(tr("Move top card to grave&yard")); + aMoveTopCardToExile->setText(tr("Move top card to e&xile")); + aMoveTopCardsToGraveyard->setText(tr("Move top cards to &graveyard...")); aMoveTopCardsToExile->setText(tr("Move top cards to &exile...")); aMoveTopCardToBottom->setText(tr("Put top card on &bottom")); aMoveBottomCardToGrave->setText(tr("Put bottom card &in graveyard")); @@ -678,8 +692,8 @@ void Player::retranslateUi() aCardMenu->setText(tr("C&ard")); - for (int i = 0; i < allPlayersActions.size(); ++i) - allPlayersActions[i]->setText(tr("&All players")); + for (auto &allPlayersAction : allPlayersActions) + allPlayersAction->setText(tr("&All players")); } aPlay->setText(tr("&Play")); @@ -730,52 +744,54 @@ void Player::retranslateUi() aMoveToExile->setText(tr("&Exile")); QMapIterator zoneIterator(zones); - while (zoneIterator.hasNext()) + while (zoneIterator.hasNext()) { zoneIterator.next().value()->retranslateUi(); + } } void Player::setShortcutsActive() { shortcutsActive = true; + ShortcutsSettings &shortcuts = settingsCache->shortcuts(); - aPlay->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aPlay")); - aTap->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aTap")); - aDoesntUntap->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aDoesntUntap")); - aFlip->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aFlip")); - aPeek->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aPeek")); - aClone->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aClone")); - aAttach->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aAttach")); - aUnattach->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aUnattach")); - aDrawArrow->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aDrawArrow")); - aIncP->setShortcuts(settingsCache->shortcuts().getShortcut("Player/IncP")); - aDecP->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aDecP")); - aIncT->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aIncT")); - aDecT->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aDecT")); - aIncPT->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aIncPT")); - aDecPT->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aDecPT")); - aSetPT->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aSetPT")); - aResetPT->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aResetPT")); - aSetAnnotation->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aSetAnnotation")); - aMoveToTopLibrary->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aMoveToTopLibrary")); - aMoveToBottomLibrary->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aMoveToBottomLibrary")); - aMoveToHand->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aMoveToHand")); - aMoveToGraveyard->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aMoveToGraveyard")); - aMoveToExile->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aMoveToExile")); + aPlay->setShortcuts(shortcuts.getShortcut("Player/aPlay")); + aTap->setShortcuts(shortcuts.getShortcut("Player/aTap")); + aDoesntUntap->setShortcuts(shortcuts.getShortcut("Player/aDoesntUntap")); + aFlip->setShortcuts(shortcuts.getShortcut("Player/aFlip")); + aPeek->setShortcuts(shortcuts.getShortcut("Player/aPeek")); + aClone->setShortcuts(shortcuts.getShortcut("Player/aClone")); + aAttach->setShortcuts(shortcuts.getShortcut("Player/aAttach")); + aUnattach->setShortcuts(shortcuts.getShortcut("Player/aUnattach")); + aDrawArrow->setShortcuts(shortcuts.getShortcut("Player/aDrawArrow")); + aIncP->setShortcuts(shortcuts.getShortcut("Player/aIncP")); + aDecP->setShortcuts(shortcuts.getShortcut("Player/aDecP")); + aIncT->setShortcuts(shortcuts.getShortcut("Player/aIncT")); + aDecT->setShortcuts(shortcuts.getShortcut("Player/aDecT")); + aIncPT->setShortcuts(shortcuts.getShortcut("Player/aIncPT")); + aDecPT->setShortcuts(shortcuts.getShortcut("Player/aDecPT")); + aSetPT->setShortcuts(shortcuts.getShortcut("Player/aSetPT")); + aResetPT->setShortcuts(shortcuts.getShortcut("Player/aResetPT")); + aSetAnnotation->setShortcuts(shortcuts.getShortcut("Player/aSetAnnotation")); + aMoveToTopLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToTopLibrary")); + aMoveToBottomLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToBottomLibrary")); + aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand")); + aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard")); + aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile")); QList addCCShortCuts; - addCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aCCRed")); - addCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aCCYellow")); - addCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aCCGreen")); + addCCShortCuts.append(shortcuts.getSingleShortcut("Player/aCCRed")); + addCCShortCuts.append(shortcuts.getSingleShortcut("Player/aCCYellow")); + addCCShortCuts.append(shortcuts.getSingleShortcut("Player/aCCGreen")); QList removeCCShortCuts; - removeCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aRCRed")); - removeCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aRCYellow")); - removeCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aRCGreen")); + removeCCShortCuts.append(shortcuts.getSingleShortcut("Player/aRCRed")); + removeCCShortCuts.append(shortcuts.getSingleShortcut("Player/aRCYellow")); + removeCCShortCuts.append(shortcuts.getSingleShortcut("Player/aRCGreen")); QList setCCShortCuts; - setCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aSCRed")); - setCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aSCYellow")); - setCCShortCuts.append(settingsCache->shortcuts().getSingleShortcut("Player/aSCGreen")); + setCCShortCuts.append(shortcuts.getSingleShortcut("Player/aSCRed")); + setCCShortCuts.append(shortcuts.getSingleShortcut("Player/aSCYellow")); + setCCShortCuts.append(shortcuts.getSingleShortcut("Player/aSCGreen")); for (int i = 0; i < aAddCounter.size(); ++i) { aAddCounter[i]->setShortcut(addCCShortCuts.at(i)); @@ -788,24 +804,29 @@ void Player::setShortcutsActive() } QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) + while (counterIterator.hasNext()) { counterIterator.next().value()->setShortcutsActive(); + } - aViewSideboard->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aViewSideboard")); - aViewLibrary->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aViewLibrary")); - aViewTopCards->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aViewTopCards")); - aViewGraveyard->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aViewGraveyard")); - aDrawCard->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aDrawCard")); - aDrawCards->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aDrawCards")); - aUndoDraw->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aUndoDraw")); - aMulligan->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aMulligan")); - aShuffle->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aShuffle")); - aUntapAll->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aUntapAll")); - aRollDie->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aRollDie")); - aCreateToken->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aCreateToken")); - aCreateAnotherToken->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aCreateAnotherToken")); - aAlwaysRevealTopCard->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aAlwaysRevealTopCard")); - aMoveTopToPlayFaceDown->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aMoveTopToPlayFaceDown")); + aViewSideboard->setShortcut(shortcuts.getSingleShortcut("Player/aViewSideboard")); + aViewLibrary->setShortcut(shortcuts.getSingleShortcut("Player/aViewLibrary")); + aViewTopCards->setShortcut(shortcuts.getSingleShortcut("Player/aViewTopCards")); + aViewGraveyard->setShortcut(shortcuts.getSingleShortcut("Player/aViewGraveyard")); + aDrawCard->setShortcut(shortcuts.getSingleShortcut("Player/aDrawCard")); + aDrawCards->setShortcut(shortcuts.getSingleShortcut("Player/aDrawCards")); + aUndoDraw->setShortcut(shortcuts.getSingleShortcut("Player/aUndoDraw")); + aMulligan->setShortcut(shortcuts.getSingleShortcut("Player/aMulligan")); + aShuffle->setShortcut(shortcuts.getSingleShortcut("Player/aShuffle")); + aUntapAll->setShortcut(shortcuts.getSingleShortcut("Player/aUntapAll")); + aRollDie->setShortcut(shortcuts.getSingleShortcut("Player/aRollDie")); + aCreateToken->setShortcut(shortcuts.getSingleShortcut("Player/aCreateToken")); + aCreateAnotherToken->setShortcut(shortcuts.getSingleShortcut("Player/aCreateAnotherToken")); + aAlwaysRevealTopCard->setShortcut(shortcuts.getSingleShortcut("Player/aAlwaysRevealTopCard")); + aMoveTopToPlayFaceDown->setShortcut(shortcuts.getSingleShortcut("Player/aMoveTopToPlayFaceDown")); + aMoveTopCardToGraveyard->setShortcut(shortcuts.getSingleShortcut("Player/aMoveTopCardToGraveyard")); + aMoveTopCardsToGraveyard->setShortcut(shortcuts.getSingleShortcut("Player/aMoveTopCardsToGraveyard")); + aMoveTopCardToExile->setShortcut(shortcuts.getSingleShortcut("Player/aMoveTopCardToExile")); + aMoveTopCardsToExile->setShortcut(shortcuts.getSingleShortcut("Player/aMoveTopCardsToExile")); } void Player::setShortcutsInactive() @@ -826,10 +847,16 @@ void Player::setShortcutsInactive() aCreateToken->setShortcut(QKeySequence()); aCreateAnotherToken->setShortcut(QKeySequence()); aAlwaysRevealTopCard->setShortcut(QKeySequence()); + aMoveTopToPlayFaceDown->setShortcut(QKeySequence()); + aMoveTopCardToGraveyard->setShortcut(QKeySequence()); + aMoveTopCardsToGraveyard->setShortcut(QKeySequence()); + aMoveTopCardToExile->setShortcut(QKeySequence()); + aMoveTopCardsToExile->setShortcut(QKeySequence()); QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) + while (counterIterator.hasNext()) { counterIterator.next().value()->setShortcutsInactive(); + } } void Player::initSayMenu() @@ -838,7 +865,7 @@ void Player::initSayMenu() int count = settingsCache->messages().getCount(); - for (int i = 0; i < count; i++) { + for (int i = 0; i < count; ++i) { QAction *newAction = new QAction(settingsCache->messages().getMessageAt(i), this); if (i <= 10) { newAction->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10))); @@ -877,8 +904,8 @@ void Player::actViewLibrary() void Player::actViewTopCards() { bool ok; - int number = QInputDialog::getInt(0, tr("View top cards of library"), tr("Number of cards:"), defaultNumberTopCards, - 1, 2000000000, 1, &ok); + int number = QInputDialog::getInt(nullptr, tr("View top cards of library"), tr("Number of cards:"), + defaultNumberTopCards, 1, 2000000000, 1, &ok); if (ok) { defaultNumberTopCards = number; static_cast(scene())->toggleZoneView(this, "deck", number); @@ -907,10 +934,11 @@ void Player::actViewGraveyard() void Player::actRevealRandomGraveyardCard() { Command_RevealCards cmd; - QAction *action = dynamic_cast(sender()); + auto *action = dynamic_cast(sender()); const int otherPlayerId = action->data().toInt(); - if (otherPlayerId != -1) + if (otherPlayerId != -1) { cmd.set_player_id(otherPlayerId); + } cmd.set_zone_name("grave"); cmd.set_card_id(RANDOM_CARD_FROM_ZONE); sendGameCommand(cmd); @@ -945,10 +973,10 @@ void Player::actMulligan() void Player::actDrawCards() { - int number = QInputDialog::getInt(0, tr("Draw cards"), tr("Number:")); + int number = QInputDialog::getInt(nullptr, tr("Draw cards"), tr("Number:")); if (number) { Command_DrawCards cmd; - cmd.set_number(number); + cmd.set_number(static_cast(number)); sendGameCommand(cmd); } } @@ -958,15 +986,51 @@ void Player::actUndoDraw() sendGameCommand(Command_UndoDraw()); } +void Player::actMoveTopCardToGrave() +{ + if (zones.value("deck")->getCards().empty()) { + return; + } + + Command_MoveCard cmd; + cmd.set_start_zone("deck"); + cmd.mutable_cards_to_move()->add_card()->set_card_id(0); + cmd.set_target_player_id(getId()); + cmd.set_target_zone("grave"); + cmd.set_x(0); + cmd.set_y(0); + + sendGameCommand(cmd); +} + +void Player::actMoveTopCardToExile() +{ + if (zones.value("deck")->getCards().empty()) { + return; + } + + Command_MoveCard cmd; + cmd.set_start_zone("deck"); + cmd.mutable_cards_to_move()->add_card()->set_card_id(0); + cmd.set_target_player_id(getId()); + cmd.set_target_zone("rfg"); + cmd.set_x(0); + cmd.set_y(0); + + sendGameCommand(cmd); +} + void Player::actMoveTopCardsToGrave() { - int number = QInputDialog::getInt(0, tr("Move top cards to grave"), tr("Number:")); - if (!number) + int number = QInputDialog::getInt(nullptr, tr("Move top cards to grave"), tr("Number:")); + if (!number) { return; + } const int maxCards = zones.value("deck")->getCards().size(); - if (number > maxCards) + if (number > maxCards) { number = maxCards; + } Command_MoveCard cmd; cmd.set_start_zone("deck"); @@ -975,21 +1039,24 @@ void Player::actMoveTopCardsToGrave() cmd.set_x(0); cmd.set_y(0); - for (int i = 0; i < number; ++i) + for (int i = 0; i < number; ++i) { cmd.mutable_cards_to_move()->add_card()->set_card_id(i); + } sendGameCommand(cmd); } void Player::actMoveTopCardsToExile() { - int number = QInputDialog::getInt(0, tr("Move top cards to exile"), tr("Number:")); - if (!number) + int number = QInputDialog::getInt(nullptr, tr("Move top cards to exile"), tr("Number:")); + if (!number) { return; + } const int maxCards = zones.value("deck")->getCards().size(); - if (number > maxCards) + if (number > maxCards) { number = maxCards; + } Command_MoveCard cmd; cmd.set_start_zone("deck"); @@ -998,8 +1065,9 @@ void Player::actMoveTopCardsToExile() cmd.set_x(0); cmd.set_y(0); - for (int i = 0; i < number; ++i) + for (int i = 0; i < number; ++i) { cmd.mutable_cards_to_move()->add_card()->set_card_id(i); + } sendGameCommand(cmd); } @@ -1063,7 +1131,7 @@ void Player::actRollDie() 1000, 1, &ok); if (ok) { Command_RollDie cmd; - cmd.set_sides(sides); + cmd.set_sides(static_cast(sides)); sendGameCommand(cmd); } } @@ -1071,17 +1139,19 @@ void Player::actRollDie() void Player::actCreateToken() { DlgCreateToken dlg(predefinedTokens); - if (!dlg.exec()) + if (!dlg.exec()) { return; + } lastTokenName = dlg.getName(); lastTokenPT = dlg.getPT(); CardInfoPtr correctedCard = db->getCardBySimpleName(lastTokenName); if (correctedCard) { lastTokenName = correctedCard->getName(); - lastTokenTableRow = table->clampValidTableRow(2 - correctedCard->getTableRow()); - if (lastTokenPT.isEmpty()) + lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard->getTableRow()); + if (lastTokenPT.isEmpty()) { lastTokenPT = correctedCard->getPowTough(); + } } lastTokenColor = dlg.getColor(); lastTokenAnnotation = dlg.getAnnotation(); @@ -1093,8 +1163,9 @@ void Player::actCreateToken() void Player::actCreateAnotherToken() { - if (lastTokenName.isEmpty()) + if (lastTokenName.isEmpty()) { return; + } Command_CreateToken cmd; cmd.set_zone("table"); @@ -1111,10 +1182,11 @@ void Player::actCreateAnotherToken() void Player::actCreatePredefinedToken() { - QAction *action = static_cast(sender()); + auto *action = static_cast(sender()); CardInfoPtr cardInfo = db->getCard(action->text()); - if (!cardInfo) + if (!cardInfo) { return; + } setLastToken(cardInfo); @@ -1124,9 +1196,10 @@ void Player::actCreatePredefinedToken() void Player::actCreateRelatedCard() { CardItem *sourceCard = game->getActiveCard(); - if (!sourceCard) + if (!sourceCard) { return; - QAction *action = static_cast(sender()); + } + auto *action = static_cast(sender()); // If there is a better way of passing a CardRelation through a QAction, please add it here. QList relatedCards = QList(); relatedCards.append(sourceCard->getInfo()->getRelatedCards()); @@ -1138,7 +1211,7 @@ void Player::actCreateRelatedCard() * then let's allow it to be created via "create another token" */ if (createRelatedFromRelation(sourceCard, cardRelation) && cardRelation->getCanCreateAnother()) { - CardInfoPtr cardInfo = db->getCard(dbNameFromTokenDisplayName(cardRelation->getName())); + CardInfoPtr cardInfo = db->getCard(cardRelation->getName()); setLastToken(cardInfo); } } @@ -1146,70 +1219,70 @@ void Player::actCreateRelatedCard() void Player::actCreateAllRelatedCards() { CardItem *sourceCard = game->getActiveCard(); - if (!sourceCard) + if (!sourceCard) { return; + } - QList relatedCards = QList(); - relatedCards.append(sourceCard->getInfo()->getRelatedCards()); + QList relatedCards = sourceCard->getInfo()->getRelatedCards(); relatedCards.append(sourceCard->getInfo()->getReverseRelatedCards2Me()); + if (relatedCards.empty()) { + return; + } - QList nonExcludedRelatedCards = QList(); - QString dbName; CardRelation *cardRelation = nullptr; int tokensTypesCreated = 0; - switch (relatedCards.length()) { // Is an if/elseif/else pattern better? - case 0: // if (relatedCards.length() == 0) - return; - case 1: // else if (relatedCards.length() == 1) - cardRelation = relatedCards.at(0); - if (createRelatedFromRelation(sourceCard, cardRelation)) - tokensTypesCreated++; - break; - default: // else - foreach (CardRelation *cardRelationTemp, relatedCards) { - if (!cardRelationTemp->getIsCreateAllExclusion() && !cardRelationTemp->getDoesAttach()) { - nonExcludedRelatedCards.append(cardRelationTemp); + if (relatedCards.length() == 1) { + cardRelation = relatedCards.at(0); + if (createRelatedFromRelation(sourceCard, cardRelation)) { + ++tokensTypesCreated; + } + } else { + QList nonExcludedRelatedCards; + QString dbName; + for (CardRelation *cardRelationTemp : relatedCards) { + if (!cardRelationTemp->getIsCreateAllExclusion() && !cardRelationTemp->getDoesAttach()) { + nonExcludedRelatedCards.append(cardRelationTemp); + } + } + switch (nonExcludedRelatedCards.length()) { + case 1: // if nonExcludedRelatedCards == 1 + cardRelation = nonExcludedRelatedCards.at(0); + if (createRelatedFromRelation(sourceCard, cardRelation)) { + ++tokensTypesCreated; } - } - switch (nonExcludedRelatedCards.length()) { - case 1: // if nonExcludedRelatedCards == 1 - cardRelation = nonExcludedRelatedCards.at(0); - if (createRelatedFromRelation(sourceCard, cardRelation)) - tokensTypesCreated++; - break; - // If all are marked "Exclude", then treat the situation as if none of them are. - // We won't accept "garbage in, garbage out", here. - case 0: // else if nonExcludedRelatedCards == 0 - foreach (CardRelation *cardRelationAll, relatedCards) { - if (!cardRelationAll->getDoesAttach() && !cardRelationAll->getIsVariable()) { - dbName = dbNameFromTokenDisplayName(cardRelationAll->getName()); - for (int i = 0; i < cardRelationAll->getDefaultCount(); i++) { - createCard(sourceCard, dbName); - } - tokensTypesCreated++; - if (tokensTypesCreated == 1) { - cardRelation = cardRelationAll; - } + break; + // If all are marked "Exclude", then treat the situation as if none of them are. + // We won't accept "garbage in, garbage out", here. + case 0: // else if nonExcludedRelatedCards == 0 + for (CardRelation *cardRelationAll : relatedCards) { + if (!cardRelationAll->getDoesAttach() && !cardRelationAll->getIsVariable()) { + dbName = cardRelationAll->getName(); + for (int i = 0; i < cardRelationAll->getDefaultCount(); ++i) { + createCard(sourceCard, dbName); + } + ++tokensTypesCreated; + if (tokensTypesCreated == 1) { + cardRelation = cardRelationAll; } } - break; - default: // else - foreach (CardRelation *cardRelationNotExcluded, nonExcludedRelatedCards) { - if (!cardRelationNotExcluded->getDoesAttach() && !cardRelationNotExcluded->getIsVariable()) { - dbName = dbNameFromTokenDisplayName(cardRelationNotExcluded->getName()); - for (int i = 0; i < cardRelationNotExcluded->getDefaultCount(); i++) { - createCard(sourceCard, dbName); - } - tokensTypesCreated++; - if (tokensTypesCreated == 1) { - cardRelation = cardRelationNotExcluded; - } + } + break; + default: // else + for (CardRelation *cardRelationNotExcluded : nonExcludedRelatedCards) { + if (!cardRelationNotExcluded->getDoesAttach() && !cardRelationNotExcluded->getIsVariable()) { + dbName = cardRelationNotExcluded->getName(); + for (int i = 0; i < cardRelationNotExcluded->getDefaultCount(); ++i) { + createCard(sourceCard, dbName); + } + ++tokensTypesCreated; + if (tokensTypesCreated == 1) { + cardRelation = cardRelationNotExcluded; } } - break; - } - break; + } + break; + } } /* @@ -1217,29 +1290,31 @@ void Player::actCreateAllRelatedCards() * then assign the first to the "Create another" shortcut. */ if (cardRelation != nullptr && cardRelation->getCanCreateAnother()) { - CardInfoPtr cardInfo = db->getCard(dbNameFromTokenDisplayName(cardRelation->getName())); + CardInfoPtr cardInfo = db->getCard(cardRelation->getName()); setLastToken(cardInfo); } } bool Player::createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation) { - if (sourceCard == nullptr || cardRelation == nullptr) + if (sourceCard == nullptr || cardRelation == nullptr) { return false; - QString dbName = dbNameFromTokenDisplayName(cardRelation->getName()); + } + QString dbName = cardRelation->getName(); if (cardRelation->getIsVariable()) { bool ok; dialogSemaphore = true; - int count = QInputDialog::getInt(0, tr("Create tokens"), tr("Number:"), cardRelation->getDefaultCount(), 1, - MAX_TOKENS_PER_DIALOG, 1, &ok); + int count = QInputDialog::getInt(nullptr, tr("Create tokens"), tr("Number:"), cardRelation->getDefaultCount(), + 1, MAX_TOKENS_PER_DIALOG, 1, &ok); dialogSemaphore = false; - if (!ok) + if (!ok) { return false; - for (int i = 0; i < count; i++) { + } + for (int i = 0; i < count; ++i) { createCard(sourceCard, dbName); } } else if (cardRelation->getDefaultCount() > 1) { - for (int i = 0; i < cardRelation->getDefaultCount(); i++) { + for (int i = 0; i < cardRelation->getDefaultCount(); ++i) { createCard(sourceCard, dbName); } } else { @@ -1256,32 +1331,44 @@ void Player::createCard(const CardItem *sourceCard, const QString &dbCardName, b { CardInfoPtr cardInfo = db->getCard(dbCardName); - if (cardInfo == nullptr || sourceCard == nullptr) + if (cardInfo == nullptr || sourceCard == nullptr) { return; + } // get the target token's location // TODO: Define this QPoint into its own function along with the one below - QPoint gridPoint = QPoint(-1, table->clampValidTableRow(2 - cardInfo->getTableRow())); + QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - cardInfo->getTableRow())); // create the token for the related card Command_CreateToken cmd; cmd.set_zone("table"); cmd.set_card_name(cardInfo->getName().toStdString()); - if (cardInfo->getColors().length() > 1) // Multicoloured - cmd.set_color("m"); - else - cmd.set_color(cardInfo->getColors().isEmpty() ? QString().toStdString() - : cardInfo->getColors().first().toLower().toStdString()); + switch (cardInfo->getColors().size()) { + case 0: + cmd.set_color(""); + break; + case 1: + cmd.set_color("m"); + break; + default: + cmd.set_color(cardInfo->getColors().left(1).toLower().toStdString()); + break; + } + cmd.set_pt(cardInfo->getPowTough().toStdString()); - cmd.set_annotation(settingsCache->getAnnotateTokens() ? cardInfo->getText().toStdString() - : QString().toStdString()); + if (settingsCache->getAnnotateTokens()) { + cmd.set_annotation(cardInfo->getText().toStdString()); + } else { + cmd.set_annotation(""); + } cmd.set_destroy_on_zone_change(true); cmd.set_target_zone(sourceCard->getZone()->getName().toStdString()); cmd.set_x(gridPoint.x()); cmd.set_y(gridPoint.y()); - if (attach) + if (attach) { cmd.set_target_card_id(sourceCard->getId()); + } sendGameCommand(cmd); } @@ -1293,7 +1380,7 @@ void Player::createAttachedCard(const CardItem *sourceCard, const QString &dbCar void Player::actSayMessage() { - QAction *a = qobject_cast(sender()); + auto *a = qobject_cast(sender()); Command_GameSay cmd; cmd.set_message(a->text().toStdString()); sendGameCommand(cmd); @@ -1305,29 +1392,34 @@ void Player::setCardAttrHelper(const GameEventContext &context, const QString &avalue, bool allCards) { - if (card == nullptr) + if (card == nullptr) { return; + } bool moveCardContext = context.HasExtension(Context_MoveCard::ext); switch (attribute) { case AttrTapped: { bool tapped = avalue == "1"; if (!(!tapped && card->getDoesntUntap() && allCards)) { - if (!allCards) + if (!allCards) { emit logSetTapped(this, card, tapped); + } card->setTapped(tapped, !moveCardContext); } break; } - case AttrAttacking: + case AttrAttacking: { card->setAttacking(avalue == "1"); break; - case AttrFaceDown: + } + case AttrFaceDown: { card->setFaceDown(avalue == "1"); break; - case AttrColor: + } + case AttrColor: { card->setColor(avalue); break; + } case AttrAnnotation: { emit logSetAnnotation(this, card, avalue); card->setAnnotation(avalue); @@ -1347,23 +1439,6 @@ void Player::setCardAttrHelper(const GameEventContext &context, } } -// token names take the form of " / " or " ". -// dbName for tokens should take the form of " ". -// trailing whitespace is significant; it is hacked on at the end as an additional identifier in our single key database -QString Player::dbNameFromTokenDisplayName(const QString &tokenName) -{ - QRegExp tokenNamePattern(".*/\\S+\\s+(.*)"); - - int index = tokenNamePattern.indexIn(tokenName); - if (index != -1) { - return tokenNamePattern.capturedTexts()[1]; - } else if (tokenName.indexOf(tr("Token: ")) != -1) { - return tokenName.mid(tr("Token: ").length()); - } else { - return tokenName; - } -} - void Player::eventGameSay(const Event_GameSay &event) { emit logSay(this, QString::fromStdString(event.message())); @@ -1372,11 +1447,12 @@ void Player::eventGameSay(const Event_GameSay &event) void Player::eventShuffle(const Event_Shuffle &event) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name())); - if (!zone) + if (!zone) { return; - if (zone->getView()) - if (zone->getView()->getRevealZone()) - zone->getView()->setWriteableRevealZone(false); + } + if (zone->getView() && zone->getView()->getRevealZone()) { + zone->getView()->setWriteableRevealZone(false); + } emit logShuffle(this, zone); } @@ -1388,17 +1464,19 @@ void Player::eventRollDie(const Event_RollDie &event) void Player::eventCreateArrow(const Event_CreateArrow &event) { ArrowItem *arrow = addArrow(event.arrow_info()); - if (!arrow) + if (!arrow) { return; + } - CardItem *startCard = static_cast(arrow->getStartItem()); - CardItem *targetCard = qgraphicsitem_cast(arrow->getTargetItem()); - if (targetCard) + auto *startCard = static_cast(arrow->getStartItem()); + auto *targetCard = qgraphicsitem_cast(arrow->getTargetItem()); + if (targetCard) { emit logCreateArrow(this, startCard->getOwner(), startCard->getName(), targetCard->getOwner(), targetCard->getName(), false); - else + } else { emit logCreateArrow(this, startCard->getOwner(), startCard->getName(), arrow->getTargetItem()->getOwner(), QString(), true); + } } void Player::eventDeleteArrow(const Event_DeleteArrow &event) @@ -1409,8 +1487,9 @@ void Player::eventDeleteArrow(const Event_DeleteArrow &event) void Player::eventCreateToken(const Event_CreateToken &event) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); - if (!zone) + if (!zone) { return; + } CardItem *card = new CardItem(this, QString::fromStdString(event.card_name()), event.card_id()); // use db PT if not provided in event @@ -1418,8 +1497,9 @@ void Player::eventCreateToken(const Event_CreateToken &event) card->setPT(QString::fromStdString(event.pt())); } else { CardInfoPtr dbCard = db->getCard(QString::fromStdString(event.card_name())); - if (dbCard) + if (dbCard) { card->setPT(dbCard->getPowTough()); + } } card->setColor(QString::fromStdString(event.color())); card->setAnnotation(QString::fromStdString(event.annotation())); @@ -1432,16 +1512,19 @@ void Player::eventCreateToken(const Event_CreateToken &event) void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); - if (!zone) + if (!zone) { return; + } if (!event.has_card_id()) { const CardList &cards = zone->getCards(); - for (int i = 0; i < cards.size(); i++) + for (int i = 0; i < cards.size(); ++i) { setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), true); - if (event.attribute() == AttrTapped) - emit logSetTapped(this, 0, event.attr_value() == "1"); + } + if (event.attribute() == AttrTapped) { + emit logSetTapped(this, nullptr, event.attr_value() == "1"); + } } else { CardItem *card = zone->getCard(event.card_id(), QString()); if (!card) { @@ -1455,12 +1538,14 @@ void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventCon void Player::eventSetCardCounter(const Event_SetCardCounter &event) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); - if (!zone) + if (!zone) { return; + } CardItem *card = zone->getCard(event.card_id(), QString()); - if (!card) + if (!card) { return; + } int oldValue = card->getCounters().value(event.counter_id(), 0); card->setCounter(event.counter_id(), event.counter_value()); @@ -1474,12 +1559,12 @@ void Player::eventCreateCounter(const Event_CreateCounter &event) void Player::eventSetCounter(const Event_SetCounter &event) { - AbstractCounter *c = counters.value(event.counter_id(), 0); - if (!c) + AbstractCounter *ctr = counters.value(event.counter_id(), 0); + if (!ctr) return; - int oldValue = c->getValue(); - c->setValue(event.value()); - emit logSetCounter(this, c->getName(), event.value(), oldValue); + int oldValue = ctr->getValue(); + ctr->setValue(event.value()); + emit logSetCounter(this, ctr->getName(), event.value(), oldValue); } void Player::eventDelCounter(const Event_DelCounter &event) @@ -1490,41 +1575,49 @@ void Player::eventDelCounter(const Event_DelCounter &event) void Player::eventDumpZone(const Event_DumpZone &event) { Player *zoneOwner = game->getPlayers().value(event.zone_owner_id(), 0); - if (!zoneOwner) + if (!zoneOwner) { return; + } CardZone *zone = zoneOwner->getZones().value(QString::fromStdString(event.zone_name()), 0); - if (!zone) + if (!zone) { return; + } emit logDumpZone(this, zone, event.number_cards()); } void Player::eventStopDumpZone(const Event_StopDumpZone &event) { Player *zoneOwner = game->getPlayers().value(event.zone_owner_id(), 0); - if (!zoneOwner) + if (!zoneOwner) { return; + } CardZone *zone = zoneOwner->getZones().value(QString::fromStdString(event.zone_name()), 0); - if (!zone) + if (!zone) { return; + } emit logStopDumpZone(this, zone); } void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext &context) { Player *startPlayer = game->getPlayers().value(event.start_player_id()); - if (!startPlayer) + if (!startPlayer) { return; + } CardZone *startZone = startPlayer->getZones().value(QString::fromStdString(event.start_zone()), 0); Player *targetPlayer = game->getPlayers().value(event.target_player_id()); - if (!targetPlayer) + if (!targetPlayer) { return; + } CardZone *targetZone; - if (event.has_target_zone()) + if (event.has_target_zone()) { targetZone = targetPlayer->getZones().value(QString::fromStdString(event.target_zone()), 0); - else + } else { targetZone = startZone; - if (!startZone || !targetZone) + } + if (!startZone || !targetZone) { return; + } int position = event.position(); int x = event.x(); @@ -1532,19 +1625,23 @@ void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext & int logPosition = position; int logX = x; - if (x == -1) + if (x == -1) { x = 0; + } CardItem *card = startZone->takeCard(position, event.card_id(), startZone != targetZone); - if (!card) + if (!card) { return; - if (startZone != targetZone) + } + if (startZone != targetZone) { card->deleteCardInfoPopup(); - if (event.has_card_name()) + } + if (event.has_card_name()) { card->setName(QString::fromStdString(event.card_name())); + } if (card->getAttachedTo() && (startZone != targetZone)) { CardItem *parentCard = card->getAttachedTo(); - card->setAttachedTo(0); + card->setAttachedTo(nullptr); parentCard->getZone()->reorganizeCards(); } @@ -1557,19 +1654,22 @@ void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext & card->setHovered(false); const QList &attachedCards = card->getAttachedCards(); - for (int i = 0; i < attachedCards.size(); ++i) - attachedCards[i]->setParentItem(targetZone); + for (auto attachedCard : attachedCards) { + attachedCard->setParentItem(targetZone); + } - if (startZone->getPlayer() != targetZone->getPlayer()) + if (startZone->getPlayer() != targetZone->getPlayer()) { card->setOwner(targetZone->getPlayer()); + } } // The log event has to be sent before the card is added to the target zone // because the addCard function can modify the card object. - if (context.HasExtension(Context_UndoDraw::ext)) + if (context.HasExtension(Context_UndoDraw::ext)) { emit logUndoDraw(this, card->getName()); - else + } else { emit logMoveCard(this, card, startZone, logPosition, targetZone, logX); + } targetZone->addCard(card, true, x, y); @@ -1584,25 +1684,29 @@ void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext & while (arrowIterator.hasNext()) { ArrowItem *arrow = arrowIterator.next().value(); if ((arrow->getStartItem() == card) || (arrow->getTargetItem() == card)) { - if (startZone == targetZone) + if (startZone == targetZone) { arrow->updatePath(); - else + } else { arrowsToDelete.append(arrow); + } } } - for (int i = 0; i < arrowsToDelete.size(); ++i) - arrowsToDelete[i]->delArrow(); + for (auto &i : arrowsToDelete) { + i->delArrow(); + } } } void Player::eventFlipCard(const Event_FlipCard &event) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); - if (!zone) + if (!zone) { return; + } CardItem *card = zone->getCard(event.card_id(), QString::fromStdString(event.card_name())); - if (!card) + if (!card) { return; + } emit logFlipCard(this, card->getName(), event.face_down()); card->setFaceDown(event.face_down()); } @@ -1610,17 +1714,20 @@ void Player::eventFlipCard(const Event_FlipCard &event) void Player::eventDestroyCard(const Event_DestroyCard &event) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); - if (!zone) + if (!zone) { return; + } CardItem *card = zone->getCard(event.card_id(), QString()); - if (!card) + if (!card) { return; + } QList attachedCards = card->getAttachedCards(); // This list is always empty except for buggy server implementations. - for (int i = 0; i < attachedCards.size(); ++i) - attachedCards[i]->setAttachedTo(0); + for (auto &attachedCard : attachedCards) { + attachedCard->setAttachedTo(0); + } emit logDestroyCard(this, card->getName()); zone->takeCard(-1, event.card_id(), true); @@ -1630,40 +1737,46 @@ void Player::eventDestroyCard(const Event_DestroyCard &event) void Player::eventAttachCard(const Event_AttachCard &event) { const QMap &playerList = game->getPlayers(); - Player *targetPlayer = 0; - CardZone *targetZone = 0; - CardItem *targetCard = 0; + Player *targetPlayer = nullptr; + CardZone *targetZone = nullptr; + CardItem *targetCard = nullptr; if (event.has_target_player_id()) { targetPlayer = playerList.value(event.target_player_id(), 0); if (targetPlayer) { targetZone = targetPlayer->getZones().value(QString::fromStdString(event.target_zone()), 0); - if (targetZone) + if (targetZone) { targetCard = targetZone->getCard(event.target_card_id(), QString()); + } } } CardZone *startZone = getZones().value(QString::fromStdString(event.start_zone()), 0); - if (!startZone) + if (!startZone) { return; + } CardItem *startCard = startZone->getCard(event.card_id(), QString()); - if (!startCard) + if (!startCard) { return; + } CardItem *oldParent = startCard->getAttachedTo(); startCard->setAttachedTo(targetCard); startZone->reorganizeCards(); - if ((startZone != targetZone) && targetZone) + if ((startZone != targetZone) && targetZone) { targetZone->reorganizeCards(); - if (oldParent) + } + if (oldParent) { oldParent->getZone()->reorganizeCards(); + } - if (targetCard) + if (targetCard) { emit logAttachCard(this, startCard->getName(), targetPlayer, targetCard->getName()); - else + } else { emit logUnattachCard(this, startCard->getName()); + } } void Player::eventDrawCards(const Event_DrawCards &event) @@ -1681,8 +1794,9 @@ void Player::eventDrawCards(const Event_DrawCards &event) } } else { const int number = event.number(); - for (int i = 0; i < number; ++i) + for (int i = 0; i < number; ++i) { hand->addCard(deck->takeCard(0, -1), false, -1); + } } hand->reorganizeCards(); @@ -1693,13 +1807,15 @@ void Player::eventDrawCards(const Event_DrawCards &event) void Player::eventRevealCards(const Event_RevealCards &event) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name())); - if (!zone) + if (!zone) { return; - Player *otherPlayer = 0; + } + Player *otherPlayer = nullptr; if (event.has_other_player_id()) { otherPlayer = game->getPlayers().value(event.other_player_id()); - if (!otherPlayer) + if (!otherPlayer) { return; + } } bool peeking = false; @@ -1707,19 +1823,21 @@ void Player::eventRevealCards(const Event_RevealCards &event) const int cardListSize = event.cards_size(); for (int i = 0; i < cardListSize; ++i) { const ServerInfo_Card *temp = &event.cards(i); - if (temp->face_down()) + if (temp->face_down()) { peeking = true; + } cardList.append(temp); } if (peeking) { - for (int i = 0; i < cardList.size(); ++i) { - QString cardName = QString::fromStdString(cardList.at(i)->name()); - CardItem *card = zone->getCard(cardList.at(i)->id(), QString()); - if (!card) + for (auto &card : cardList) { + QString cardName = QString::fromStdString(card->name()); + CardItem *cardItem = zone->getCard(card->id(), QString()); + if (!cardItem) { continue; - card->setName(cardName); - emit logRevealCards(this, zone, cardList.at(i)->id(), cardName, this, true); + } + cardItem->setName(cardName); + emit logRevealCards(this, zone, card->id(), cardName, this, true, 1); } } else { bool showZoneView = true; @@ -1732,18 +1850,20 @@ void Player::eventRevealCards(const Event_RevealCards &event) showZoneView = false; } } - if (showZoneView && !cardList.isEmpty()) + if (showZoneView && !cardList.isEmpty()) { static_cast(scene())->addRevealedZoneView(this, zone, cardList, event.grant_write_access()); + } - emit logRevealCards(this, zone, event.card_id(), cardName, otherPlayer, false); + emit logRevealCards(this, zone, event.card_id(), cardName, otherPlayer, false, cardList.size()); } } void Player::eventChangeZoneProperties(const Event_ChangeZoneProperties &event) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name())); - if (!zone) + if (!zone) { return; + } if (event.has_always_reveal_top_card()) { zone->setAlwaysRevealTopCard(event.always_reveal_top_card()); @@ -1842,20 +1962,23 @@ void Player::processPlayerInfo(const ServerInfo_Player &info) clearArrows(); QMapIterator zoneIt(zones); - while (zoneIt.hasNext()) + while (zoneIt.hasNext()) { zoneIt.next().value()->clearContents(); + } const int zoneListSize = info.zone_list_size(); for (int i = 0; i < zoneListSize; ++i) { const ServerInfo_Zone &zoneInfo = info.zone_list(i); CardZone *zone = zones.value(QString::fromStdString(zoneInfo.name()), 0); - if (!zone) + if (!zone) { continue; + } const int cardListSize = zoneInfo.card_list_size(); if (!cardListSize) { - for (int j = 0; j < zoneInfo.card_count(); ++j) + for (int j = 0; j < zoneInfo.card_count(); ++j) { zone->addCard(new CardItem(this), false, -1); + } } else { for (int j = 0; j < cardListSize; ++j) { const ServerInfo_Card &cardInfo = zoneInfo.card_list(j); @@ -1864,15 +1987,17 @@ void Player::processPlayerInfo(const ServerInfo_Player &info) zone->addCard(card, false, cardInfo.x(), cardInfo.y()); } } - if (zoneInfo.has_always_reveal_top_card()) + if (zoneInfo.has_always_reveal_top_card()) { zone->setAlwaysRevealTopCard(zoneInfo.always_reveal_top_card()); + } zone->reorganizeCards(); } const int counterListSize = info.counter_list_size(); - for (int i = 0; i < counterListSize; ++i) + for (int i = 0; i < counterListSize; ++i) { addCounter(info.counter_list(i)); + } setConceded(info.properties().conceded()); } @@ -1883,8 +2008,9 @@ void Player::processCardAttachment(const ServerInfo_Player &info) for (int i = 0; i < zoneListSize; ++i) { const ServerInfo_Zone &zoneInfo = info.zone_list(i); CardZone *zone = zones.value(QString::fromStdString(zoneInfo.name()), 0); - if (!zone) + if (!zone) { continue; + } const int cardListSize = zoneInfo.card_list_size(); for (int j = 0; j < cardListSize; ++j) { @@ -1894,8 +2020,9 @@ void Player::processCardAttachment(const ServerInfo_Player &info) CardItem *targetCard = game->getCard(cardInfo.attach_player_id(), QString::fromStdString(cardInfo.attach_zone()), cardInfo.attach_card_id()); - if (!targetCard) + if (!targetCard) { continue; + } startCard->setAttachedTo(targetCard); } @@ -1903,36 +2030,46 @@ void Player::processCardAttachment(const ServerInfo_Player &info) } const int arrowListSize = info.arrow_list_size(); - for (int i = 0; i < arrowListSize; ++i) + for (int i = 0; i < arrowListSize; ++i) { addArrow(info.arrow_list(i)); + } } -void Player::playCard(CardItem *c, bool faceDown, bool tapped) +void Player::playCard(CardItem *card, bool faceDown, bool tapped) { - if (c == nullptr) + if (card == nullptr) { return; + } Command_MoveCard cmd; - cmd.set_start_player_id(c->getZone()->getPlayer()->getId()); - cmd.set_start_zone(c->getZone()->getName().toStdString()); + cmd.set_start_player_id(card->getZone()->getPlayer()->getId()); + cmd.set_start_zone(card->getZone()->getName().toStdString()); cmd.set_target_player_id(getId()); CardToMove *cardToMove = cmd.mutable_cards_to_move()->add_card(); - cardToMove->set_card_id(c->getId()); + cardToMove->set_card_id(card->getId()); - CardInfoPtr ci = c->getInfo(); - if (!ci) + CardInfoPtr info = card->getInfo(); + if (!info) { return; - if (!faceDown && ((!settingsCache->getPlayToStack() && ci->getTableRow() == 3) || - ((settingsCache->getPlayToStack() && ci->getTableRow() != 0) && - c->getZone()->getName().toStdString() != "stack"))) { + } + + int tableRow = info->getTableRow(); + bool playToStack = settingsCache->getPlayToStack(); + QString currentZone = card->getZone()->getName(); + if (currentZone == "stack" && tableRow == 3) { + cmd.set_target_zone("grave"); + cmd.set_x(0); + cmd.set_y(0); + } else if (!faceDown && + ((!playToStack && tableRow == 3) || ((playToStack && tableRow != 0) && currentZone != "stack"))) { cmd.set_target_zone("stack"); cmd.set_x(0); cmd.set_y(0); } else { - int tableRow = faceDown ? 2 : ci->getTableRow(); - QPoint gridPoint = QPoint(-1, table->clampValidTableRow(2 - tableRow)); + tableRow = faceDown ? 2 : info->getTableRow(); + QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow)); cardToMove->set_face_down(faceDown); - cardToMove->set_pt(ci->getPowTough().toStdString()); + cardToMove->set_pt(info->getPowTough().toStdString()); cardToMove->set_tapped(faceDown ? false : tapped); if (tableRow != 3) cmd.set_target_zone("table"); @@ -1942,25 +2079,25 @@ void Player::playCard(CardItem *c, bool faceDown, bool tapped) sendGameCommand(cmd); } -void Player::addCard(CardItem *c) +void Player::addCard(CardItem *card) { - emit newCardAdded(c); + emit newCardAdded(card); } -void Player::deleteCard(CardItem *c) +void Player::deleteCard(CardItem *card) { - if (c == nullptr) { + if (card == nullptr) { return; } else if (dialogSemaphore) { - cardsToDelete.append(c); + cardsToDelete.append(card); } else { - c->deleteLater(); + card->deleteLater(); } } -void Player::addZone(CardZone *z) +void Player::addZone(CardZone *zone) { - zones.insert(z->getName(), z); + zones.insert(zone->getName(), zone); } AbstractCounter *Player::addCounter(const ServerInfo_Counter &counter) @@ -1972,30 +2109,35 @@ AbstractCounter *Player::addCounter(const ServerInfo_Counter &counter) AbstractCounter *Player::addCounter(int counterId, const QString &name, QColor color, int radius, int value) { qDebug() << "addCounter:" << getName() << counterId << name; - if (counters.contains(counterId)) - return 0; + if (counters.contains(counterId)) { + return nullptr; + } - AbstractCounter *c; - if (name == "life") - c = playerTarget->addCounter(counterId, name, value); - else - c = new GeneralCounter(this, counterId, name, color, radius, value, true, this); - counters.insert(counterId, c); - if (countersMenu) - countersMenu->addMenu(c->getMenu()); - if (shortcutsActive) - c->setShortcutsActive(); + AbstractCounter *ctr; + if (name == "life") { + ctr = playerTarget->addCounter(counterId, name, value); + } else { + ctr = new GeneralCounter(this, counterId, name, color, radius, value, true, this); + } + counters.insert(counterId, ctr); + if (countersMenu) { + countersMenu->addMenu(ctr->getMenu()); + } + if (shortcutsActive) { + ctr->setShortcutsActive(); + } rearrangeCounters(); - return c; + return ctr; } void Player::delCounter(int counterId) { - AbstractCounter *c = counters.value(counterId, 0); - if (!c) + AbstractCounter *ctr = counters.value(counterId, 0); + if (!ctr) { return; + } - c->delCounter(); + ctr->delCounter(); counters.remove(counterId); rearrangeCounters(); } @@ -2003,8 +2145,9 @@ void Player::delCounter(int counterId) void Player::clearCounters() { QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) + while (counterIterator.hasNext()) { counterIterator.next().value()->delCounter(); + } counters.clear(); } @@ -2013,33 +2156,39 @@ ArrowItem *Player::addArrow(const ServerInfo_Arrow &arrow) const QMap &playerList = game->getPlayers(); Player *startPlayer = playerList.value(arrow.start_player_id(), 0); Player *targetPlayer = playerList.value(arrow.target_player_id(), 0); - if (!startPlayer || !targetPlayer) - return 0; + if (!startPlayer || !targetPlayer) { + return nullptr; + } CardZone *startZone = startPlayer->getZones().value(QString::fromStdString(arrow.start_zone()), 0); - CardZone *targetZone = 0; - if (arrow.has_target_zone()) + CardZone *targetZone = nullptr; + if (arrow.has_target_zone()) { targetZone = targetPlayer->getZones().value(QString::fromStdString(arrow.target_zone()), 0); - if (!startZone || (!targetZone && arrow.has_target_zone())) - return 0; + } + if (!startZone || (!targetZone && arrow.has_target_zone())) { + return nullptr; + } CardItem *startCard = startZone->getCard(arrow.start_card_id(), QString()); - CardItem *targetCard = 0; - if (targetZone) + CardItem *targetCard = nullptr; + if (targetZone) { targetCard = targetZone->getCard(arrow.target_card_id(), QString()); - if (!startCard || (!targetCard && arrow.has_target_card_id())) - return 0; + } + if (!startCard || (!targetCard && arrow.has_target_card_id())) { + return nullptr; + } - if (targetCard) + if (targetCard) { return addArrow(arrow.id(), startCard, targetCard, convertColorToQColor(arrow.arrow_color())); - else + } else { return addArrow(arrow.id(), startCard, targetPlayer->getPlayerTarget(), convertColorToQColor(arrow.arrow_color())); + } } ArrowItem *Player::addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color) { - ArrowItem *arrow = new ArrowItem(this, arrowId, startCard, targetItem, color); + auto *arrow = new ArrowItem(this, arrowId, startCard, targetItem, color); arrows.insert(arrowId, arrow); scene()->addItem(arrow); return arrow; @@ -2047,23 +2196,26 @@ ArrowItem *Player::addArrow(int arrowId, CardItem *startCard, ArrowTarget *targe void Player::delArrow(int arrowId) { - ArrowItem *a = arrows.value(arrowId, 0); - if (!a) + ArrowItem *arr = arrows.value(arrowId, 0); + if (!arr) { return; - a->delArrow(); + } + arr->delArrow(); } void Player::removeArrow(ArrowItem *arrow) { - if (arrow->getId() != -1) + if (arrow->getId() != -1) { arrows.remove(arrow->getId()); + } } void Player::clearArrows() { QMapIterator arrowIterator(arrows); - while (arrowIterator.hasNext()) + while (arrowIterator.hasNext()) { arrowIterator.next().value()->delArrow(); + } arrows.clear(); } @@ -2076,23 +2228,25 @@ void Player::rearrangeCounters() QMapIterator counterIterator(counters); while (counterIterator.hasNext()) { counterIterator.next(); - if (counterIterator.value()->getShownInCounterArea()) + if (counterIterator.value()->getShownInCounterArea()) { totalHeight += counterIterator.value()->boundingRect().height(); + } } const qreal padding = 5; - qreal y = boundingRect().y() + marginTop; + qreal ySize = boundingRect().y() + marginTop; // Place objects for (counterIterator.toFront(); counterIterator.hasNext();) { - AbstractCounter *c = counterIterator.next().value(); + AbstractCounter *ctr = counterIterator.next().value(); - if (!c->getShownInCounterArea()) + if (!ctr->getShownInCounterArea()) { continue; + } - QRectF br = c->boundingRect(); - c->setPos((counterAreaWidth - br.width()) / 2, y); - y += br.height() + padding; + QRectF br = ctr->boundingRect(); + ctr->setPos((counterAreaWidth - br.width()) / 2, ySize); + ySize += br.height() + padding; } } @@ -2118,8 +2272,9 @@ void Player::sendGameCommand(PendingCommand *pend) bool Player::clearCardsToDelete() { - if (cardsToDelete.isEmpty()) + if (cardsToDelete.isEmpty()) { return false; + } for (auto &i : cardsToDelete) { if (i != nullptr) { @@ -2134,33 +2289,37 @@ bool Player::clearCardsToDelete() void Player::actMoveCardXCardsFromTop() { bool ok; - int number = QInputDialog::getInt(0, tr("Place card X cards from top of library"), + int number = QInputDialog::getInt(nullptr, tr("Place card X cards from top of library"), tr("How many cards from the top of the deck should this card be placed:"), defaultNumberTopCardsToPlaceBelow, 1, 2000000000, 1, &ok); number--; - if (!ok) + if (!ok) { return; + } defaultNumberTopCardsToPlaceBelow = number; QList sel = scene()->selectedItems(); QList cardList; - while (!sel.isEmpty()) + while (!sel.isEmpty()) { cardList.append(qgraphicsitem_cast(sel.takeFirst())); + } QList commandList; ListOfCardsToMove idList; - for (auto &i : cardList) + for (auto &i : cardList) { idList.add_card()->set_card_id(i->getId()); + } - if (cardList.isEmpty()) + if (cardList.isEmpty()) { return; + } int startPlayerId = cardList[0]->getZone()->getPlayer()->getId(); QString startZone = cardList[0]->getZone()->getName(); - Command_MoveCard *cmd = new Command_MoveCard; + auto *cmd = new Command_MoveCard; cmd->set_start_player_id(startPlayerId); cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); @@ -2170,29 +2329,31 @@ void Player::actMoveCardXCardsFromTop() cmd->set_y(0); commandList.append(cmd); - if (local) + if (local) { sendGameCommand(prepareGameCommand(commandList)); - else + } else { game->sendGameCommand(prepareGameCommand(commandList)); + } } void Player::cardMenuAction() { - QAction *a = dynamic_cast(sender()); + auto *a = dynamic_cast(sender()); QList sel = scene()->selectedItems(); QList cardList; - while (!sel.isEmpty()) + while (!sel.isEmpty()) { cardList.append(qgraphicsitem_cast(sel.takeFirst())); + } QList commandList; if (a->data().toInt() <= (int)cmClone) { - for (int i = 0; i < cardList.size(); ++i) { - CardItem *card = cardList[i]; + for (auto card : cardList) { switch (static_cast(a->data().toInt())) { // Leaving both for compatibility with server case cmUntap: + // fallthrough case cmTap: { - Command_SetCardAttr *cmd = new Command_SetCardAttr; + auto *cmd = new Command_SetCardAttr; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_attribute(AttrTapped); @@ -2201,7 +2362,7 @@ void Player::cardMenuAction() break; } case cmDoesntUntap: { - Command_SetCardAttr *cmd = new Command_SetCardAttr; + auto *cmd = new Command_SetCardAttr; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_attribute(AttrDoesntUntap); @@ -2210,20 +2371,21 @@ void Player::cardMenuAction() break; } case cmFlip: { - Command_FlipCard *cmd = new Command_FlipCard; + auto *cmd = new Command_FlipCard; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_face_down(!card->getFaceDown()); if (card->getFaceDown()) { CardInfoPtr ci = card->getInfo(); - if (ci) + if (ci) { cmd->set_pt(ci->getPowTough().toStdString()); + } } commandList.append(cmd); break; } case cmPeek: { - Command_RevealCards *cmd = new Command_RevealCards; + auto *cmd = new Command_RevealCards; cmd->set_zone_name(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_player_id(id); @@ -2231,7 +2393,7 @@ void Player::cardMenuAction() break; } case cmClone: { - Command_CreateToken *cmd = new Command_CreateToken; + auto *cmd = new Command_CreateToken; cmd->set_zone("table"); cmd->set_card_name(card->getName().toStdString()); cmd->set_color(card->getColor().toStdString()); @@ -2249,15 +2411,15 @@ void Player::cardMenuAction() } } else { ListOfCardsToMove idList; - for (int i = 0; i < cardList.size(); i++) { - idList.add_card()->set_card_id(cardList[i]->getId()); + for (auto &i : cardList) { + idList.add_card()->set_card_id(i->getId()); } int startPlayerId = cardList[0]->getZone()->getPlayer()->getId(); QString startZone = cardList[0]->getZone()->getName(); switch (static_cast(a->data().toInt())) { case cmMoveToTopLibrary: { - Command_MoveCard *cmd = new Command_MoveCard; + auto *cmd = new Command_MoveCard; cmd->set_start_player_id(startPlayerId); cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); @@ -2269,7 +2431,7 @@ void Player::cardMenuAction() break; } case cmMoveToBottomLibrary: { - Command_MoveCard *cmd = new Command_MoveCard; + auto *cmd = new Command_MoveCard; cmd->set_start_player_id(startPlayerId); cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); @@ -2281,7 +2443,7 @@ void Player::cardMenuAction() break; } case cmMoveToHand: { - Command_MoveCard *cmd = new Command_MoveCard; + auto *cmd = new Command_MoveCard; cmd->set_start_player_id(startPlayerId); cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); @@ -2293,7 +2455,7 @@ void Player::cardMenuAction() break; } case cmMoveToGraveyard: { - Command_MoveCard *cmd = new Command_MoveCard; + auto *cmd = new Command_MoveCard; cmd->set_start_player_id(startPlayerId); cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); @@ -2305,7 +2467,7 @@ void Player::cardMenuAction() break; } case cmMoveToExile: { - Command_MoveCard *cmd = new Command_MoveCard; + auto *cmd = new Command_MoveCard; cmd->set_start_player_id(startPlayerId); cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); @@ -2336,16 +2498,17 @@ void Player::actIncPT(int deltaP, int deltaT) QList commandList; QListIterator j(scene()->selectedItems()); while (j.hasNext()) { - CardItem *card = static_cast(j.next()); - Command_SetCardAttr *cmd = new Command_SetCardAttr; + auto *card = static_cast(j.next()); + auto *cmd = new Command_SetCardAttr; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_attribute(AttrPT); cmd->set_attr_value(ptString.toStdString()); commandList.append(cmd); - if (local) + if (local) { playerid = card->getZone()->getPlayer()->getId(); + } } game->sendGameCommand(prepareGameCommand(commandList), playerid); @@ -2357,14 +2520,22 @@ void Player::actResetPT() QList commandList; QListIterator selected(scene()->selectedItems()); while (selected.hasNext()) { - CardItem *card = static_cast(selected.next()); - CardInfoPtr info = card->getInfo(); - Command_SetCardAttr *cmd = new Command_SetCardAttr; + auto *card = static_cast(selected.next()); + QString ptString; + if (!card->getFaceDown()) { // leave the pt empty if the card is face down + CardInfoPtr info = card->getInfo(); + if (info) { + ptString = info->getPowTough(); + } + } + if (ptString == card->getPT()) { + continue; + } QString zoneName = card->getZone()->getName(); + auto *cmd = new Command_SetCardAttr; cmd->set_zone(zoneName.toStdString()); cmd->set_card_id(card->getId()); cmd->set_attribute(AttrPT); - QString ptString = info->getPowTough(); cmd->set_attr_value(ptString.toStdString()); commandList.append(cmd); @@ -2383,33 +2554,37 @@ void Player::actSetPT() QListIterator i(scene()->selectedItems()); while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - if (!card->getPT().isEmpty()) + auto *card = static_cast(i.next()); + if (!card->getPT().isEmpty()) { oldPT = card->getPT(); + } } bool ok; dialogSemaphore = true; - QString pt = QInputDialog::getText(0, tr("Set power/toughness"), tr("Please enter the new PT:"), QLineEdit::Normal, - oldPT, &ok); + QString pt = QInputDialog::getText(nullptr, tr("Set power/toughness"), tr("Please enter the new PT:"), + QLineEdit::Normal, oldPT, &ok); dialogSemaphore = false; - if (clearCardsToDelete()) + if (clearCardsToDelete()) { return; - if (!ok) + } + if (!ok) { return; + } QList commandList; QListIterator j(scene()->selectedItems()); while (j.hasNext()) { - CardItem *card = static_cast(j.next()); - Command_SetCardAttr *cmd = new Command_SetCardAttr; + auto *card = static_cast(j.next()); + auto *cmd = new Command_SetCardAttr; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_attribute(AttrPT); cmd->set_attr_value(pt.toStdString()); commandList.append(cmd); - if (local) + if (local) { playerid = card->getZone()->getPlayer()->getId(); + } } game->sendGameCommand(prepareGameCommand(commandList), playerid); @@ -2417,8 +2592,9 @@ void Player::actSetPT() void Player::actDrawArrow() { - if (!game->getActiveCard()) + if (!game->getActiveCard()) { return; + } game->getActiveCard()->drawArrow(Qt::red); } @@ -2458,26 +2634,29 @@ void Player::actSetAnnotation() QString oldAnnotation; QListIterator i(scene()->selectedItems()); while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - if (!card->getAnnotation().isEmpty()) + auto *card = static_cast(i.next()); + if (!card->getAnnotation().isEmpty()) { oldAnnotation = card->getAnnotation(); + } } bool ok; dialogSemaphore = true; - QString annotation = QInputDialog::getText(0, tr("Set annotation"), tr("Please enter the new annotation:"), + QString annotation = QInputDialog::getText(nullptr, tr("Set annotation"), tr("Please enter the new annotation:"), QLineEdit::Normal, oldAnnotation, &ok); dialogSemaphore = false; - if (clearCardsToDelete()) + if (clearCardsToDelete()) { return; - if (!ok) + } + if (!ok) { return; + } QList commandList; i.toFront(); while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - Command_SetCardAttr *cmd = new Command_SetCardAttr; + auto *card = static_cast(i.next()); + auto *cmd = new Command_SetCardAttr; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_attribute(AttrAnnotation); @@ -2489,18 +2668,20 @@ void Player::actSetAnnotation() void Player::actAttach() { - if (!game->getActiveCard()) + if (!game->getActiveCard()) { return; + } - ArrowAttachItem *arrow = new ArrowAttachItem(game->getActiveCard()); + auto *arrow = new ArrowAttachItem(game->getActiveCard()); scene()->addItem(arrow); arrow->grabMouse(); } void Player::actUnattach() { - if (!game->getActiveCard()) + if (!game->getActiveCard()) { return; + } Command_AttachCard cmd; cmd.set_start_zone(game->getActiveCard()->getZone()->getName().toStdString()); @@ -2510,17 +2691,16 @@ void Player::actUnattach() void Player::actCardCounterTrigger() { - QAction *a = static_cast(sender()); - int counterId = a->data().toInt() / 1000; - int action = a->data().toInt() % 1000; + auto *action = static_cast(sender()); + int counterId = action->data().toInt() / 1000; QList commandList; - switch (action) { + switch (action->data().toInt() % 1000) { // TODO: define case numbers case 9: { QListIterator i(scene()->selectedItems()); while (i.hasNext()) { - CardItem *card = static_cast(i.next()); + auto *card = static_cast(i.next()); if (card->getCounters().value(counterId, 0) < MAX_COUNTERS_ON_CARD) { - Command_SetCardCounter *cmd = new Command_SetCardCounter; + auto *cmd = new Command_SetCardCounter; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_counter_id(counterId); @@ -2533,9 +2713,9 @@ void Player::actCardCounterTrigger() case 10: { QListIterator i(scene()->selectedItems()); while (i.hasNext()) { - CardItem *card = static_cast(i.next()); + auto *card = static_cast(i.next()); if (card->getCounters().value(counterId, 0)) { - Command_SetCardCounter *cmd = new Command_SetCardCounter; + auto *cmd = new Command_SetCardCounter; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_counter_id(counterId); @@ -2548,17 +2728,17 @@ void Player::actCardCounterTrigger() case 11: { bool ok; dialogSemaphore = true; - int number = QInputDialog::getInt(0, tr("Set counters"), tr("Number:"), 0, 0, MAX_COUNTERS_ON_CARD, 1, &ok); + int number = + QInputDialog::getInt(nullptr, tr("Set counters"), tr("Number:"), 0, 0, MAX_COUNTERS_ON_CARD, 1, &ok); dialogSemaphore = false; - if (clearCardsToDelete()) - return; - if (!ok) + if (clearCardsToDelete() || !ok) { return; + } QListIterator i(scene()->selectedItems()); while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - Command_SetCardCounter *cmd = new Command_SetCardCounter; + auto *card = static_cast(i.next()); + auto *cmd = new Command_SetCardCounter; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); cmd->set_counter_id(counterId); @@ -2574,8 +2754,9 @@ void Player::actCardCounterTrigger() void Player::actPlay() { - if (!game->getActiveCard()) + if (!game->getActiveCard()) { return; + } bool cipt = game->getActiveCard()->getInfo() ? game->getActiveCard()->getInfo()->getCipt() : false; playCard(game->getActiveCard(), false, cipt); @@ -2583,16 +2764,18 @@ void Player::actPlay() void Player::actHide() { - if (!game->getActiveCard()) + if (!game->getActiveCard()) { return; + } game->getActiveCard()->getZone()->removeCard(game->getActiveCard()); } void Player::actPlayFacedown() { - if (!game->getActiveCard()) + if (!game->getActiveCard()) { return; + } playCard(game->getActiveCard(), true, false); } @@ -2602,7 +2785,7 @@ void Player::refreshShortcuts() if (shortcutsActive) { setShortcutsActive(); - foreach (const CardItem *cardItem, table->getCards()) { + for (const CardItem *cardItem : table->getCards()) { updateCardMenu(cardItem); } } @@ -2716,6 +2899,9 @@ void Player::updateCardMenu(const CardItem *card) cardMenu->addSeparator(); cardMenu->addAction(aClone); cardMenu->addMenu(moveMenu); + + addRelatedCardView(card, cardMenu); + addRelatedCardActions(card, cardMenu); } else { // Card is in hand or a custom zone specified by server cardMenu->addAction(aPlay); @@ -2763,86 +2949,76 @@ void Player::addRelatedCardView(const CardItem *card, QMenu *cardMenu) void Player::addRelatedCardActions(const CardItem *card, QMenu *cardMenu) { - if (card == nullptr || cardMenu == nullptr || card->getInfo() == nullptr) + if (card == nullptr || cardMenu == nullptr || card->getInfo() == nullptr) { return; - - QList relatedCards = QList(); - relatedCards.append(card->getInfo()->getRelatedCards()); - relatedCards.append(card->getInfo()->getReverseRelatedCards2Me()); - - switch (relatedCards.length()) { - case 0: - break; - case 1: { - cardMenu->addSeparator(); - QAction *createRelatedCards; - if (relatedCards.at(0)->getDoesAttach()) { - createRelatedCards = - new QAction(tr("Token: ") + tr("Attach to ") + "\"" + relatedCards.at(0)->getName() + "\"", this); - } else - createRelatedCards = new QAction( - tr("Token: ") + - (relatedCards.at(0)->getIsVariable() - ? "X " - : QString(relatedCards.at(0)->getDefaultCount() == 1 - ? QString() - : QString::number(relatedCards.at(0)->getDefaultCount()) + "x ")) + - relatedCards.at(0)->getName(), - this); - connect(createRelatedCards, SIGNAL(triggered()), this, SLOT(actCreateAllRelatedCards())); - if (shortcutsActive) { - createRelatedCards->setShortcut( - settingsCache->shortcuts().getSingleShortcut("Player/aCreateRelatedTokens")); - } - cardMenu->addAction(createRelatedCards); - break; - } - default: { - cardMenu->addSeparator(); - int i = 0; - foreach (CardRelation *cardRelation, relatedCards) { - QString cardName = cardRelation->getName(); - QAction *createRelated; - if (cardRelation->getDoesAttach()) - createRelated = new QAction(tr("Token: ") + tr("Attach to ") + "\"" + cardName + "\"", this); - else - createRelated = - new QAction(tr("Token: ") + - (cardRelation->getIsVariable() - ? "X " - : QString(cardRelation->getDefaultCount() == 1 - ? QString() - : QString::number(cardRelation->getDefaultCount()) + "x ")) + - cardName, - this); - createRelated->setData(QVariant(i)); - connect(createRelated, SIGNAL(triggered()), this, SLOT(actCreateRelatedCard())); - cardMenu->addAction(createRelated); - i++; - } - QAction *createRelatedCards = new QAction(tr("All tokens"), this); - connect(createRelatedCards, SIGNAL(triggered()), this, SLOT(actCreateAllRelatedCards())); - if (shortcutsActive) { - createRelatedCards->setShortcut( - settingsCache->shortcuts().getSingleShortcut("Player/aCreateRelatedTokens")); - } - cardMenu->addAction(createRelatedCards); - break; - } } + + QList relatedCards(card->getInfo()->getRelatedCards()); + relatedCards.append(card->getInfo()->getReverseRelatedCards2Me()); + if (relatedCards.empty()) { + return; + } + + cardMenu->addSeparator(); + int index = 0; + QAction *createRelatedCards = nullptr; + for (const CardRelation *cardRelation : relatedCards) { + CardInfoPtr relatedCard = db->getCard(cardRelation->getName()); + if (relatedCard == nullptr) + continue; + QString relatedCardName; + if (relatedCard->getPowTough().size() > 0) { + relatedCardName = relatedCard->getPowTough() + " " + relatedCard->getName(); // "n/n name" + } else { + relatedCardName = relatedCard->getName(); // "name" + } + + QString text = tr("Token: "); + if (cardRelation->getDoesAttach()) { + text += tr("Attach to ") + "\"" + relatedCardName + "\""; + } else if (cardRelation->getIsVariable()) { + text += "X " + relatedCardName; + } else if (cardRelation->getDefaultCount() != 1) { + text += QString::number(cardRelation->getDefaultCount()) + "x " + relatedCardName; + } else { + text += relatedCardName; + } + + if (createRelatedCards == nullptr) { + if (relatedCards.length() == 1) { + createRelatedCards = new QAction(text, this); // set actCreateAllRelatedCards with this text + break; // do not set an individual entry as there is only one entry + } else { + createRelatedCards = new QAction(tr("All tokens"), this); + } + } + + auto *createRelated = new QAction(text, this); + createRelated->setData(QVariant(index++)); + connect(createRelated, SIGNAL(triggered()), this, SLOT(actCreateRelatedCard())); + cardMenu->addAction(createRelated); + } + + if (shortcutsActive) { + createRelatedCards->setShortcut(settingsCache->shortcuts().getSingleShortcut("Player/aCreateRelatedTokens")); + } + connect(createRelatedCards, SIGNAL(triggered()), this, SLOT(actCreateAllRelatedCards())); + cardMenu->addAction(createRelatedCards); } void Player::setCardMenu(QMenu *menu) { - if (aCardMenu) + if (aCardMenu) { aCardMenu->setMenu(menu); + } } QMenu *Player::getCardMenu() const { - if (aCardMenu) + if (aCardMenu) { return aCardMenu->menu(); - return 0; + } + return nullptr; } QString Player::getName() const @@ -2853,15 +3029,17 @@ QString Player::getName() const qreal Player::getMinimumWidth() const { qreal result = table->getMinimumWidth() + CARD_HEIGHT + 15 + counterAreaWidth + stack->boundingRect().width(); - if (!settingsCache->getHorizontalHand()) + if (!settingsCache->getHorizontalHand()) { result += hand->boundingRect().width(); + } return result; } void Player::setGameStarted() { - if (local) + if (local) { aAlwaysRevealTopCard->setChecked(false); + } setConceded(false); } @@ -2871,8 +3049,8 @@ void Player::setConceded(bool _conceded) setVisible(!conceded); if (conceded) { clear(); - emit gameConceded(); } + emit playerCountChanged(); } void Player::setMirrored(bool _mirrored) @@ -2885,10 +3063,11 @@ void Player::setMirrored(bool _mirrored) void Player::processSceneSizeChange(int newPlayerWidth) { - // Extend table (and hand, if horizontal) to accomodate the new player width. + // Extend table (and hand, if horizontal) to accommodate the new player width. qreal tableWidth = newPlayerWidth - CARD_HEIGHT - 15 - counterAreaWidth - stack->boundingRect().width(); - if (!settingsCache->getHorizontalHand()) + if (!settingsCache->getHorizontalHand()) { tableWidth -= hand->boundingRect().width(); + } table->setWidth(tableWidth); hand->setWidth(tableWidth + stack->boundingRect().width()); @@ -2896,14 +3075,15 @@ void Player::processSceneSizeChange(int newPlayerWidth) void Player::setLastToken(CardInfoPtr cardInfo) { - if (cardInfo == nullptr || aCreateAnotherToken == nullptr) + if (cardInfo == nullptr || aCreateAnotherToken == nullptr) { return; + } lastTokenName = cardInfo->getName(); - lastTokenColor = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().first().toLower(); + lastTokenColor = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(); lastTokenPT = cardInfo->getPowTough(); lastTokenAnnotation = settingsCache->getAnnotateTokens() ? cardInfo->getText() : ""; - lastTokenTableRow = table->clampValidTableRow(2 - cardInfo->getTableRow()); + lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getTableRow()); lastTokenDestroy = true; aCreateAnotherToken->setText(tr("C&reate another %1 token").arg(lastTokenName)); aCreateAnotherToken->setEnabled(true); diff --git a/cockatrice/src/player.h b/cockatrice/src/player.h index e459d3d24..58ee8c2f5 100644 --- a/cockatrice/src/player.h +++ b/cockatrice/src/player.h @@ -40,7 +40,7 @@ class CommandContainer; class GameCommand; class GameEvent; class GameEventContext; -class Event_ConnectionStateChanged; +// class Event_ConnectionStateChanged; class Event_GameSay; class Event_Shuffle; class Event_RollDie; @@ -79,17 +79,17 @@ public: { Type = typeOther }; - int type() const + int type() const override { return Type; } - PlayerArea(QGraphicsItem *parent = 0); - QRectF boundingRect() const + explicit PlayerArea(QGraphicsItem *parent = nullptr); + QRectF boundingRect() const override { return bRect; } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void setSize(qreal width, qreal height); }; @@ -127,12 +127,17 @@ signals: void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); void logDumpZone(Player *player, CardZone *zone, int numberCards); void logStopDumpZone(Player *player, CardZone *zone); - void - logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer, bool faceDown); + void logRevealCards(Player *player, + CardZone *zone, + int cardId, + QString cardName, + Player *otherPlayer, + bool faceDown, + int amount); void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal); void sizeChanged(); - void gameConceded(); + void playerCountChanged(); public slots: void actUntapAll(); void actRollDie(); @@ -144,6 +149,8 @@ public slots: void actUndoDraw(); void actMulligan(); void actMoveTopCardToPlayFaceDown(); + void actMoveTopCardToGrave(); + void actMoveTopCardToExile(); void actMoveTopCardsToGrave(); void actMoveTopCardsToExile(); void actMoveTopCardToBottom(); @@ -201,10 +208,10 @@ private: QAction *aMoveHandToTopLibrary, *aMoveHandToBottomLibrary, *aMoveHandToGrave, *aMoveHandToRfg, *aMoveGraveToTopLibrary, *aMoveGraveToBottomLibrary, *aMoveGraveToHand, *aMoveGraveToRfg, *aMoveRfgToTopLibrary, *aMoveRfgToBottomLibrary, *aMoveRfgToHand, *aMoveRfgToGrave, *aViewLibrary, *aViewTopCards, - *aAlwaysRevealTopCard, *aOpenDeckInDeckEditor, *aMoveTopCardsToGrave, *aMoveTopCardsToExile, - *aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg, *aViewSideboard, *aDrawCard, *aDrawCards, *aUndoDraw, - *aMulligan, *aShuffle, *aMoveTopToPlayFaceDown, *aUntapAll, *aRollDie, *aCreateToken, *aCreateAnotherToken, - *aCardMenu, *aMoveBottomCardToGrave; + *aAlwaysRevealTopCard, *aOpenDeckInDeckEditor, *aMoveTopCardToGraveyard, *aMoveTopCardToExile, + *aMoveTopCardsToGraveyard, *aMoveTopCardsToExile, *aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg, + *aViewSideboard, *aDrawCard, *aDrawCards, *aUndoDraw, *aMulligan, *aShuffle, *aMoveTopToPlayFaceDown, + *aUntapAll, *aRollDie, *aCreateToken, *aCreateAnotherToken, *aCardMenu, *aMoveBottomCardToGrave; QList aAddCounter, aSetCounter, aRemoveCounter; QAction *aPlay, *aPlayFacedown, *aHide, *aTap, *aDoesntUntap, *aAttach, *aUnattach, *aDrawArrow, *aSetPT, *aResetPT, @@ -249,7 +256,6 @@ private: void createCard(const CardItem *sourceCard, const QString &dbCardName, bool attach = false); void createAttachedCard(const CardItem *sourceCard, const QString &dbCardName); bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation); - QString dbNameFromTokenDisplayName(const QString &tokenName); QRectF bRect; @@ -259,7 +265,7 @@ private: void initSayMenu(); - void eventConnectionStateChanged(const Event_ConnectionStateChanged &event); + // void eventConnectionStateChanged(const Event_ConnectionStateChanged &event); void eventGameSay(const Event_GameSay &event); void eventShuffle(const Event_Shuffle &event); void eventRollDie(const Event_RollDie &event); @@ -306,12 +312,12 @@ public: { Type = typeOther }; - int type() const + int type() const override { return Type; } - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void playCard(CardItem *c, bool faceDown, bool tapped); void addCard(CardItem *c); @@ -334,7 +340,7 @@ public: } Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_parent); - ~Player(); + ~Player() override; void retranslateUi(); void clear(); TabGame *getGame() const diff --git a/cockatrice/src/releasechannel.cpp b/cockatrice/src/releasechannel.cpp index 841f801a6..aab2cd804 100644 --- a/cockatrice/src/releasechannel.cpp +++ b/cockatrice/src/releasechannel.cpp @@ -1,5 +1,4 @@ #include "releasechannel.h" -#include "qt-json/json.h" #include "version_string.h" #include @@ -93,21 +92,20 @@ QString StableReleaseChannel::getReleaseChannelUrl() const void StableReleaseChannel::releaseListFinished() { - QNetworkReply *reply = static_cast(sender()); - bool ok; - QString tmp = QString(reply->readAll()); + auto *reply = static_cast(sender()); + QJsonParseError parseError{}; + QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError); reply->deleteLater(); - - QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap(); - if (!ok) { - qWarning() << "No reply received from the release update server:" << tmp; + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "No reply received from the release update server."; emit error(tr("No reply received from the release update server.")); return; } + QVariantMap resultMap = jsonResponse.toVariant().toMap(); if (!(resultMap.contains("name") && resultMap.contains("html_url") && resultMap.contains("tag_name") && resultMap.contains("published_at"))) { - qWarning() << "Invalid received from the release update server:" << tmp; + qWarning() << "Invalid received from the release update server."; emit error(tr("Invalid reply received from the release update server.")); return; } @@ -145,7 +143,7 @@ void StableReleaseChannel::releaseListFinished() QString myHash = QString(VERSION_COMMIT); qDebug() << "Current hash=" << myHash << "update hash=" << shortHash; - qDebug() << "Got reply from release server, size=" << tmp.size() << "name=" << lastRelease->getName() + qDebug() << "Got reply from release server, name=" << lastRelease->getName() << "desc=" << lastRelease->getDescriptionUrl() << "date=" << lastRelease->getPublishDate() << "url=" << lastRelease->getDownloadUrl(); @@ -158,26 +156,25 @@ void StableReleaseChannel::releaseListFinished() void StableReleaseChannel::tagListFinished() { - QNetworkReply *reply = static_cast(sender()); - bool ok; - QString tmp = QString(reply->readAll()); + auto *reply = static_cast(sender()); + QJsonParseError parseError{}; + QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError); reply->deleteLater(); - - QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap(); - if (!ok) { - qWarning() << "No reply received from the tag update server:" << tmp; + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "No reply received from the tag update server."; emit error(tr("No reply received from the tag update server.")); return; } + QVariantMap resultMap = jsonResponse.toVariant().toMap(); if (!(resultMap.contains("object") && resultMap["object"].toMap().contains("sha"))) { - qWarning() << "Invalid received from the tag update server:" << tmp; + qWarning() << "Invalid received from the tag update server."; emit error(tr("Invalid reply received from the tag update server.")); return; } lastRelease->setCommitHash(resultMap["object"].toMap()["sha"].toString()); - qDebug() << "Got reply from tag server, size=" << tmp.size() << "commit=" << lastRelease->getCommitHash(); + qDebug() << "Got reply from tag server, commit=" << lastRelease->getCommitHash(); QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN); QString myHash = QString(VERSION_COMMIT); @@ -190,7 +187,6 @@ void StableReleaseChannel::tagListFinished() void StableReleaseChannel::fileListFinished() { // Only implemented to satisfy interface - return; } QString BetaReleaseChannel::getManualDownloadUrl() const @@ -210,7 +206,7 @@ QString BetaReleaseChannel::getReleaseChannelUrl() const void BetaReleaseChannel::releaseListFinished() { - QNetworkReply *reply = static_cast(sender()); + auto *reply = static_cast(sender()); QByteArray jsonData = reply->readAll(); reply->deleteLater(); @@ -224,7 +220,7 @@ void BetaReleaseChannel::releaseListFinished() */ QVariantMap resultMap = array.at(0).toObject().toVariantMap(); - if (array.size() == 0 || resultMap.size() == 0) { + if (array.empty() || resultMap.empty()) { qWarning() << "No reply received from the release update server:" << QString(jsonData); emit error(tr("No reply received from the release update server.")); return; @@ -262,18 +258,17 @@ void BetaReleaseChannel::releaseListFinished() void BetaReleaseChannel::fileListFinished() { - QNetworkReply *reply = static_cast(sender()); - QByteArray jsonData = reply->readAll(); + auto *reply = static_cast(sender()); + QJsonParseError parseError{}; + QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError); reply->deleteLater(); - bool ok; - - QVariantList resultList = QtJson::Json::parse(jsonData, ok).toList(); - if (!ok) { - qWarning() << "No reply received from the file update server:" << QString(jsonData); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "No reply received from the file update server."; emit error(tr("No reply received from the file update server.")); return; } + QVariantList resultList = jsonResponse.toVariant().toList(); QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN); QString myHash = QString(VERSION_COMMIT); qDebug() << "Current hash=" << myHash << "update hash=" << shortHash; diff --git a/cockatrice/src/releasechannel.h b/cockatrice/src/releasechannel.h index c2b020dc4..8ec4d04c2 100644 --- a/cockatrice/src/releasechannel.h +++ b/cockatrice/src/releasechannel.h @@ -5,6 +5,7 @@ #include #include #include +#include class QNetworkReply; class QNetworkAccessManager; @@ -15,8 +16,8 @@ class Release friend class BetaReleaseChannel; public: - Release(){}; - ~Release(){}; + Release() = default; + ~Release() = default; private: QString name, descriptionUrl, downloadUrl, commitHash; @@ -26,20 +27,20 @@ private: protected: void setName(QString _name) { - name = _name; + name = std::move(_name); } void setDescriptionUrl(QString _descriptionUrl) { - descriptionUrl = _descriptionUrl; + descriptionUrl = std::move(_descriptionUrl); } void setDownloadUrl(QString _downloadUrl) { - downloadUrl = _downloadUrl; + downloadUrl = std::move(_downloadUrl); compatibleVersionFound = true; } void setCommitHash(QString _commitHash) { - commitHash = _commitHash; + commitHash = std::move(_commitHash); } void setPublishDate(QDate _publishDate) { @@ -78,7 +79,7 @@ class ReleaseChannel : public QObject Q_OBJECT public: ReleaseChannel(); - ~ReleaseChannel(); + ~ReleaseChannel() override; protected: // shared by all instances @@ -116,33 +117,41 @@ class StableReleaseChannel : public ReleaseChannel { Q_OBJECT public: - StableReleaseChannel(){}; - ~StableReleaseChannel(){}; - virtual QString getManualDownloadUrl() const; - virtual QString getName() const; + StableReleaseChannel() = default; + ~StableReleaseChannel() override = default; + + QString getManualDownloadUrl() const override; + + QString getName() const override; protected: - virtual QString getReleaseChannelUrl() const; + QString getReleaseChannelUrl() const override; protected slots: - virtual void releaseListFinished(); + + void releaseListFinished() override; void tagListFinished(); - virtual void fileListFinished(); + + void fileListFinished() override; }; class BetaReleaseChannel : public ReleaseChannel { Q_OBJECT public: - BetaReleaseChannel(){}; - ~BetaReleaseChannel(){}; - virtual QString getManualDownloadUrl() const; - virtual QString getName() const; + BetaReleaseChannel() = default; + ~BetaReleaseChannel() override = default; + + QString getManualDownloadUrl() const override; + + QString getName() const override; protected: - virtual QString getReleaseChannelUrl() const; + QString getReleaseChannelUrl() const override; protected slots: - virtual void releaseListFinished(); - virtual void fileListFinished(); + + void releaseListFinished() override; + + void fileListFinished() override; }; #endif \ No newline at end of file diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 5d78f7fef..3451e5754 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -18,12 +18,13 @@ #include #include #include +#include static const unsigned int protocolVersion = 14; RemoteClient::RemoteClient(QObject *parent) : AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false), - messageLength(0) + usingWebSocket(false), messageLength(0) { clearNewClientFeatures(); @@ -38,6 +39,13 @@ RemoteClient::RemoteClient(QObject *parent) connect(socket, SIGNAL(readyRead()), this, SLOT(readData())); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(slotSocketError(QAbstractSocket::SocketError))); + + websocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this); + connect(websocket, &QWebSocket::binaryMessageReceived, this, &RemoteClient::websocketMessageReceived); + connect(websocket, &QWebSocket::connected, this, &RemoteClient::slotConnected); + connect(websocket, SIGNAL(error(QAbstractSocket::SocketError)), this, + SLOT(slotWebSocketError(QAbstractSocket::SocketError))); + connect(this, SIGNAL(serverIdentificationEventReceived(const Event_ServerIdentification &)), this, SLOT(processServerIdentificationEvent(const Event_ServerIdentification &))); connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this, @@ -69,15 +77,25 @@ void RemoteClient::slotSocketError(QAbstractSocket::SocketError /*error*/) emit socketError(errorString); } +void RemoteClient::slotWebSocketError(QAbstractSocket::SocketError /*error*/) +{ + + QString errorString = websocket->errorString(); + doDisconnectFromServer(); + emit socketError(errorString); +} + void RemoteClient::slotConnected() { timeRunning = lastDataReceived = 0; timer->start(); - // dirty hack to be compatible with v14 server - sendCommandContainer(CommandContainer()); - getNewCmdId(); - // end of hack + if (!usingWebSocket) { + // dirty hack to be compatible with v14 server + sendCommandContainer(CommandContainer()); + getNewCmdId(); + // end of hack + } } void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event) @@ -217,7 +235,7 @@ void RemoteClient::loginResponse(const Response &response) missingFeatures << QString::fromStdString(resp.missing_features(i)); } emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), - resp.denied_end_time(), missingFeatures); + static_cast(resp.denied_end_time()), missingFeatures); setStatus(StatusDisconnecting); } } @@ -236,7 +254,7 @@ void RemoteClient::registerResponse(const Response &response) break; default: emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), - resp.denied_end_time()); + static_cast(resp.denied_end_time())); setStatus(StatusDisconnecting); doDisconnectFromServer(); break; @@ -301,21 +319,51 @@ void RemoteClient::readData() } while (!inputBuffer.isEmpty()); } +void RemoteClient::websocketMessageReceived(const QByteArray &message) +{ + lastDataReceived = timeRunning; + ServerMessage newServerMessage; + newServerMessage.ParseFromArray(message.data(), message.length()); +#ifdef QT_DEBUG + qDebug() << "IN" << messageLength << QString::fromStdString(newServerMessage.ShortDebugString()); +#endif + processProtocolItem(newServerMessage); +} + void RemoteClient::sendCommandContainer(const CommandContainer &cont) { - QByteArray buf; - unsigned int size = cont.ByteSize(); + + auto size = static_cast(cont.ByteSize()); #ifdef QT_DEBUG qDebug() << "OUT" << size << QString::fromStdString(cont.ShortDebugString()); #endif - buf.resize(size + 4); - cont.SerializeToArray(buf.data() + 4, size); - buf.data()[3] = (unsigned char)size; - buf.data()[2] = (unsigned char)(size >> 8); - buf.data()[1] = (unsigned char)(size >> 16); - buf.data()[0] = (unsigned char)(size >> 24); - socket->write(buf); + QByteArray buf; + if (usingWebSocket) { + buf.resize(size); + cont.SerializeToArray(buf.data(), size); + websocket->sendBinaryMessage(buf); + } else { + buf.resize(size + 4); + cont.SerializeToArray(buf.data() + 4, size); + buf.data()[3] = (unsigned char)size; + buf.data()[2] = (unsigned char)(size >> 8); + buf.data()[1] = (unsigned char)(size >> 16); + buf.data()[0] = (unsigned char)(size >> 24); + + socket->write(buf); + } +} + +void RemoteClient::connectToHost(const QString &hostname, unsigned int port) +{ + usingWebSocket = port == 443 || port == 80 || port == 4748 || port == 8080; + if (usingWebSocket) { + QUrl url(QString("%1://%2:%3/servatrice").arg(port == 443 ? "wss" : "ws").arg(hostname).arg(port)); + websocket->open(url); + } else { + socket->connectToHost(hostname, static_cast(port)); + } } void RemoteClient::doConnectToServer(const QString &hostname, @@ -330,7 +378,7 @@ void RemoteClient::doConnectToServer(const QString &hostname, lastHostname = hostname; lastPort = port; - socket->connectToHost(hostname, port); + connectToHost(hostname, port); setStatus(StatusConnecting); } @@ -354,7 +402,7 @@ void RemoteClient::doRegisterToServer(const QString &hostname, lastHostname = hostname; lastPort = port; - socket->connectToHost(hostname, port); + connectToHost(hostname, port); setStatus(StatusRegistering); } @@ -364,7 +412,7 @@ void RemoteClient::doActivateToServer(const QString &_token) token = _token; - socket->connectToHost(lastHostname, lastPort); + connectToHost(lastHostname, static_cast(lastPort)); setStatus(StatusActivating); } @@ -377,17 +425,19 @@ void RemoteClient::doDisconnectFromServer() messageLength = 0; QList pc = pendingCommands.values(); - for (int i = 0; i < pc.size(); i++) { + for (const auto &i : pc) { Response response; response.set_response_code(Response::RespNotConnected); - response.set_cmd_id(pc[i]->getCommandContainer().cmd_id()); - pc[i]->processResponse(response); + response.set_cmd_id(i->getCommandContainer().cmd_id()); + i->processResponse(response); - delete pc[i]; + delete i; } pendingCommands.clear(); setStatus(StatusDisconnected); + if (websocket->isValid()) + websocket->close(); socket->close(); } @@ -508,7 +558,7 @@ void RemoteClient::doRequestForgotPasswordToServer(const QString &hostname, unsi lastHostname = hostname; lastPort = port; - socket->connectToHost(lastHostname, lastPort); + connectToHost(lastHostname, static_cast(lastPort)); setStatus(StatusRequestingForgotPassword); } @@ -540,7 +590,7 @@ void RemoteClient::doSubmitForgotPasswordResetToServer(const QString &hostname, token = _token; password = _newpassword; - socket->connectToHost(lastHostname, lastPort); + connectToHost(lastHostname, static_cast(lastPort)); setStatus(StatusSubmitForgotPasswordReset); } @@ -574,7 +624,7 @@ void RemoteClient::doSubmitForgotPasswordChallengeToServer(const QString &hostna lastPort = port; email = _email; - socket->connectToHost(lastHostname, lastPort); + connectToHost(lastHostname, static_cast(lastPort)); setStatus(StatusSubmitForgotPasswordChallenge); } diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index f0bc93bc9..7c6ff2ab6 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -3,6 +3,7 @@ #include "abstractclient.h" #include +#include class QTimer; @@ -17,7 +18,6 @@ signals: void activateError(); void socketError(const QString &errorString); void protocolVersionMismatch(int clientVersion, int serverVersion); - void protocolError(); void sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); void sigRegisterToServer(const QString &hostname, @@ -25,7 +25,7 @@ signals: const QString &_userName, const QString &_password, const QString &_email, - const int _gender, + int _gender, const QString &_country, const QString &_realname); void sigActivateToServer(const QString &_token); @@ -48,7 +48,9 @@ signals: private slots: void slotConnected(); void readData(); + void websocketMessageReceived(const QByteArray &message); void slotSocketError(QAbstractSocket::SocketError error); + void slotWebSocketError(QAbstractSocket::SocketError error); void ping(); void processServerIdentificationEvent(const Event_ServerIdentification &event); void processConnectionClosedEvent(const Event_ConnectionClosed &event); @@ -62,7 +64,7 @@ private slots: const QString &_userName, const QString &_password, const QString &_email, - const int _gender, + int _gender, const QString &_country, const QString &_realname); void doLogin(); @@ -85,28 +87,35 @@ private slots: private: static const int maxTimeout = 10; int timeRunning, lastDataReceived; - QByteArray inputBuffer; bool messageInProgress; bool handshakeStarted; - bool newMissingFeatureFound(QString _serversMissingFeatures); - void clearNewClientFeatures(); + bool usingWebSocket; int messageLength; - QTimer *timer; QTcpSocket *socket; + QWebSocket *websocket; QString lastHostname; int lastPort; - QString getSrvClientID(const QString _hostname); + + QString getSrvClientID(QString _hostname); + bool newMissingFeatureFound(QString _serversMissingFeatures); + void clearNewClientFeatures(); + void connectToHost(const QString &hostname, unsigned int port); + protected slots: - void sendCommandContainer(const CommandContainer &cont); + void sendCommandContainer(const CommandContainer &cont) override; public: - RemoteClient(QObject *parent = 0); - ~RemoteClient(); + explicit RemoteClient(QObject *parent = nullptr); + ~RemoteClient() override; QString peerName() const { - return socket->peerName(); + if (usingWebSocket) { + return websocket->peerName(); + } else { + return socket->peerName(); + } } void connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); @@ -115,7 +124,7 @@ public: const QString &_userName, const QString &_password, const QString &_email, - const int _gender, + int _gender, const QString &_country, const QString &_realname); void activateToServer(const QString &_token); diff --git a/cockatrice/src/sequenceEdit/sequenceedit.cpp b/cockatrice/src/sequenceEdit/sequenceedit.cpp index 3553870d3..c652a5252 100644 --- a/cockatrice/src/sequenceEdit/sequenceedit.cpp +++ b/cockatrice/src/sequenceEdit/sequenceedit.cpp @@ -1,20 +1,12 @@ #include "sequenceedit.h" #include "../settingscache.h" -#include #include #include -#include -#include #include #include -SequenceEdit::SequenceEdit(QString _shorcutName, QWidget *parent) : QWidget(parent) +SequenceEdit::SequenceEdit(const QString &_shortcutName, QWidget *parent) : QWidget(parent), shortcutName(_shortcutName) { - shorcutName = std::move(_shorcutName); - currentKey = 0; - keys = 0; - valid = false; - lineEdit = new QLineEdit(this); clearButton = new QPushButton("", this); defaultButton = new QPushButton("", this); @@ -42,20 +34,20 @@ SequenceEdit::SequenceEdit(QString _shorcutName, QWidget *parent) : QWidget(pare connect(defaultButton, SIGNAL(clicked()), this, SLOT(restoreDefault())); lineEdit->installEventFilter(this); - lineEdit->setText(settingsCache->shortcuts().getShortcutString(shorcutName)); + lineEdit->setText(settingsCache->shortcuts().getShortcutString(shortcutName)); } -QString SequenceEdit::getSecuence() +QString SequenceEdit::getSequence() { return lineEdit->text(); } void SequenceEdit::removeLastShortcut() { - QString secuences = lineEdit->text(); - if (!secuences.isEmpty()) { - if (secuences.lastIndexOf(";") > 0) { - QString valid = secuences.left(secuences.lastIndexOf(";")); + QString sequences = lineEdit->text(); + if (!sequences.isEmpty()) { + if (sequences.lastIndexOf(";") > 0) { + QString valid = sequences.left(sequences.lastIndexOf(";")); lineEdit->setText(valid); } else { lineEdit->clear(); @@ -67,18 +59,18 @@ void SequenceEdit::removeLastShortcut() void SequenceEdit::restoreDefault() { - lineEdit->setText(settingsCache->shortcuts().getDefaultShortcutString(shorcutName)); + lineEdit->setText(settingsCache->shortcuts().getDefaultShortcutString(shortcutName)); updateSettings(); } void SequenceEdit::refreshShortcut() { - lineEdit->setText(settingsCache->shortcuts().getShortcutString(shorcutName)); + lineEdit->setText(settingsCache->shortcuts().getShortcutString(shortcutName)); } void SequenceEdit::clear() { - this->lineEdit->setText(""); + lineEdit->setText(""); } bool SequenceEdit::eventFilter(QObject *, QEvent *event) @@ -142,8 +134,8 @@ void SequenceEdit::finishShortcut() QKeySequence sequence(keys); if (!sequence.isEmpty() && valid) { QString sequenceString = sequence.toString(); - if (settingsCache->shortcuts().isKeyAllowed(shorcutName, sequenceString)) { - if (settingsCache->shortcuts().isValid(shorcutName, sequenceString)) { + if (settingsCache->shortcuts().isKeyAllowed(shortcutName, sequenceString)) { + if (settingsCache->shortcuts().isValid(shortcutName, sequenceString)) { if (!lineEdit->text().isEmpty()) { if (lineEdit->text().contains(sequenceString)) { return; @@ -167,5 +159,5 @@ void SequenceEdit::finishShortcut() void SequenceEdit::updateSettings() { - settingsCache->shortcuts().setShortcuts(shorcutName, lineEdit->text()); -} \ No newline at end of file + settingsCache->shortcuts().setShortcuts(shortcutName, lineEdit->text()); +} diff --git a/cockatrice/src/sequenceEdit/sequenceedit.h b/cockatrice/src/sequenceEdit/sequenceedit.h index 2dffc101e..5f61127a4 100644 --- a/cockatrice/src/sequenceEdit/sequenceedit.h +++ b/cockatrice/src/sequenceEdit/sequenceedit.h @@ -1,19 +1,18 @@ -#ifndef SECUENCEEDIT_H -#define SECUENCEEDIT_H +#ifndef SEQUENCEEDIT_H +#define SEQUENCEEDIT_H +#include #include +#include +#include #include -class QLineEdit; -class QPushButton; -class QEvent; - class SequenceEdit : public QWidget { Q_OBJECT public: - SequenceEdit(QString _shorcutName, QWidget *parent = nullptr); - QString getSecuence(); + SequenceEdit(const QString &_shortcutName, QWidget *parent = nullptr); + QString getSequence(); void refreshShortcut(); void clear(); @@ -25,13 +24,13 @@ protected: bool eventFilter(QObject *, QEvent *event); private: - QString shorcutName; + QString shortcutName; QLineEdit *lineEdit; QPushButton *clearButton; QPushButton *defaultButton; - int keys; - int currentKey; - bool valid; + int keys = 0; + int currentKey = 0; + bool valid = false; void processKey(QKeyEvent *e); int translateModifiers(Qt::KeyboardModifiers state, const QString &text); @@ -39,4 +38,4 @@ private: void updateSettings(); }; -#endif // SECUENCEEDIT_H +#endif // SEQUENCEEDIT_H diff --git a/cockatrice/src/sequenceEdit/ui_shortcutstab.h b/cockatrice/src/sequenceEdit/ui_shortcutstab.h index 1a6714df3..4fb147724 100644 --- a/cockatrice/src/sequenceEdit/ui_shortcutstab.h +++ b/cockatrice/src/sequenceEdit/ui_shortcutstab.h @@ -217,8 +217,8 @@ public: QGridLayout *gridLayout_10; QLabel *lbl_Player_aDecP; SequenceEdit *Player_aDecP; - SequenceEdit *Player_IncP; - QLabel *lbl_Player_IncP; + SequenceEdit *Player_aIncP; + QLabel *lbl_Player_aIncP; QGroupBox *groupBox_8; QGridLayout *gridLayout_5; QLabel *lbl_TabGame_phase0; @@ -246,12 +246,14 @@ public: SequenceEdit *TabGame_phase10; QLabel *lbl_TabGame_aNextTurn; SequenceEdit *TabGame_aNextPhase; + QLabel *lbl_TabGame_aNextPhaseAction; + SequenceEdit *TabGame_aNextPhaseAction; + SequenceEdit *TabGame_aNextTurn; QGroupBox *groupBox_13; QGridLayout *gridLayout_13; QLabel *lbl_Player_aTap; SequenceEdit *Player_aTap; - SequenceEdit *Player_aUntap; QLabel *lbl_Player_aUntapAll; SequenceEdit *Player_aUntapAll; QLabel *lbl_Player_aDoesntUntap; @@ -279,42 +281,8 @@ public: QSpacerItem *verticalSpacer_2; QWidget *tab_3; QGridLayout *gridLayout_20; - QGroupBox *groupBox_15; - QGridLayout *gridLayout_15; - QLabel *lbl_Player_aMoveToBottomLibrary; - SequenceEdit *Player_aMoveToBottomLibrary; - QLabel *lbl_Player_aMoveToTopLibrary; - SequenceEdit *Player_aMoveToTopLibrary; - QLabel *lbl_Player_aMoveToGraveyard; - SequenceEdit *Player_aMoveToGraveyard; - QLabel *lbl_Player_aMoveToExile; - SequenceEdit *Player_aMoveToExile; - QLabel *lbl_Player_aMoveToHand; - SequenceEdit *Player_aMoveToHand; - QLabel *lbl_Player_aMoveTopToPlayFaceDown; - SequenceEdit *Player_aMoveTopToPlayFaceDown; - QGroupBox *groupBox_16; - QGridLayout *gridLayout_16; - QLabel *lbl_Player_aViewGraveyard; - SequenceEdit *Player_aViewGraveyard; - QLabel *lbl_Player_aViewLibrary; - SequenceEdit *Player_aViewLibrary; - QLabel *lbl_Player_aViewTopCards; - SequenceEdit *Player_aViewTopCards; - QLabel *lbl_Player_aViewSideboard; - SequenceEdit *Player_aViewSideboard; - QLabel *lbl_Player_aViewRfg; - SequenceEdit *Player_aViewRfg; - QLabel *lbl_GameView_aCloseMostRecentZoneView; - SequenceEdit *GameView_aCloseMostRecentZoneView; - QGroupBox *groupBox_17; - QGridLayout *gridLayout_18; - SequenceEdit *DeckViewContainer_loadRemoteButton; - SequenceEdit *DeckViewContainer_loadLocalButton; - QLabel *lbl_DeckViewContainer_loadRemoteButton; - QLabel *lbl_DeckViewContainer_loadLocalButton; - QGroupBox *groupBox_18; - QGridLayout *gridLayout_19; + QGroupBox *groupBox_gameplay; + QGridLayout *gridLayout_gameplay; QLabel *lbl_Player_aDrawArrow; SequenceEdit *Player_aDrawArrow; QLabel *lbl_TabGame_aLeaveGame; @@ -331,8 +299,36 @@ public: SequenceEdit *Player_aShuffle; QLabel *lbl_TabGame_aRotateViewCCW; SequenceEdit *TabGame_aRotateViewCCW; - QGroupBox *groupBox_14; - QGridLayout *gridLayout_14; + QGroupBox *groupBox_moveCard; + QGridLayout *gridLayout_moveCard; + QLabel *lbl_Player_aMoveToBottomLibrary; + SequenceEdit *Player_aMoveToBottomLibrary; + QLabel *lbl_Player_aMoveToTopLibrary; + SequenceEdit *Player_aMoveToTopLibrary; + QLabel *lbl_Player_aMoveToGraveyard; + SequenceEdit *Player_aMoveToGraveyard; + QLabel *lbl_Player_aMoveToExile; + SequenceEdit *Player_aMoveToExile; + QLabel *lbl_Player_aMoveToHand; + SequenceEdit *Player_aMoveToHand; + QLabel *lbl_Player_aMoveTopToPlayFaceDown; + SequenceEdit *Player_aMoveTopToPlayFaceDown; + QGroupBox *groupBox_view; + QGridLayout *gridLayout_view; + QLabel *lbl_Player_aViewGraveyard; + SequenceEdit *Player_aViewGraveyard; + QLabel *lbl_Player_aViewLibrary; + SequenceEdit *Player_aViewLibrary; + QLabel *lbl_Player_aViewTopCards; + SequenceEdit *Player_aViewTopCards; + QLabel *lbl_Player_aViewSideboard; + SequenceEdit *Player_aViewSideboard; + QLabel *lbl_Player_aViewRfg; + SequenceEdit *Player_aViewRfg; + QLabel *lbl_GameView_aCloseMostRecentZoneView; + SequenceEdit *GameView_aCloseMostRecentZoneView; + QGroupBox *groupBox_draw; + QGridLayout *gridLayout_draw; QLabel *lbl_Player_aMulligan; SequenceEdit *Player_aMulligan; QLabel *lbl_Player_aDrawCard; @@ -343,6 +339,22 @@ public: SequenceEdit *Player_aUndoDraw; QLabel *lbl_Player_aAlwaysRevealTopCard; SequenceEdit *Player_aAlwaysRevealTopCard; + QGroupBox *groupBox_moveDeck; + QGridLayout *gridLayout_moveDeck; + QLabel *lbl_Player_aMoveTopCardToGraveyard; + SequenceEdit *Player_aMoveTopCardToGraveyard; + QLabel *lbl_Player_aMoveTopCardToExile; + SequenceEdit *Player_aMoveTopCardToExile; + QLabel *lbl_Player_aMoveTopCardsToGraveyard; + SequenceEdit *Player_aMoveTopCardsToGraveyard; + QLabel *lbl_Player_aMoveTopCardsToExile; + SequenceEdit *Player_aMoveTopCardsToExile; + QGroupBox *groupBox_gameLobby; + QGridLayout *gridLayout_gameLobby; + SequenceEdit *DeckViewContainer_loadRemoteButton; + QLabel *lbl_DeckViewContainer_loadRemoteButton; + SequenceEdit *DeckViewContainer_loadLocalButton; + QLabel *lbl_DeckViewContainer_loadLocalButton; QSpacerItem *verticalSpacer_3; QWidget *tab_4; QLabel *faqLabel; @@ -1111,15 +1123,15 @@ public: gridLayout_10->addWidget(Player_aDecP, 1, 1, 1, 1); - Player_IncP = new SequenceEdit("Player/IncP", groupBox_10); - Player_IncP->setObjectName("Player_IncP"); + Player_aIncP = new SequenceEdit("Player/aIncP", groupBox_10); + Player_aIncP->setObjectName("Player_aIncP"); - gridLayout_10->addWidget(Player_IncP, 0, 1, 1, 1); + gridLayout_10->addWidget(Player_aIncP, 0, 1, 1, 1); - lbl_Player_IncP = new QLabel(groupBox_10); - lbl_Player_IncP->setObjectName("lbl_Player_IncP"); + lbl_Player_aIncP = new QLabel(groupBox_10); + lbl_Player_aIncP->setObjectName("lbl_Player_aIncP"); - gridLayout_10->addWidget(lbl_Player_IncP, 0, 0, 1, 1); + gridLayout_10->addWidget(lbl_Player_aIncP, 0, 0, 1, 1); verticalLayout->addWidget(groupBox_10); @@ -1239,6 +1251,11 @@ public: gridLayout_5->addWidget(lbl_TabGame_aNextPhase, 11, 0, 1, 1); + lbl_TabGame_aNextPhaseAction = new QLabel(groupBox_8); + lbl_TabGame_aNextPhaseAction->setObjectName("lbl_TabGame_aNextPhaseAction"); + + gridLayout_5->addWidget(lbl_TabGame_aNextPhaseAction, 12, 0, 1, 1); + TabGame_phase10 = new SequenceEdit("Player/phase10", groupBox_8); TabGame_phase10->setObjectName("TabGame_phase10"); @@ -1247,17 +1264,22 @@ public: lbl_TabGame_aNextTurn = new QLabel(groupBox_8); lbl_TabGame_aNextTurn->setObjectName("lbl_TabGame_aNextTurn"); - gridLayout_5->addWidget(lbl_TabGame_aNextTurn, 12, 0, 1, 1); + gridLayout_5->addWidget(lbl_TabGame_aNextTurn, 13, 0, 1, 1); TabGame_aNextPhase = new SequenceEdit("Player/aNextPhase", groupBox_8); TabGame_aNextPhase->setObjectName("TabGame_aNextPhase"); gridLayout_5->addWidget(TabGame_aNextPhase, 11, 1, 1, 1); + TabGame_aNextPhaseAction = new SequenceEdit("Player/aNextPhaseAction", groupBox_8); + TabGame_aNextPhaseAction->setObjectName("TabGame_aaNextPhaseAction"); + + gridLayout_5->addWidget(TabGame_aNextPhaseAction, 12, 1, 1, 1); + TabGame_aNextTurn = new SequenceEdit("Player/aNextTurn", groupBox_8); TabGame_aNextTurn->setObjectName("TabGame_aNextTurn"); - gridLayout_5->addWidget(TabGame_aNextTurn, 12, 1, 1, 1); + gridLayout_5->addWidget(TabGame_aNextTurn, 13, 1, 1, 1); gridLayout_17->addWidget(groupBox_8, 0, 0, 1, 1); @@ -1406,309 +1428,357 @@ public: tab_3->setObjectName("tab_3"); gridLayout_20 = new QGridLayout(tab_3); gridLayout_20->setObjectName("gridLayout_20"); - groupBox_15 = new QGroupBox(tab_3); - groupBox_15->setObjectName("groupBox_15"); - gridLayout_15 = new QGridLayout(groupBox_15); - gridLayout_15->setObjectName("gridLayout_15"); - lbl_Player_aMoveToBottomLibrary = new QLabel(groupBox_15); - lbl_Player_aMoveToBottomLibrary->setObjectName("lbl_Player_aMoveToBottomLibrary"); - gridLayout_15->addWidget(lbl_Player_aMoveToBottomLibrary, 0, 0, 1, 1); - - Player_aMoveToBottomLibrary = new SequenceEdit("Player/aMoveToBottomLibrary", groupBox_15); - Player_aMoveToBottomLibrary->setObjectName("Player_aMoveToBottomLibrary"); - - gridLayout_15->addWidget(Player_aMoveToBottomLibrary, 0, 1, 1, 1); - - lbl_Player_aMoveToTopLibrary = new QLabel(groupBox_15); - lbl_Player_aMoveToTopLibrary->setObjectName("lbl_Player_aMoveToTopLibrary"); - - gridLayout_15->addWidget(lbl_Player_aMoveToTopLibrary, 1, 0, 1, 1); - - Player_aMoveToTopLibrary = new SequenceEdit("Player/aMoveToTopLibrary", groupBox_15); - Player_aMoveToTopLibrary->setObjectName("Player_aMoveToTopLibrary"); - - gridLayout_15->addWidget(Player_aMoveToTopLibrary, 1, 1, 1, 1); - - lbl_Player_aMoveToGraveyard = new QLabel(groupBox_15); - lbl_Player_aMoveToGraveyard->setObjectName("lbl_Player_aMoveToGraveyard"); - - gridLayout_15->addWidget(lbl_Player_aMoveToGraveyard, 2, 0, 1, 1); - - Player_aMoveToGraveyard = new SequenceEdit("Player/aMoveToGraveyard", groupBox_15); - Player_aMoveToGraveyard->setObjectName("Player_aMoveToGraveyard"); - - gridLayout_15->addWidget(Player_aMoveToGraveyard, 2, 1, 1, 1); - - lbl_Player_aMoveToExile = new QLabel(groupBox_15); - lbl_Player_aMoveToExile->setObjectName("lbl_Player_aMoveToExile"); - - gridLayout_15->addWidget(lbl_Player_aMoveToExile, 3, 0, 1, 1); - - Player_aMoveToExile = new SequenceEdit("Player/aMoveToExile", groupBox_15); - Player_aMoveToExile->setObjectName("Player_aMoveToExile"); - - gridLayout_15->addWidget(Player_aMoveToExile, 3, 1, 1, 1); - - lbl_Player_aMoveToHand = new QLabel(groupBox_15); - lbl_Player_aMoveToHand->setObjectName("lbl_Player_aMoveToHand"); - - gridLayout_15->addWidget(lbl_Player_aMoveToHand, 4, 0, 1, 1); - - Player_aMoveToHand = new SequenceEdit("Player/aMoveToHand", groupBox_15); - Player_aMoveToHand->setObjectName("Player_aMoveToHand"); - - gridLayout_15->addWidget(Player_aMoveToHand, 4, 1, 1, 1); - - lbl_Player_aMoveTopToPlayFaceDown = new QLabel(groupBox_15); - lbl_Player_aMoveTopToPlayFaceDown->setObjectName("lbl_Player_aMoveTopToPlayFaceDown"); - - gridLayout_15->addWidget(lbl_Player_aMoveTopToPlayFaceDown, 5, 0, 1, 1); - - Player_aMoveTopToPlayFaceDown = new SequenceEdit("Player/aMoveTopToPlayFaceDown", groupBox_15); - Player_aMoveTopToPlayFaceDown->setObjectName("Player_aMoveTopToPlayFaceDown"); - - gridLayout_15->addWidget(Player_aMoveTopToPlayFaceDown, 5, 1, 1, 1); - - gridLayout_20->addWidget(groupBox_15, 0, 1, 1, 1); - - groupBox_16 = new QGroupBox(tab_3); - groupBox_16->setObjectName("groupBox_16"); - gridLayout_16 = new QGridLayout(groupBox_16); - gridLayout_16->setObjectName("gridLayout_16"); - lbl_Player_aViewGraveyard = new QLabel(groupBox_16); - lbl_Player_aViewGraveyard->setObjectName("lbl_Player_aViewGraveyard"); - - gridLayout_16->addWidget(lbl_Player_aViewGraveyard, 0, 0, 1, 1); - - Player_aViewGraveyard = new SequenceEdit("Player/aViewGraveyard", groupBox_16); - Player_aViewGraveyard->setObjectName("Player_aViewGraveyard"); - - gridLayout_16->addWidget(Player_aViewGraveyard, 0, 1, 1, 1); - - lbl_Player_aViewLibrary = new QLabel(groupBox_16); - lbl_Player_aViewLibrary->setObjectName("lbl_Player_aViewLibrary"); - - gridLayout_16->addWidget(lbl_Player_aViewLibrary, 1, 0, 1, 1); - - Player_aViewLibrary = new SequenceEdit("Player/aViewLibrary", groupBox_16); - Player_aViewLibrary->setObjectName("Player_aViewLibrary"); - - gridLayout_16->addWidget(Player_aViewLibrary, 1, 1, 1, 1); - - lbl_Player_aViewTopCards = new QLabel(groupBox_16); - lbl_Player_aViewTopCards->setObjectName("lbl_Player_aViewTopCards"); - - gridLayout_16->addWidget(lbl_Player_aViewTopCards, 2, 0, 1, 1); - - Player_aViewTopCards = new SequenceEdit("Player/aViewTopCards", groupBox_16); - Player_aViewTopCards->setObjectName("Player_aViewTopCards"); - - gridLayout_16->addWidget(Player_aViewTopCards, 2, 1, 1, 1); - - lbl_Player_aViewSideboard = new QLabel(groupBox_16); - lbl_Player_aViewSideboard->setObjectName("lbl_Player_aViewSideboard"); - - gridLayout_16->addWidget(lbl_Player_aViewSideboard, 3, 0, 1, 1); - - Player_aViewSideboard = new SequenceEdit("Player/aViewSideboard", groupBox_16); - Player_aViewSideboard->setObjectName("Player_aViewSideboard"); - - gridLayout_16->addWidget(Player_aViewSideboard, 3, 1, 1, 1); - - lbl_Player_aViewRfg = new QLabel(groupBox_16); - lbl_Player_aViewRfg->setObjectName("lbl_Player_aViewRfg"); - - gridLayout_16->addWidget(lbl_Player_aViewRfg, 4, 0, 1, 1); - - Player_aViewRfg = new SequenceEdit("Player/aViewRfg", groupBox_16); - Player_aViewRfg->setObjectName("Player_aViewRfg"); - - gridLayout_16->addWidget(Player_aViewRfg, 4, 1, 1, 1); - - lbl_GameView_aCloseMostRecentZoneView = new QLabel(groupBox_16); - lbl_GameView_aCloseMostRecentZoneView->setObjectName("lbl_GameView_aCloseMostRecentZoneView"); - - gridLayout_16->addWidget(lbl_GameView_aCloseMostRecentZoneView, 5, 0, 1, 1); - - GameView_aCloseMostRecentZoneView = new SequenceEdit("Player/aCloseMostRecentZoneView", groupBox_16); - GameView_aCloseMostRecentZoneView->setObjectName("GameView_aCloseMostRecentZoneView"); - - gridLayout_16->addWidget(GameView_aCloseMostRecentZoneView, 5, 1, 1, 1); - - gridLayout_20->addWidget(groupBox_16, 0, 2, 1, 1); - - groupBox_17 = new QGroupBox(tab_3); - groupBox_17->setObjectName("groupBox_17"); - gridLayout_18 = new QGridLayout(groupBox_17); - gridLayout_18->setObjectName("gridLayout_18"); - DeckViewContainer_loadRemoteButton = new SequenceEdit("DeckViewContainer/loadRemoteButton", groupBox_17); - DeckViewContainer_loadRemoteButton->setObjectName("DeckViewContainer_loadRemoteButton"); - - gridLayout_18->addWidget(DeckViewContainer_loadRemoteButton, 2, 1, 1, 1); - - DeckViewContainer_loadLocalButton = new SequenceEdit("DeckViewContainer/loadLocalButton", groupBox_17); - DeckViewContainer_loadLocalButton->setObjectName("DeckViewContainer_loadLocalButton"); - - gridLayout_18->addWidget(DeckViewContainer_loadLocalButton, 0, 1, 1, 1); - - lbl_DeckViewContainer_loadRemoteButton = new QLabel(groupBox_17); - lbl_DeckViewContainer_loadRemoteButton->setObjectName("lbl_DeckViewContainer_loadRemoteButton"); - - gridLayout_18->addWidget(lbl_DeckViewContainer_loadRemoteButton, 2, 0, 1, 1); - - lbl_DeckViewContainer_loadLocalButton = new QLabel(groupBox_17); - lbl_DeckViewContainer_loadLocalButton->setObjectName("lbl_DeckViewContainer_loadLocalButton"); - - gridLayout_18->addWidget(lbl_DeckViewContainer_loadLocalButton, 0, 0, 1, 1); - - gridLayout_20->addWidget(groupBox_17, 1, 0, 1, 1); - - groupBox_18 = new QGroupBox(tab_3); - groupBox_18->setObjectName("groupBox_18"); - gridLayout_19 = new QGridLayout(groupBox_18); - gridLayout_19->setObjectName("gridLayout_19"); - lbl_Player_aDrawArrow = new QLabel(groupBox_18); + groupBox_gameplay = new QGroupBox(tab_3); + groupBox_gameplay->setObjectName("groupBox_gameplay"); + gridLayout_gameplay = new QGridLayout(groupBox_gameplay); + gridLayout_gameplay->setObjectName("gridLayout_gameplay"); + lbl_Player_aDrawArrow = new QLabel(groupBox_gameplay); lbl_Player_aDrawArrow->setObjectName("lbl_Player_aDrawArrow"); - gridLayout_19->addWidget(lbl_Player_aDrawArrow, 0, 0, 1, 1); + gridLayout_gameplay->addWidget(lbl_Player_aDrawArrow, 0, 0, 1, 1); - Player_aDrawArrow = new SequenceEdit("Player/aDrawArrow", groupBox_18); + Player_aDrawArrow = new SequenceEdit("Player/aDrawArrow", groupBox_gameplay); Player_aDrawArrow->setObjectName("Player_aDrawArrow"); - gridLayout_19->addWidget(Player_aDrawArrow, 0, 1, 1, 1); + gridLayout_gameplay->addWidget(Player_aDrawArrow, 0, 1, 1, 1); - lbl_TabGame_aLeaveGame = new QLabel(groupBox_18); - lbl_TabGame_aLeaveGame->setObjectName("lbl_TabGame_aLeaveGame"); - - gridLayout_19->addWidget(lbl_TabGame_aLeaveGame, 0, 2, 1, 1); - - TabGame_aLeaveGame = new SequenceEdit("Player/aLeaveGame", groupBox_18); - TabGame_aLeaveGame->setObjectName("TabGame_aLeaveGame"); - - gridLayout_19->addWidget(TabGame_aLeaveGame, 0, 3, 1, 1); - - lbl_TabGame_aRemoveLocalArrows = new QLabel(groupBox_18); + lbl_TabGame_aRemoveLocalArrows = new QLabel(groupBox_gameplay); lbl_TabGame_aRemoveLocalArrows->setObjectName("lbl_TabGame_aRemoveLocalArrows"); - gridLayout_19->addWidget(lbl_TabGame_aRemoveLocalArrows, 1, 0, 1, 1); + gridLayout_gameplay->addWidget(lbl_TabGame_aRemoveLocalArrows, 1, 0, 1, 1); - TabGame_aRemoveLocalArrows = new SequenceEdit("Player/aRemoveLocalArrows", groupBox_18); + TabGame_aRemoveLocalArrows = new SequenceEdit("Player/aRemoveLocalArrows", groupBox_gameplay); TabGame_aRemoveLocalArrows->setObjectName("TabGame_aRemoveLocalArrows"); - gridLayout_19->addWidget(TabGame_aRemoveLocalArrows, 1, 1, 1, 1); + gridLayout_gameplay->addWidget(TabGame_aRemoveLocalArrows, 1, 1, 1, 1); - lbl_TabGame_aConcede = new QLabel(groupBox_18); + lbl_TabGame_aConcede = new QLabel(groupBox_gameplay); lbl_TabGame_aConcede->setObjectName("lbl_TabGame_aConcede"); - gridLayout_19->addWidget(lbl_TabGame_aConcede, 1, 2, 1, 1); + gridLayout_gameplay->addWidget(lbl_TabGame_aConcede, 2, 0, 1, 1); - TabGame_aConcede = new SequenceEdit("Player/aConcede", groupBox_18); + TabGame_aConcede = new SequenceEdit("Player/aConcede", groupBox_gameplay); TabGame_aConcede->setObjectName("TabGame_aConcede"); - gridLayout_19->addWidget(TabGame_aConcede, 1, 3, 1, 1); + gridLayout_gameplay->addWidget(TabGame_aConcede, 2, 1, 1, 1); - lbl_Player_aRollDie = new QLabel(groupBox_18); + lbl_TabGame_aLeaveGame = new QLabel(groupBox_gameplay); + lbl_TabGame_aLeaveGame->setObjectName("lbl_TabGame_aLeaveGame"); + + gridLayout_gameplay->addWidget(lbl_TabGame_aLeaveGame, 3, 0, 1, 1); + + TabGame_aLeaveGame = new SequenceEdit("Player/aLeaveGame", groupBox_gameplay); + TabGame_aLeaveGame->setObjectName("TabGame_aLeaveGame"); + + gridLayout_gameplay->addWidget(TabGame_aLeaveGame, 3, 1, 1, 1); + + lbl_Player_aRollDie = new QLabel(groupBox_gameplay); lbl_Player_aRollDie->setObjectName("lbl_Player_aRollDie"); - gridLayout_19->addWidget(lbl_Player_aRollDie, 2, 0, 1, 1); + gridLayout_gameplay->addWidget(lbl_Player_aRollDie, 4, 0, 1, 1); - Player_aRollDie = new SequenceEdit("Player/aRollDie", groupBox_18); + Player_aRollDie = new SequenceEdit("Player/aRollDie", groupBox_gameplay); Player_aRollDie->setObjectName("Player_aRollDie"); - gridLayout_19->addWidget(Player_aRollDie, 2, 1, 1, 1); + gridLayout_gameplay->addWidget(Player_aRollDie, 4, 1, 1, 1); - lbl_TabGame_aRotateViewCW = new QLabel(groupBox_18); - lbl_TabGame_aRotateViewCW->setObjectName("lbl_TabGame_aRotateViewCW"); - - gridLayout_19->addWidget(lbl_TabGame_aRotateViewCW, 2, 2, 1, 1); - - TabGame_aRotateViewCW = new SequenceEdit("Player/aRotateViewCW", groupBox_18); - TabGame_aRotateViewCW->setObjectName("TabGame_aRotateViewCW"); - - gridLayout_19->addWidget(TabGame_aRotateViewCW, 2, 3, 1, 1); - - lbl_Player_aShuffle = new QLabel(groupBox_18); + lbl_Player_aShuffle = new QLabel(groupBox_gameplay); lbl_Player_aShuffle->setObjectName("lbl_Player_aShuffle"); - gridLayout_19->addWidget(lbl_Player_aShuffle, 3, 0, 1, 1); + gridLayout_gameplay->addWidget(lbl_Player_aShuffle, 5, 0, 1, 1); - Player_aShuffle = new SequenceEdit("Player/aShuffle", groupBox_18); + Player_aShuffle = new SequenceEdit("Player/aShuffle", groupBox_gameplay); Player_aShuffle->setObjectName("Player_aShuffle"); - gridLayout_19->addWidget(Player_aShuffle, 3, 1, 1, 1); + gridLayout_gameplay->addWidget(Player_aShuffle, 5, 1, 1, 1); - lbl_TabGame_aRotateViewCCW = new QLabel(groupBox_18); + lbl_TabGame_aRotateViewCW = new QLabel(groupBox_gameplay); + lbl_TabGame_aRotateViewCW->setObjectName("lbl_TabGame_aRotateViewCW"); + + gridLayout_gameplay->addWidget(lbl_TabGame_aRotateViewCW, 6, 0, 1, 1); + + TabGame_aRotateViewCW = new SequenceEdit("Player/aRotateViewCW", groupBox_gameplay); + TabGame_aRotateViewCW->setObjectName("TabGame_aRotateViewCW"); + + gridLayout_gameplay->addWidget(TabGame_aRotateViewCW, 6, 1, 1, 1); + + lbl_TabGame_aRotateViewCCW = new QLabel(groupBox_gameplay); lbl_TabGame_aRotateViewCCW->setObjectName("lbl_TabGame_aRotateViewCCW"); - gridLayout_19->addWidget(lbl_TabGame_aRotateViewCCW, 3, 2, 1, 1); + gridLayout_gameplay->addWidget(lbl_TabGame_aRotateViewCCW, 7, 0, 1, 1); - TabGame_aRotateViewCCW = new SequenceEdit("Player/aRotateViewCCW", groupBox_18); + TabGame_aRotateViewCCW = new SequenceEdit("Player/aRotateViewCCW", groupBox_gameplay); TabGame_aRotateViewCCW->setObjectName("TabGame_aRotateViewCCW"); - gridLayout_19->addWidget(TabGame_aRotateViewCCW, 3, 3, 1, 1); + gridLayout_gameplay->addWidget(TabGame_aRotateViewCCW, 7, 1, 1, 1); - gridLayout_20->addWidget(groupBox_18, 1, 1, 1, 2); + gridLayout_20->addWidget(groupBox_gameplay, 0, 0, 1, 1); - groupBox_14 = new QGroupBox(tab_3); - groupBox_14->setObjectName("groupBox_14"); - gridLayout_14 = new QGridLayout(groupBox_14); - gridLayout_14->setObjectName("gridLayout_14"); - lbl_Player_aMulligan = new QLabel(groupBox_14); + groupBox_moveCard = new QGroupBox(tab_3); + groupBox_moveCard->setObjectName("groupBox_moveCard"); + gridLayout_moveCard = new QGridLayout(groupBox_moveCard); + gridLayout_moveCard->setObjectName("gridLayout_moveCard"); + lbl_Player_aMoveToBottomLibrary = new QLabel(groupBox_moveCard); + lbl_Player_aMoveToBottomLibrary->setObjectName("lbl_Player_aMoveToBottomLibrary"); + + gridLayout_moveCard->addWidget(lbl_Player_aMoveToBottomLibrary, 0, 0, 1, 1); + + Player_aMoveToBottomLibrary = new SequenceEdit("Player/aMoveToBottomLibrary", groupBox_moveCard); + Player_aMoveToBottomLibrary->setObjectName("Player_aMoveToBottomLibrary"); + + gridLayout_moveCard->addWidget(Player_aMoveToBottomLibrary, 0, 1, 1, 1); + + lbl_Player_aMoveToTopLibrary = new QLabel(groupBox_moveCard); + lbl_Player_aMoveToTopLibrary->setObjectName("lbl_Player_aMoveToTopLibrary"); + + gridLayout_moveCard->addWidget(lbl_Player_aMoveToTopLibrary, 1, 0, 1, 1); + + Player_aMoveToTopLibrary = new SequenceEdit("Player/aMoveToTopLibrary", groupBox_moveCard); + Player_aMoveToTopLibrary->setObjectName("Player_aMoveToTopLibrary"); + + gridLayout_moveCard->addWidget(Player_aMoveToTopLibrary, 1, 1, 1, 1); + + lbl_Player_aMoveToGraveyard = new QLabel(groupBox_moveCard); + lbl_Player_aMoveToGraveyard->setObjectName("lbl_Player_aMoveToGraveyard"); + + gridLayout_moveCard->addWidget(lbl_Player_aMoveToGraveyard, 2, 0, 1, 1); + + Player_aMoveToGraveyard = new SequenceEdit("Player/aMoveToGraveyard", groupBox_moveCard); + Player_aMoveToGraveyard->setObjectName("Player_aMoveToGraveyard"); + + gridLayout_moveCard->addWidget(Player_aMoveToGraveyard, 2, 1, 1, 1); + + lbl_Player_aMoveToExile = new QLabel(groupBox_moveCard); + lbl_Player_aMoveToExile->setObjectName("lbl_Player_aMoveToExile"); + + gridLayout_moveCard->addWidget(lbl_Player_aMoveToExile, 3, 0, 1, 1); + + Player_aMoveToExile = new SequenceEdit("Player/aMoveToExile", groupBox_moveCard); + Player_aMoveToExile->setObjectName("Player_aMoveToExile"); + + gridLayout_moveCard->addWidget(Player_aMoveToExile, 3, 1, 1, 1); + + lbl_Player_aMoveToHand = new QLabel(groupBox_moveCard); + lbl_Player_aMoveToHand->setObjectName("lbl_Player_aMoveToHand"); + + gridLayout_moveCard->addWidget(lbl_Player_aMoveToHand, 4, 0, 1, 1); + + Player_aMoveToHand = new SequenceEdit("Player/aMoveToHand", groupBox_moveCard); + Player_aMoveToHand->setObjectName("Player_aMoveToHand"); + + gridLayout_moveCard->addWidget(Player_aMoveToHand, 4, 1, 1, 1); + + lbl_Player_aMoveTopToPlayFaceDown = new QLabel(groupBox_moveCard); + lbl_Player_aMoveTopToPlayFaceDown->setObjectName("lbl_Player_aMoveTopToPlayFaceDown"); + + gridLayout_moveCard->addWidget(lbl_Player_aMoveTopToPlayFaceDown, 5, 0, 1, 1); + + Player_aMoveTopToPlayFaceDown = new SequenceEdit("Player/aMoveTopToPlayFaceDown", groupBox_moveCard); + Player_aMoveTopToPlayFaceDown->setObjectName("Player_aMoveTopToPlayFaceDown"); + + gridLayout_moveCard->addWidget(Player_aMoveTopToPlayFaceDown, 5, 1, 1, 1); + + gridLayout_20->addWidget(groupBox_moveCard, 0, 1, 1, 1); + + groupBox_view = new QGroupBox(tab_3); + groupBox_view->setObjectName("groupBox_view"); + gridLayout_view = new QGridLayout(groupBox_view); + gridLayout_view->setObjectName("gridLayout_view"); + lbl_Player_aViewGraveyard = new QLabel(groupBox_view); + lbl_Player_aViewGraveyard->setObjectName("lbl_Player_aViewGraveyard"); + + gridLayout_view->addWidget(lbl_Player_aViewGraveyard, 0, 0, 1, 1); + + Player_aViewGraveyard = new SequenceEdit("Player/aViewGraveyard", groupBox_view); + Player_aViewGraveyard->setObjectName("Player_aViewGraveyard"); + + gridLayout_view->addWidget(Player_aViewGraveyard, 0, 1, 1, 1); + + lbl_Player_aViewLibrary = new QLabel(groupBox_view); + lbl_Player_aViewLibrary->setObjectName("lbl_Player_aViewLibrary"); + + gridLayout_view->addWidget(lbl_Player_aViewLibrary, 1, 0, 1, 1); + + Player_aViewLibrary = new SequenceEdit("Player/aViewLibrary", groupBox_view); + Player_aViewLibrary->setObjectName("Player_aViewLibrary"); + + gridLayout_view->addWidget(Player_aViewLibrary, 1, 1, 1, 1); + + lbl_Player_aViewTopCards = new QLabel(groupBox_view); + lbl_Player_aViewTopCards->setObjectName("lbl_Player_aViewTopCards"); + + gridLayout_view->addWidget(lbl_Player_aViewTopCards, 2, 0, 1, 1); + + Player_aViewTopCards = new SequenceEdit("Player/aViewTopCards", groupBox_view); + Player_aViewTopCards->setObjectName("Player_aViewTopCards"); + + gridLayout_view->addWidget(Player_aViewTopCards, 2, 1, 1, 1); + + lbl_Player_aViewSideboard = new QLabel(groupBox_view); + lbl_Player_aViewSideboard->setObjectName("lbl_Player_aViewSideboard"); + + gridLayout_view->addWidget(lbl_Player_aViewSideboard, 3, 0, 1, 1); + + Player_aViewSideboard = new SequenceEdit("Player/aViewSideboard", groupBox_view); + Player_aViewSideboard->setObjectName("Player_aViewSideboard"); + + gridLayout_view->addWidget(Player_aViewSideboard, 3, 1, 1, 1); + + lbl_Player_aViewRfg = new QLabel(groupBox_view); + lbl_Player_aViewRfg->setObjectName("lbl_Player_aViewRfg"); + + gridLayout_view->addWidget(lbl_Player_aViewRfg, 4, 0, 1, 1); + + Player_aViewRfg = new SequenceEdit("Player/aViewRfg", groupBox_view); + Player_aViewRfg->setObjectName("Player_aViewRfg"); + + gridLayout_view->addWidget(Player_aViewRfg, 4, 1, 1, 1); + + lbl_GameView_aCloseMostRecentZoneView = new QLabel(groupBox_view); + lbl_GameView_aCloseMostRecentZoneView->setObjectName("lbl_GameView_aCloseMostRecentZoneView"); + + gridLayout_view->addWidget(lbl_GameView_aCloseMostRecentZoneView, 5, 0, 1, 1); + + GameView_aCloseMostRecentZoneView = new SequenceEdit("Player/aCloseMostRecentZoneView", groupBox_view); + GameView_aCloseMostRecentZoneView->setObjectName("GameView_aCloseMostRecentZoneView"); + + gridLayout_view->addWidget(GameView_aCloseMostRecentZoneView, 5, 1, 1, 1); + + gridLayout_20->addWidget(groupBox_view, 0, 2, 1, 1); + + groupBox_draw = new QGroupBox(tab_3); + groupBox_draw->setObjectName("groupBox_draw"); + gridLayout_draw = new QGridLayout(groupBox_draw); + gridLayout_draw->setObjectName("gridLayout_draw"); + lbl_Player_aMulligan = new QLabel(groupBox_draw); lbl_Player_aMulligan->setObjectName("lbl_Player_aMulligan"); - gridLayout_14->addWidget(lbl_Player_aMulligan, 4, 0, 1, 1); + gridLayout_draw->addWidget(lbl_Player_aMulligan, 4, 0, 1, 1); - Player_aMulligan = new SequenceEdit("Player/aMulligan", groupBox_14); + Player_aMulligan = new SequenceEdit("Player/aMulligan", groupBox_draw); Player_aMulligan->setObjectName("Player_aMulligan"); - gridLayout_14->addWidget(Player_aMulligan, 4, 1, 1, 1); + gridLayout_draw->addWidget(Player_aMulligan, 4, 1, 1, 1); - lbl_Player_aDrawCard = new QLabel(groupBox_14); + lbl_Player_aDrawCard = new QLabel(groupBox_draw); lbl_Player_aDrawCard->setObjectName("lbl_Player_aDrawCard"); - gridLayout_14->addWidget(lbl_Player_aDrawCard, 0, 0, 1, 1); + gridLayout_draw->addWidget(lbl_Player_aDrawCard, 0, 0, 1, 1); - Player_aDrawCard = new SequenceEdit("Player/aDrawCard", groupBox_14); + Player_aDrawCard = new SequenceEdit("Player/aDrawCard", groupBox_draw); Player_aDrawCard->setObjectName("Player_aDrawCard"); - gridLayout_14->addWidget(Player_aDrawCard, 0, 1, 1, 1); + gridLayout_draw->addWidget(Player_aDrawCard, 0, 1, 1, 1); - lbl_Player_aDrawCards = new QLabel(groupBox_14); + lbl_Player_aDrawCards = new QLabel(groupBox_draw); lbl_Player_aDrawCards->setObjectName("lbl_Player_aDrawCards"); - gridLayout_14->addWidget(lbl_Player_aDrawCards, 1, 0, 1, 1); + gridLayout_draw->addWidget(lbl_Player_aDrawCards, 1, 0, 1, 1); - Player_aDrawCards = new SequenceEdit("Player/aDrawCards", groupBox_14); + Player_aDrawCards = new SequenceEdit("Player/aDrawCards", groupBox_draw); Player_aDrawCards->setObjectName("Player_aDrawCards"); - gridLayout_14->addWidget(Player_aDrawCards, 1, 1, 1, 1); + gridLayout_draw->addWidget(Player_aDrawCards, 1, 1, 1, 1); - lbl_Player_aUndoDraw = new QLabel(groupBox_14); + lbl_Player_aUndoDraw = new QLabel(groupBox_draw); lbl_Player_aUndoDraw->setObjectName("lbl_Player_aUndoDraw"); - gridLayout_14->addWidget(lbl_Player_aUndoDraw, 2, 0, 1, 1); + gridLayout_draw->addWidget(lbl_Player_aUndoDraw, 2, 0, 1, 1); - Player_aUndoDraw = new SequenceEdit("Player/aUndoDraw", groupBox_14); + Player_aUndoDraw = new SequenceEdit("Player/aUndoDraw", groupBox_draw); Player_aUndoDraw->setObjectName("Player_aUndoDraw"); - gridLayout_14->addWidget(Player_aUndoDraw, 2, 1, 1, 1); + gridLayout_draw->addWidget(Player_aUndoDraw, 2, 1, 1, 1); - lbl_Player_aAlwaysRevealTopCard = new QLabel(groupBox_14); + lbl_Player_aAlwaysRevealTopCard = new QLabel(groupBox_draw); lbl_Player_aAlwaysRevealTopCard->setObjectName("lbl_Player_aAlwaysRevealTopCard"); - gridLayout_14->addWidget(lbl_Player_aAlwaysRevealTopCard, 3, 0, 1, 1); + gridLayout_draw->addWidget(lbl_Player_aAlwaysRevealTopCard, 3, 0, 1, 1); - Player_aAlwaysRevealTopCard = new SequenceEdit("Player/aAlwaysRevealTopCard", groupBox_14); + Player_aAlwaysRevealTopCard = new SequenceEdit("Player/aAlwaysRevealTopCard", groupBox_draw); Player_aAlwaysRevealTopCard->setObjectName("Player_aAlwaysRevealTopCard"); - gridLayout_14->addWidget(Player_aAlwaysRevealTopCard, 3, 1, 1, 1); - gridLayout_20->addWidget(groupBox_14, 0, 0, 1, 1); + gridLayout_draw->addWidget(Player_aAlwaysRevealTopCard, 3, 1, 1, 1); + gridLayout_20->addWidget(groupBox_draw, 1, 0, 1, 1); + + groupBox_moveDeck = new QGroupBox(tab_3); + groupBox_moveDeck->setObjectName("groupBox_moveDeck"); + gridLayout_moveDeck = new QGridLayout(groupBox_moveDeck); + gridLayout_moveDeck->setObjectName("gridLayout_moveDeck"); + lbl_Player_aMoveTopCardToGraveyard = new QLabel(groupBox_moveDeck); + lbl_Player_aMoveTopCardToGraveyard->setObjectName("lbl_Player_aMoveTopCardToGraveyard"); + + gridLayout_moveDeck->addWidget(lbl_Player_aMoveTopCardToGraveyard, 0, 0, 1, 1); + + Player_aMoveTopCardToGraveyard = new SequenceEdit("Player/aMoveTopCardToGraveyard", groupBox_moveDeck); + Player_aMoveTopCardToGraveyard->setObjectName("Player_aMoveTopCardToGraveyard"); + + gridLayout_moveDeck->addWidget(Player_aMoveTopCardToGraveyard, 0, 1, 1, 1); + + lbl_Player_aMoveTopCardsToGraveyard = new QLabel(groupBox_moveDeck); + lbl_Player_aMoveTopCardsToGraveyard->setObjectName("lbl_Player_aMoveTopCardsToGraveyard"); + + gridLayout_moveDeck->addWidget(lbl_Player_aMoveTopCardsToGraveyard, 1, 0, 1, 1); + + Player_aMoveTopCardsToGraveyard = new SequenceEdit("Player/aMoveTopCardsToGraveyard", groupBox_moveDeck); + Player_aMoveTopCardsToGraveyard->setObjectName("Player_aMoveTopCardsToGraveyard"); + + gridLayout_moveDeck->addWidget(Player_aMoveTopCardsToGraveyard, 1, 1, 1, 1); + + lbl_Player_aMoveTopCardToExile = new QLabel(groupBox_moveDeck); + lbl_Player_aMoveTopCardToExile->setObjectName("lbl_Player_aMoveTopCardToExile"); + + gridLayout_moveDeck->addWidget(lbl_Player_aMoveTopCardToExile, 2, 0, 1, 1); + + Player_aMoveTopCardToExile = new SequenceEdit("Player/aMoveTopCardToExile", groupBox_moveDeck); + Player_aMoveTopCardToExile->setObjectName("Player_aMoveTopCardToExile"); + + gridLayout_moveDeck->addWidget(Player_aMoveTopCardToExile, 2, 1, 1, 1); + + lbl_Player_aMoveTopCardsToExile = new QLabel(groupBox_moveDeck); + lbl_Player_aMoveTopCardsToExile->setObjectName("lbl_Player_aMoveTopCardsToExile"); + + gridLayout_moveDeck->addWidget(lbl_Player_aMoveTopCardsToExile, 3, 0, 1, 1); + + Player_aMoveTopCardsToExile = new SequenceEdit("Player/aMoveTopCardsToExile", groupBox_moveDeck); + Player_aMoveTopCardsToExile->setObjectName("Player_aMoveTopCardsToExile"); + + gridLayout_moveDeck->addWidget(Player_aMoveTopCardsToExile, 3, 1, 1, 1); + + gridLayout_20->addWidget(groupBox_moveDeck, 1, 1, 1, 1); + + groupBox_gameLobby = new QGroupBox(tab_3); + groupBox_gameLobby->setObjectName("groupBox_gameLobby"); + gridLayout_gameLobby = new QGridLayout(groupBox_gameLobby); + gridLayout_gameLobby->setObjectName("gridLayout_gameLobby"); + DeckViewContainer_loadRemoteButton = new SequenceEdit("DeckViewContainer/loadRemoteButton", groupBox_gameLobby); + DeckViewContainer_loadRemoteButton->setObjectName("DeckViewContainer_loadRemoteButton"); + + gridLayout_gameLobby->addWidget(DeckViewContainer_loadRemoteButton, 2, 1, 1, 1); + + DeckViewContainer_loadLocalButton = new SequenceEdit("DeckViewContainer/loadLocalButton", groupBox_gameLobby); + DeckViewContainer_loadLocalButton->setObjectName("DeckViewContainer_loadLocalButton"); + + gridLayout_gameLobby->addWidget(DeckViewContainer_loadLocalButton, 0, 1, 1, 1); + + lbl_DeckViewContainer_loadRemoteButton = new QLabel(groupBox_gameLobby); + lbl_DeckViewContainer_loadRemoteButton->setObjectName("lbl_DeckViewContainer_loadRemoteButton"); + + gridLayout_gameLobby->addWidget(lbl_DeckViewContainer_loadRemoteButton, 2, 0, 1, 1); + + lbl_DeckViewContainer_loadLocalButton = new QLabel(groupBox_gameLobby); + lbl_DeckViewContainer_loadLocalButton->setObjectName("lbl_DeckViewContainer_loadLocalButton"); + + gridLayout_gameLobby->addWidget(lbl_DeckViewContainer_loadLocalButton, 0, 0, 1, 1); + + gridLayout_20->addWidget(groupBox_gameLobby, 1, 2, 1, 1); + verticalSpacer_3 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout_20->addItem(verticalSpacer_3, 2, 1, 1, 1); tabWidget->addTab(tab_3, QString()); tab_4 = new QWidget(tabWidget); - QGridLayout *grid = new QGridLayout(tab_4); + auto *grid = new QGridLayout(tab_4); grid->addWidget(groupBox_3); grid->addItem(new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding), 1, 0); @@ -1724,7 +1794,7 @@ public: btnResetAll->setIcon(QPixmap("theme:icons/update")); btnClearAll->setIcon(QPixmap("theme:icons/clearsearch")); - QHBoxLayout *buttonsLayout = new QHBoxLayout(shortcutsTab); + auto *buttonsLayout = new QHBoxLayout(shortcutsTab); buttonsLayout->addWidget(btnClearAll); buttonsLayout->addWidget(btnResetAll); @@ -1747,12 +1817,13 @@ public: gridLayout_11->setSpacing(3); gridLayout_12->setSpacing(3); gridLayout_13->setSpacing(3); - gridLayout_14->setSpacing(3); - gridLayout_15->setSpacing(3); - gridLayout_16->setSpacing(3); + gridLayout_moveDeck->setSpacing(3); + gridLayout_draw->setSpacing(3); + gridLayout_moveCard->setSpacing(3); + gridLayout_view->setSpacing(3); + gridLayout_gameLobby->setSpacing(3); + gridLayout_gameplay->setSpacing(3); gridLayout_17->setSpacing(3); - gridLayout_18->setSpacing(3); - gridLayout_19->setSpacing(3); gridLayout_20->setSpacing(3); verticalLayout->setSpacing(3); @@ -1763,176 +1834,180 @@ public: void retranslateUi(QWidget * /*shortcutsTab */) { - groupBox->setTitle(QApplication::translate("shortcutsTab", "Main Window", 0)); - lbl_MainWindow_aDeckEditor->setText(QApplication::translate("shortcutsTab", "Deck editor", 0)); - lbl_MainWindow_aSinglePlayer->setText(QApplication::translate("shortcutsTab", "Local gameplay", 0)); - lbl_MainWindow_aWatchReplay->setText(QApplication::translate("shortcutsTab", "Watch replay", 0)); - lbl_MainWindow_aConnect->setText(QApplication::translate("shortcutsTab", "Connect", 0)); - lbl_MainWindow_aRegister->setText(QApplication::translate("shortcutsTab", "Register", 0)); - lbl_MainWindow_aFullScreen->setText(QApplication::translate("shortcutsTab", "Full screen", 0)); - lbl_MainWindow_aSettings->setText(QApplication::translate("shortcutsTab", "Settings", 0)); - lbl_MainWindow_aCheckCardUpdates->setText(QApplication::translate("shortcutsTab", "Check for card updates", 0)); - lbl_MainWindow_aDisconnect->setText(QApplication::translate("shortcutsTab", "Disconnect", 0)); - lbl_MainWindow_aExit->setText(QApplication::translate("shortcutsTab", "Exit", 0)); - groupBox_2->setTitle(QApplication::translate("shortcutsTab", "Deck Editor", 0)); - lbl_TabDeckEditor_aAnalyzeDeck->setText(QApplication::translate("shortcutsTab", "Analyze deck", 0)); + groupBox->setTitle(QApplication::translate("shortcutsTab", "Main Window")); + lbl_MainWindow_aDeckEditor->setText(QApplication::translate("shortcutsTab", "Deck editor")); + lbl_MainWindow_aSinglePlayer->setText(QApplication::translate("shortcutsTab", "Local gameplay")); + lbl_MainWindow_aWatchReplay->setText(QApplication::translate("shortcutsTab", "Watch replay")); + lbl_MainWindow_aConnect->setText(QApplication::translate("shortcutsTab", "Connect")); + lbl_MainWindow_aRegister->setText(QApplication::translate("shortcutsTab", "Register")); + lbl_MainWindow_aFullScreen->setText(QApplication::translate("shortcutsTab", "Full screen")); + lbl_MainWindow_aSettings->setText(QApplication::translate("shortcutsTab", "Settings")); + lbl_MainWindow_aCheckCardUpdates->setText(QApplication::translate("shortcutsTab", "Check for card updates")); + lbl_MainWindow_aDisconnect->setText(QApplication::translate("shortcutsTab", "Disconnect")); + lbl_MainWindow_aExit->setText(QApplication::translate("shortcutsTab", "Exit")); + groupBox_2->setTitle(QApplication::translate("shortcutsTab", "Deck Editor")); + lbl_TabDeckEditor_aAnalyzeDeck->setText(QApplication::translate("shortcutsTab", "Analyze deck")); lbl_TabDeckEditor_aLoadDeckFromClipboard->setText( - QApplication::translate("shortcutsTab", "Load deck (clipboard)", 0)); - lbl_TabDeckEditor_aClearFilterAll->setText(QApplication::translate("shortcutsTab", "Clear all filters", 0)); - lbl_TabDeckEditor_aNewDeck->setText(QApplication::translate("shortcutsTab", "New deck", 0)); - lbl_TabDeckEditor_aClearFilterOne->setText(QApplication::translate("shortcutsTab", "Clear selected filter", 0)); - lbl_TabDeckEditor_aOpenCustomFolder->setText( - QApplication::translate("shortcutsTab", "Open custom pic folder", 0)); - lbl_TabDeckEditor_aClose->setText(QApplication::translate("shortcutsTab", "Close", 0)); - lbl_TabDeckEditor_aPrintDeck->setText(QApplication::translate("shortcutsTab", "Print deck", 0)); - lbl_TabDeckEditor_aManageSets->setText(QApplication::translate("shortcutsTab", "Manage sets", 0)); - lbl_TabDeckEditor_aRemoveCard->setText(QApplication::translate("shortcutsTab", "Delete card", 0)); - lbl_TabDeckEditor_aEditTokens->setText(QApplication::translate("shortcutsTab", "Edit tokens", 0)); - lbl_TabDeckEditor_aResetLayout->setText(QApplication::translate("shortcutsTab", "Reset layout", 0)); - lbl_TabDeckEditor_aIncrement->setText(QApplication::translate("shortcutsTab", "Add card", 0)); - lbl_TabDeckEditor_aSaveDeck->setText(QApplication::translate("shortcutsTab", "Save deck", 0)); - lbl_TabDeckEditor_aExportDeckDecklist->setText(QApplication::translate("shortcutsTab", "Export deck", 0)); - lbl_TabDeckEditor_aDecrement->setText(QApplication::translate("shortcutsTab", "Remove card", 0)); - lbl_TabDeckEditor_aSaveDeckAs->setText(QApplication::translate("shortcutsTab", "Save deck as", 0)); - lbl_TabDeckEditor_aLoadDeck->setText(QApplication::translate("shortcutsTab", "Load deck", 0)); - lbl_TabDeckEditor_aSaveDeckToClipboard->setText(QApplication::translate("shortcutsTab", "Save deck (clip)", 0)); + QApplication::translate("shortcutsTab", "Load deck (clipboard)")); + lbl_TabDeckEditor_aClearFilterAll->setText(QApplication::translate("shortcutsTab", "Clear all filters")); + lbl_TabDeckEditor_aNewDeck->setText(QApplication::translate("shortcutsTab", "New deck")); + lbl_TabDeckEditor_aClearFilterOne->setText(QApplication::translate("shortcutsTab", "Clear selected filter")); + lbl_TabDeckEditor_aOpenCustomFolder->setText(QApplication::translate("shortcutsTab", "Open custom pic folder")); + lbl_TabDeckEditor_aClose->setText(QApplication::translate("shortcutsTab", "Close")); + lbl_TabDeckEditor_aPrintDeck->setText(QApplication::translate("shortcutsTab", "Print deck")); + lbl_TabDeckEditor_aManageSets->setText(QApplication::translate("shortcutsTab", "Manage sets")); + lbl_TabDeckEditor_aRemoveCard->setText(QApplication::translate("shortcutsTab", "Delete card")); + lbl_TabDeckEditor_aEditTokens->setText(QApplication::translate("shortcutsTab", "Edit tokens")); + lbl_TabDeckEditor_aResetLayout->setText(QApplication::translate("shortcutsTab", "Reset layout")); + lbl_TabDeckEditor_aIncrement->setText(QApplication::translate("shortcutsTab", "Add card")); + lbl_TabDeckEditor_aSaveDeck->setText(QApplication::translate("shortcutsTab", "Save deck")); + lbl_TabDeckEditor_aExportDeckDecklist->setText(QApplication::translate("shortcutsTab", "Export deck")); + lbl_TabDeckEditor_aDecrement->setText(QApplication::translate("shortcutsTab", "Remove card")); + lbl_TabDeckEditor_aSaveDeckAs->setText(QApplication::translate("shortcutsTab", "Save deck as")); + lbl_TabDeckEditor_aLoadDeck->setText(QApplication::translate("shortcutsTab", "Load deck")); + lbl_TabDeckEditor_aSaveDeckToClipboard->setText(QApplication::translate("shortcutsTab", "Save deck (clip)")); lbl_TabDeckEditor_aSaveDeckToClipboardRaw->setText( - QApplication::translate("shortcutsTab", "Save deck (clip; no annotations)", 0)); - groupBox_3->setTitle(QApplication::translate("shortcutsTab", "Counters", 0)); - groupBox_4->setTitle(QApplication::translate("shortcutsTab", "Life", 0)); - lbl_abstractCounter_sSet->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_abstractCounter_aInc->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_abstractCounter_aDec->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_5->setTitle(QApplication::translate("shortcutsTab", "Red", 0)); - lbl_Player_aSCRed->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aCCRed->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aRCRed->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_6->setTitle(QApplication::translate("shortcutsTab", "Green", 0)); - lbl_Player_aSCGreen->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aCCGreen->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aRCGreen->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_7->setTitle(QApplication::translate("shortcutsTab", "Yellow", 0)); - lbl_Player_aSCYellow->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aCCYellow->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aRCYellow->setText(QApplication::translate("shortcutsTab", "Remove", 0)); + QApplication::translate("shortcutsTab", "Save deck (clip; no annotations)")); + groupBox_3->setTitle(QApplication::translate("shortcutsTab", "Counters")); + groupBox_4->setTitle(QApplication::translate("shortcutsTab", "Life")); + lbl_abstractCounter_sSet->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_abstractCounter_aInc->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_abstractCounter_aDec->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_5->setTitle(QApplication::translate("shortcutsTab", "Red")); + lbl_Player_aSCRed->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aCCRed->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aRCRed->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_6->setTitle(QApplication::translate("shortcutsTab", "Green")); + lbl_Player_aSCGreen->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aCCGreen->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aRCGreen->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_7->setTitle(QApplication::translate("shortcutsTab", "Yellow")); + lbl_Player_aSCYellow->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aCCYellow->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aRCYellow->setText(QApplication::translate("shortcutsTab", "Remove")); - groupBox_counterStorm->setTitle(QApplication::translate("shortcutsTab", "Storm", 0)); - lbl_Player_aSetCStorm->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aIncCStorm->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aDecCStorm->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_counterW->setTitle(QApplication::translate("shortcutsTab", "W", 0)); - lbl_Player_aSetCW->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aIncCW->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aDecCW->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_counterU->setTitle(QApplication::translate("shortcutsTab", "U", 0)); - lbl_Player_aSetCU->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aIncCU->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aDecCU->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_counterB->setTitle(QApplication::translate("shortcutsTab", "B", 0)); - lbl_Player_aSetCB->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aIncCB->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aDecCB->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_counterR->setTitle(QApplication::translate("shortcutsTab", "R", 0)); - lbl_Player_aSetCR->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aIncCR->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aDecCR->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_counterG->setTitle(QApplication::translate("shortcutsTab", "G", 0)); - lbl_Player_aSetCG->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aIncCG->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aDecCG->setText(QApplication::translate("shortcutsTab", "Remove", 0)); - groupBox_counterX->setTitle(QApplication::translate("shortcutsTab", "X", 0)); - lbl_Player_aSetCX->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aIncCX->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aDecCX->setText(QApplication::translate("shortcutsTab", "Remove", 0)); + groupBox_counterStorm->setTitle(QApplication::translate("shortcutsTab", "Storm")); + lbl_Player_aSetCStorm->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aIncCStorm->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aDecCStorm->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_counterW->setTitle(QApplication::translate("shortcutsTab", "W")); + lbl_Player_aSetCW->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aIncCW->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aDecCW->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_counterU->setTitle(QApplication::translate("shortcutsTab", "U")); + lbl_Player_aSetCU->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aIncCU->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aDecCU->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_counterB->setTitle(QApplication::translate("shortcutsTab", "B")); + lbl_Player_aSetCB->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aIncCB->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aDecCB->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_counterR->setTitle(QApplication::translate("shortcutsTab", "R")); + lbl_Player_aSetCR->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aIncCR->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aDecCR->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_counterG->setTitle(QApplication::translate("shortcutsTab", "G")); + lbl_Player_aSetCG->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aIncCG->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aDecCG->setText(QApplication::translate("shortcutsTab", "Remove")); + groupBox_counterX->setTitle(QApplication::translate("shortcutsTab", "X")); + lbl_Player_aSetCX->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aIncCX->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aDecCX->setText(QApplication::translate("shortcutsTab", "Remove")); - lbl_Player_aSCYellow->setText(QApplication::translate("shortcutsTab", "Set", 0)); - lbl_Player_aCCYellow->setText(QApplication::translate("shortcutsTab", "Add", 0)); - lbl_Player_aRCYellow->setText(QApplication::translate("shortcutsTab", "Remove", 0)); + lbl_Player_aSCYellow->setText(QApplication::translate("shortcutsTab", "Set")); + lbl_Player_aCCYellow->setText(QApplication::translate("shortcutsTab", "Add")); + lbl_Player_aRCYellow->setText(QApplication::translate("shortcutsTab", "Remove")); tabWidget->setTabText(tabWidget->indexOf(tab), - QApplication::translate("shortcutsTab", "Main Window | Deck Editor", 0)); - groupBox_9->setTitle(QApplication::translate("shortcutsTab", "Power / Toughness", 0)); - groupBox_12->setTitle(QApplication::translate("shortcutsTab", "Power and Toughness", 0)); - lbl_Player_aIncPT->setText(QApplication::translate("shortcutsTab", "Add (+1/+1)", 0)); - lbl_Player_aDecPT->setText(QApplication::translate("shortcutsTab", "Remove (-1/-1)", 0)); - lbl_Player_aResetPT->setText(QApplication::translate("shortcutsTab", "Reset", 0)); - lbl_Player_aSetPT->setText(QApplication::translate("shortcutsTab", "Set", 0)); - groupBox_11->setTitle(QApplication::translate("shortcutsTab", "Toughness", 0)); - lbl_Player_aDecT->setText(QApplication::translate("shortcutsTab", "Remove (-0/-1)", 0)); - lbl_Player_aIncT->setText(QApplication::translate("shortcutsTab", "Add (+0/+1)", 0)); - groupBox_10->setTitle(QApplication::translate("shortcutsTab", "Power", 0)); - lbl_Player_aDecP->setText(QApplication::translate("shortcutsTab", "Remove (-1/-0)", 0)); - lbl_Player_IncP->setText(QApplication::translate("shortcutsTab", "Add (+1/+0)", 0)); - groupBox_8->setTitle(QApplication::translate("shortcutsTab", "Game Phases", 0)); - lbl_TabGame_phase0->setText(QApplication::translate("shortcutsTab", "Untap", 0)); - lbl_TabGame_phase1->setText(QApplication::translate("shortcutsTab", "Upkeep", 0)); - lbl_TabGame_phase2->setText(QApplication::translate("shortcutsTab", "Draw", 0)); - lbl_TabGame_phase3->setText(QApplication::translate("shortcutsTab", "Main 1", 0)); - lbl_TabGame_phase4->setText(QApplication::translate("shortcutsTab", "Start combat", 0)); - lbl_TabGame_phase5->setText(QApplication::translate("shortcutsTab", "Attack", 0)); - lbl_TabGame_phase6->setText(QApplication::translate("shortcutsTab", "Block", 0)); - lbl_TabGame_phase7->setText(QApplication::translate("shortcutsTab", "Damage", 0)); - lbl_TabGame_phase8->setText(QApplication::translate("shortcutsTab", "End combat", 0)); - lbl_TabGame_phase9->setText(QApplication::translate("shortcutsTab", "Main 2", 0)); - lbl_TabGame_phase10->setText(QApplication::translate("shortcutsTab", "End", 0)); - lbl_TabGame_aNextPhase->setText(QApplication::translate("shortcutsTab", "Next phase", 0)); - lbl_TabGame_aNextTurn->setText(QApplication::translate("shortcutsTab", "Next turn", 0)); - groupBox_13->setTitle(QApplication::translate("shortcutsTab", "Playing Area", 0)); - lbl_Player_aTap->setText(QApplication::translate("shortcutsTab", "Tap / Untap Card", 0)); - lbl_Player_aUntapAll->setText(QApplication::translate("shortcutsTab", "Untap all", 0)); - lbl_Player_aDoesntUntap->setText(QApplication::translate("shortcutsTab", "Toggle untap", 0)); - lbl_Player_aFlip->setText(QApplication::translate("shortcutsTab", "Flip card", 0)); - lbl_Player_aPeek->setText(QApplication::translate("shortcutsTab", "Peek card", 0)); - lbl_Player_aPlay->setText(QApplication::translate("shortcutsTab", "Play card", 0)); - lbl_Player_aAttach->setText(QApplication::translate("shortcutsTab", "Attach card", 0)); - lbl_Player_aUnattach->setText(QApplication::translate("shortcutsTab", "Unattach card", 0)); - lbl_Player_aClone->setText(QApplication::translate("shortcutsTab", "Clone card", 0)); - lbl_Player_aCreateToken->setText(QApplication::translate("shortcutsTab", "Create token", 0)); - lbl_Player_aCreateRelatedTokens->setText( - QApplication::translate("shortcutsTab", "Create all related tokens", 0)); - lbl_Player_aCreateAnotherToken->setText(QApplication::translate("shortcutsTab", "Create another token", 0)); - lbl_Player_aSetAnnotation->setText(QApplication::translate("shortcutsTab", "Set annotation", 0)); + QApplication::translate("shortcutsTab", "Main Window | Deck Editor")); + groupBox_9->setTitle(QApplication::translate("shortcutsTab", "Power / Toughness")); + groupBox_12->setTitle(QApplication::translate("shortcutsTab", "Power and Toughness")); + lbl_Player_aIncPT->setText(QApplication::translate("shortcutsTab", "Add (+1/+1)")); + lbl_Player_aDecPT->setText(QApplication::translate("shortcutsTab", "Remove (-1/-1)")); + lbl_Player_aResetPT->setText(QApplication::translate("shortcutsTab", "Reset")); + lbl_Player_aSetPT->setText(QApplication::translate("shortcutsTab", "Set")); + groupBox_11->setTitle(QApplication::translate("shortcutsTab", "Toughness")); + lbl_Player_aDecT->setText(QApplication::translate("shortcutsTab", "Remove (-0/-1)")); + lbl_Player_aIncT->setText(QApplication::translate("shortcutsTab", "Add (+0/+1)")); + groupBox_10->setTitle(QApplication::translate("shortcutsTab", "Power")); + lbl_Player_aDecP->setText(QApplication::translate("shortcutsTab", "Remove (-1/-nullptr)")); + lbl_Player_aIncP->setText(QApplication::translate("shortcutsTab", "Add (+1/+nullptr)")); + groupBox_8->setTitle(QApplication::translate("shortcutsTab", "Game Phases")); + lbl_TabGame_phase0->setText(QApplication::translate("shortcutsTab", "Untap")); + lbl_TabGame_phase1->setText(QApplication::translate("shortcutsTab", "Upkeep")); + lbl_TabGame_phase2->setText(QApplication::translate("shortcutsTab", "Draw")); + lbl_TabGame_phase3->setText(QApplication::translate("shortcutsTab", "Main 1")); + lbl_TabGame_phase4->setText(QApplication::translate("shortcutsTab", "Start combat")); + lbl_TabGame_phase5->setText(QApplication::translate("shortcutsTab", "Attack")); + lbl_TabGame_phase6->setText(QApplication::translate("shortcutsTab", "Block")); + lbl_TabGame_phase7->setText(QApplication::translate("shortcutsTab", "Damage")); + lbl_TabGame_phase8->setText(QApplication::translate("shortcutsTab", "End combat")); + lbl_TabGame_phase9->setText(QApplication::translate("shortcutsTab", "Main 2")); + lbl_TabGame_phase10->setText(QApplication::translate("shortcutsTab", "End")); + lbl_TabGame_aNextPhase->setText(QApplication::translate("shortcutsTab", "Next phase")); + lbl_TabGame_aNextPhaseAction->setText(QApplication::translate("shortcutsTab", "Next phase action")); + lbl_TabGame_aNextTurn->setText(QApplication::translate("shortcutsTab", "Next turn")); + groupBox_13->setTitle(QApplication::translate("shortcutsTab", "Playing Area")); + lbl_Player_aTap->setText(QApplication::translate("shortcutsTab", "Tap / Untap Card")); + lbl_Player_aUntapAll->setText(QApplication::translate("shortcutsTab", "Untap all")); + lbl_Player_aDoesntUntap->setText(QApplication::translate("shortcutsTab", "Toggle untap")); + lbl_Player_aFlip->setText(QApplication::translate("shortcutsTab", "Flip card")); + lbl_Player_aPeek->setText(QApplication::translate("shortcutsTab", "Peek card")); + lbl_Player_aPlay->setText(QApplication::translate("shortcutsTab", "Play card")); + lbl_Player_aAttach->setText(QApplication::translate("shortcutsTab", "Attach card")); + lbl_Player_aUnattach->setText(QApplication::translate("shortcutsTab", "Unattach card")); + lbl_Player_aClone->setText(QApplication::translate("shortcutsTab", "Clone card")); + lbl_Player_aCreateToken->setText(QApplication::translate("shortcutsTab", "Create token")); + lbl_Player_aCreateRelatedTokens->setText(QApplication::translate("shortcutsTab", "Create all related tokens")); + lbl_Player_aCreateAnotherToken->setText(QApplication::translate("shortcutsTab", "Create another token")); + lbl_Player_aSetAnnotation->setText(QApplication::translate("shortcutsTab", "Set annotation")); tabWidget->setTabText(tabWidget->indexOf(tab_2), - QApplication::translate("shortcutsTab", "Phases | P/T | Playing Area", 0)); - groupBox_15->setTitle(QApplication::translate("shortcutsTab", "Move card to", 0)); - lbl_Player_aMoveToBottomLibrary->setText(QApplication::translate("shortcutsTab", "Bottom library", 0)); - lbl_Player_aMoveToTopLibrary->setText(QApplication::translate("shortcutsTab", "Top library", 0)); - lbl_Player_aMoveToGraveyard->setText(QApplication::translate("shortcutsTab", "Graveyard", 0)); - lbl_Player_aMoveToExile->setText(QApplication::translate("shortcutsTab", "Exile", 0)); - lbl_Player_aMoveToHand->setText(QApplication::translate("shortcutsTab", "Hand", 0)); + QApplication::translate("shortcutsTab", "Phases | P/T | Playing Area")); + groupBox_moveCard->setTitle(QApplication::translate("shortcutsTab", "Move selected card to")); + lbl_Player_aMoveToBottomLibrary->setText(QApplication::translate("shortcutsTab", "Bottom library")); + lbl_Player_aMoveToTopLibrary->setText(QApplication::translate("shortcutsTab", "Top library")); + lbl_Player_aMoveToGraveyard->setText(QApplication::translate("shortcutsTab", "Graveyard")); + lbl_Player_aMoveToExile->setText(QApplication::translate("shortcutsTab", "Exile")); + lbl_Player_aMoveToHand->setText(QApplication::translate("shortcutsTab", "Hand")); lbl_Player_aMoveTopToPlayFaceDown->setText(QApplication::translate("shortcutsTab", "Play face down")); - groupBox_16->setTitle(QApplication::translate("shortcutsTab", "View", 0)); - lbl_Player_aViewGraveyard->setText(QApplication::translate("shortcutsTab", "Graveyard", 0)); - lbl_Player_aViewLibrary->setText(QApplication::translate("shortcutsTab", "Library", 0)); - lbl_Player_aViewTopCards->setText(QApplication::translate("shortcutsTab", "Tops card of library", 0)); - lbl_Player_aViewSideboard->setText(QApplication::translate("shortcutsTab", "Sideboard", 0)); - lbl_Player_aViewRfg->setText(QApplication::translate("shortcutsTab", "Exile", 0)); - lbl_GameView_aCloseMostRecentZoneView->setText(QApplication::translate("shortcutsTab", "Close recent view", 0)); - groupBox_17->setTitle(QApplication::translate("shortcutsTab", "Game Lobby", 0)); - lbl_DeckViewContainer_loadRemoteButton->setText(QApplication::translate("shortcutsTab", "Load remote deck", 0)); - lbl_DeckViewContainer_loadLocalButton->setText(QApplication::translate("shortcutsTab", "Load local deck", 0)); - groupBox_18->setTitle(QApplication::translate("shortcutsTab", "Gameplay", 0)); - lbl_Player_aDrawArrow->setText(QApplication::translate("shortcutsTab", "Draw arrow", 0)); - lbl_TabGame_aLeaveGame->setText(QApplication::translate("shortcutsTab", "Leave game", 0)); - lbl_TabGame_aRemoveLocalArrows->setText(QApplication::translate("shortcutsTab", "Remove local arrows", 0)); - lbl_TabGame_aConcede->setText(QApplication::translate("shortcutsTab", "Concede", 0)); - lbl_Player_aRollDie->setText(QApplication::translate("shortcutsTab", "Roll dice", 0)); - lbl_TabGame_aRotateViewCW->setText(QApplication::translate("shortcutsTab", "Rotate view CW", 0)); - lbl_Player_aShuffle->setText(QApplication::translate("shortcutsTab", "Shuffle library", 0)); - lbl_TabGame_aRotateViewCCW->setText(QApplication::translate("shortcutsTab", "Rotate view CCW", 0)); - groupBox_14->setTitle(QApplication::translate("shortcutsTab", "Draw", 0)); - lbl_Player_aMulligan->setText(QApplication::translate("shortcutsTab", "Mulligan", 0)); - lbl_Player_aDrawCard->setText(QApplication::translate("shortcutsTab", "Draw card", 0)); - lbl_Player_aDrawCards->setText(QApplication::translate("shortcutsTab", "Draw cards", 0)); - lbl_Player_aUndoDraw->setText(QApplication::translate("shortcutsTab", "Undo draw", 0)); - lbl_Player_aAlwaysRevealTopCard->setText(QApplication::translate("shortcutsTab", "Always reveal top card", 0)); + groupBox_view->setTitle(QApplication::translate("shortcutsTab", "View")); + lbl_Player_aViewGraveyard->setText(QApplication::translate("shortcutsTab", "Graveyard")); + lbl_Player_aViewLibrary->setText(QApplication::translate("shortcutsTab", "Library")); + lbl_Player_aViewTopCards->setText(QApplication::translate("shortcutsTab", "Top cards of library")); + lbl_Player_aViewSideboard->setText(QApplication::translate("shortcutsTab", "Sideboard")); + lbl_Player_aViewRfg->setText(QApplication::translate("shortcutsTab", "Exile")); + lbl_GameView_aCloseMostRecentZoneView->setText(QApplication::translate("shortcutsTab", "Close recent view")); + groupBox_moveDeck->setTitle(QApplication::translate("shortcutsTab", "Move top card to")); + lbl_Player_aMoveTopCardToGraveyard->setText(QApplication::translate("shortcutsTab", "Graveyard Once")); + lbl_Player_aMoveTopCardsToGraveyard->setText(QApplication::translate("shortcutsTab", "Graveyard Multiple")); + lbl_Player_aMoveTopCardToExile->setText(QApplication::translate("shortcutsTab", "Exile Once")); + lbl_Player_aMoveTopCardsToExile->setText(QApplication::translate("shortcutsTab", "Exile Multiple")); + groupBox_gameLobby->setTitle(QApplication::translate("shortcutsTab", "Game Lobby")); + lbl_DeckViewContainer_loadRemoteButton->setText(QApplication::translate("shortcutsTab", "Load remote deck")); + lbl_DeckViewContainer_loadLocalButton->setText(QApplication::translate("shortcutsTab", "Load local deck")); + groupBox_gameplay->setTitle(QApplication::translate("shortcutsTab", "Gameplay")); + lbl_Player_aDrawArrow->setText(QApplication::translate("shortcutsTab", "Draw arrow")); + lbl_TabGame_aLeaveGame->setText(QApplication::translate("shortcutsTab", "Leave game")); + lbl_TabGame_aRemoveLocalArrows->setText(QApplication::translate("shortcutsTab", "Remove local arrows")); + lbl_TabGame_aConcede->setText(QApplication::translate("shortcutsTab", "Concede")); + lbl_Player_aRollDie->setText(QApplication::translate("shortcutsTab", "Roll dice")); + lbl_TabGame_aRotateViewCW->setText(QApplication::translate("shortcutsTab", "Rotate view CW")); + lbl_Player_aShuffle->setText(QApplication::translate("shortcutsTab", "Shuffle library")); + lbl_TabGame_aRotateViewCCW->setText(QApplication::translate("shortcutsTab", "Rotate view CCW")); + groupBox_draw->setTitle(QApplication::translate("shortcutsTab", "Drawing")); + lbl_Player_aMulligan->setText(QApplication::translate("shortcutsTab", "Mulligan")); + lbl_Player_aDrawCard->setText(QApplication::translate("shortcutsTab", "Draw card")); + lbl_Player_aDrawCards->setText(QApplication::translate("shortcutsTab", "Draw cards")); + lbl_Player_aUndoDraw->setText(QApplication::translate("shortcutsTab", "Undo draw")); + lbl_Player_aAlwaysRevealTopCard->setText(QApplication::translate("shortcutsTab", "Always reveal top card")); tabWidget->setTabText(tabWidget->indexOf(tab_3), - QApplication::translate("shortcutsTab", "Draw | Move | View | Gameplay", 0)); - tabWidget->setTabText(tabWidget->indexOf(tab_4), QApplication::translate("shortcutsTab", "Counters", 0)); + QApplication::translate("shortcutsTab", "Gameplay | Draw | Move | View")); + tabWidget->setTabText(tabWidget->indexOf(tab_4), QApplication::translate("shortcutsTab", "Counters")); faqLabel->setText(QString("%2") .arg(WIKI) - .arg(QApplication::translate("shortcutsTab", "How to set custom shortcuts", 0))); - btnResetAll->setText(QApplication::translate("shortcutsTab", "Restore all default shortcuts", 0)); - btnClearAll->setText(QApplication::translate("shortcutsTab", "Clear all shortcuts", 0)); + .arg(QApplication::translate("shortcutsTab", "How to set custom shortcuts"))); + btnResetAll->setText(QApplication::translate("shortcutsTab", "Restore all default shortcuts")); + btnClearAll->setText(QApplication::translate("shortcutsTab", "Clear all shortcuts")); } // retranslateUi }; diff --git a/cockatrice/src/settings/downloadsettings.cpp b/cockatrice/src/settings/downloadsettings.cpp new file mode 100644 index 000000000..239b3d6cc --- /dev/null +++ b/cockatrice/src/settings/downloadsettings.cpp @@ -0,0 +1,56 @@ +#include "downloadsettings.h" +#include "settingsmanager.h" + +DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr) + : SettingsManager(settingPath + "downloads.ini", parent) +{ + downloadURLs = getValue("urls", "downloads").value(); +} + +void DownloadSettings::setDownloadUrlAt(int index, const QString &url) +{ + downloadURLs.insert(index, url); + setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); +} + +/** + * If reset or first run, this method contains the default URLs we will populate + */ +QStringList DownloadSettings::getAllURLs() +{ + // First run, these will be empty + if (downloadURLs.count() == 0) { + populateDefaultURLs(); + } + + return downloadURLs; +} + +void DownloadSettings::populateDefaultURLs() +{ + downloadURLs.clear(); + downloadURLs.append("https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!"); + downloadURLs.append("https://api.scryfall.com/cards/multiverse/!set:muid!?format=image"); + downloadURLs.append("http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card"); + downloadURLs.append("http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"); + setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); +} + +QString DownloadSettings::getDownloadUrlAt(int index) +{ + if (0 <= index && index < downloadURLs.size()) { + return downloadURLs[index]; + } + + return ""; +} + +int DownloadSettings::getCount() +{ + return downloadURLs.size(); +} + +void DownloadSettings::clear() +{ + downloadURLs.clear(); +} \ No newline at end of file diff --git a/cockatrice/src/settings/downloadsettings.h b/cockatrice/src/settings/downloadsettings.h new file mode 100644 index 000000000..f3c3e4d12 --- /dev/null +++ b/cockatrice/src/settings/downloadsettings.h @@ -0,0 +1,28 @@ +#ifndef COCKATRICE_DOWNLOADSETTINGS_H +#define COCKATRICE_DOWNLOADSETTINGS_H + +#include "settingsmanager.h" +#include + +class DownloadSettings : public SettingsManager +{ + Q_OBJECT + friend class SettingsCache; + +public: + explicit DownloadSettings(const QString &, QObject *); + + QStringList getAllURLs(); + QString getDownloadUrlAt(int); + void setDownloadUrlAt(int, const QString &); + int getCount(); + void clear(); + +private: + QStringList downloadURLs; + +private: + void populateDefaultURLs(); +}; + +#endif // COCKATRICE_DOWNLOADSETTINGS_H diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 76c573f1b..8583885a4 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -164,6 +164,7 @@ SettingsCache::SettingsCache() messageSettings = new MessageSettings(settingsPath, this); gameFiltersSettings = new GameFiltersSettings(settingsPath, this); layoutsSettings = new LayoutsSettings(settingsPath, this); + downloadSettings = new DownloadSettings(settingsPath, this); if (!QFile(settingsPath + "global.ini").exists()) translateLegacySettings(); @@ -176,6 +177,7 @@ SettingsCache::SettingsCache() mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool(); notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); + notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool(); updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt(); lang = settings->value("personal/lang").toString(); @@ -220,9 +222,6 @@ SettingsCache::SettingsCache() picDownload = settings->value("personal/picturedownload", true).toBool(); - picUrl = settings->value("personal/picUrl", PIC_URL_DEFAULT).toString(); - picUrlFallback = settings->value("personal/picUrlFallback", PIC_URL_FALLBACK).toString(); - mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray(); tokenDialogGeometry = settings->value("interface/token_dialog_geometry").toByteArray(); notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool(); @@ -234,7 +233,7 @@ SettingsCache::SettingsCache() displayCardNames = settings->value("cards/displaycardnames", true).toBool(); horizontalHand = settings->value("hand/horizontal", true).toBool(); invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); - minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 5).toInt(); + minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt(); tapAnimation = settings->value("cards/tapanimation", true).toBool(); chatMention = settings->value("chat/mention", true).toBool(); chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool(); @@ -277,6 +276,7 @@ SettingsCache::SettingsCache() spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool(); rememberGameSettings = settings->value("game/remembergamesettings", true).toBool(); clientID = settings->value("personal/clientid", "notset").toString(); + clientVersion = settings->value("personal/clientversion", "notset").toString(); knownMissingFeatures = settings->value("interface/knownmissingfeatures", "").toString(); } @@ -415,18 +415,6 @@ void SettingsCache::setPicDownload(int _picDownload) emit picDownloadChanged(); } -void SettingsCache::setPicUrl(const QString &_picUrl) -{ - picUrl = _picUrl; - settings->setValue("personal/picUrl", picUrl); -} - -void SettingsCache::setPicUrlFallback(const QString &_picUrlFallback) -{ - picUrlFallback = _picUrlFallback; - settings->setValue("personal/picUrlFallback", picUrlFallback); -} - void SettingsCache::setNotificationsEnabled(int _notificationsEnabled) { notificationsEnabled = static_cast(_notificationsEnabled); @@ -603,6 +591,12 @@ void SettingsCache::setClientID(QString _clientID) settings->setValue("personal/clientid", clientID); } +void SettingsCache::setClientVersion(QString _clientVersion) +{ + clientVersion = std::move(_clientVersion); + settings->setValue("personal/clientversion", clientVersion); +} + QStringList SettingsCache::getCountries() const { static QStringList countries = QStringList() << "ad" @@ -924,6 +918,12 @@ void SettingsCache::setNotifyAboutUpdate(int _notifyaboutupdate) settings->setValue("personal/updatenotification", notifyAboutUpdates); } +void SettingsCache::setNotifyAboutNewVersion(int _notifyaboutnewversion) +{ + notifyAboutNewVersion = static_cast(_notifyaboutnewversion); + settings->setValue("personal/newversionnotification", notifyAboutNewVersion); +} + void SettingsCache::setDownloadSpoilerStatus(bool _spoilerStatus) { mbDownloadSpoilers = _spoilerStatus; @@ -941,4 +941,4 @@ void SettingsCache::setMaxFontSize(int _max) { maxFontSize = _max; settings->setValue("game/maxfontsize", maxFontSize); -} \ No newline at end of file +} diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index 1f21a8c2b..0dca177f5 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -2,6 +2,7 @@ #define SETTINGSCACHE_H #include "settings/carddatabasesettings.h" +#include "settings/downloadsettings.h" #include "settings/gamefilterssettings.h" #include "settings/layoutssettings.h" #include "settings/messagesettings.h" @@ -13,9 +14,6 @@ class ReleaseChannel; -// the falbacks are used for cards without a muid -#define PIC_URL_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card" -#define PIC_URL_FALLBACK "http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card" // size should be a multiple of 64 #define PIXMAPCACHE_SIZE_DEFAULT 2047 #define PIXMAPCACHE_SIZE_MIN 64 @@ -60,6 +58,7 @@ private: MessageSettings *messageSettings; GameFiltersSettings *gameFiltersSettings; LayoutsSettings *layoutsSettings; + DownloadSettings *downloadSettings; QByteArray mainWindowGeometry; QByteArray tokenDialogGeometry; @@ -67,6 +66,7 @@ private: QString deckPath, replaysPath, picsPath, customPicsPath, cardDatabasePath, customCardDatabasePath, spoilerDatabasePath, tokenDatabasePath, themeName; bool notifyAboutUpdates; + bool notifyAboutNewVersion; bool showTipsOnStartup; QList seenTips; bool mbDownloadSpoilers; @@ -98,6 +98,7 @@ private: QString picUrl; QString picUrlFallback; QString clientID; + QString clientVersion; QString knownMissingFeatures; int pixmapCacheSize; bool scaleCards; @@ -201,6 +202,10 @@ public: { return notifyAboutUpdates; } + bool getNotifyAboutNewVersion() const + { + return notifyAboutNewVersion; + } bool getShowTipsOnStartup() const { return showTipsOnStartup; @@ -302,14 +307,6 @@ public: { return ignoreUnregisteredUserMessages; } - QString getPicUrl() const - { - return picUrl; - } - QString getPicUrlFallback() const - { - return picUrlFallback; - } int getPixmapCacheSize() const { return pixmapCacheSize; @@ -396,11 +393,16 @@ public: return maxFontSize; } void setClientID(QString clientID); + void setClientVersion(QString clientVersion); void setKnownMissingFeatures(QString _knownMissingFeatures); QString getClientID() { return clientID; } + QString getClientVersion() + { + return clientVersion; + } QString getKnownMissingFeatures() { return knownMissingFeatures; @@ -429,6 +431,10 @@ public: { return *layoutsSettings; } + DownloadSettings &downloads() const + { + return *downloadSettings; + } bool getIsPortableBuild() const { return isPortableBuild; @@ -477,8 +483,6 @@ public slots: void setSoundThemeName(const QString &_soundThemeName); void setIgnoreUnregisteredUsers(int _ignoreUnregisteredUsers); void setIgnoreUnregisteredUserMessages(int _ignoreUnregisteredUserMessages); - void setPicUrl(const QString &_picUrl); - void setPicUrlFallback(const QString &_picUrlFallback); void setPixmapCacheSize(const int _pixmapCacheSize); void setCardScaling(const int _scaleCards); void setShowMessagePopups(const int _showMessagePopups); @@ -499,6 +503,7 @@ public slots: void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything); void setRememberGameSettings(const bool _rememberGameSettings); void setNotifyAboutUpdate(int _notifyaboutupdate); + void setNotifyAboutNewVersion(int _notifyaboutnewversion); void setUpdateReleaseChannel(int _updateReleaseChannel); void setMaxFontSize(int _max); }; diff --git a/cockatrice/src/shortcutssettings.cpp b/cockatrice/src/shortcutssettings.cpp index 66078c297..4fe4d844d 100644 --- a/cockatrice/src/shortcutssettings.cpp +++ b/cockatrice/src/shortcutssettings.cpp @@ -4,19 +4,18 @@ #include #include -ShortcutsSettings::ShortcutsSettings(QString settingsPath, QObject *parent) : QObject(parent) +ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *parent) : QObject(parent) { - this->settingsFilePath = std::move(settingsPath); - this->settingsFilePath.append("shortcuts.ini"); - fillDefaultShorcuts(); - shortCuts = QMap>(defaultShortCuts); + shortCuts = defaultShortCuts; + settingsFilePath = settingsPath; + settingsFilePath.append("shortcuts.ini"); bool exists = QFile(settingsFilePath).exists(); QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat); if (exists) { - shortCutsFile.beginGroup("Custom"); + shortCutsFile.beginGroup(custom); const QStringList customKeys = shortCutsFile.allKeys(); QMap invalidItems; @@ -55,114 +54,123 @@ ShortcutsSettings::ShortcutsSettings(QString settingsPath, QObject *parent) : QO } } -QList ShortcutsSettings::getShortcut(QString name) +QList ShortcutsSettings::getDefaultShortcut(const QString &name) const +{ + return defaultShortCuts.value(name, QList()); +} + +QList ShortcutsSettings::getShortcut(const QString &name) const { if (shortCuts.contains(name)) { return shortCuts.value(name); } - return defaultShortCuts.value(name, QList()); + return getDefaultShortcut(name); } -QKeySequence ShortcutsSettings::getSingleShortcut(QString name) +QKeySequence ShortcutsSettings::getSingleShortcut(const QString &name) const { - return getShortcut(std::move(name)).at(0); + return getShortcut(name).at(0); } -QString ShortcutsSettings::getDefaultShortcutString(QString name) +QString ShortcutsSettings::getDefaultShortcutString(const QString &name) const { - return stringifySequence(defaultShortCuts.value(name)); + return stringifySequence(getDefaultShortcut(name)); } -QString ShortcutsSettings::getShortcutString(QString name) +QString ShortcutsSettings::getShortcutString(const QString &name) const { - return stringifySequence(shortCuts.value(name)); + return stringifySequence(getShortcut(name)); } -QString ShortcutsSettings::stringifySequence(QList Sequence) const +QString ShortcutsSettings::stringifySequence(const QList &Sequence) const { - QString stringSequence; - for (int i = 0; i < Sequence.size(); ++i) { - stringSequence.append(Sequence.at(i).toString(QKeySequence::PortableText)); - if (i < Sequence.size() - 1) { - stringSequence.append(";"); - } + QStringList stringSequence; + for (const auto &i : Sequence) { + stringSequence.append(i.toString(QKeySequence::PortableText)); } - return stringSequence; + return stringSequence.join(sep); } -QList ShortcutsSettings::parseSequenceString(QString stringSequence) +QList ShortcutsSettings::parseSequenceString(const QString &stringSequence) const { - QStringList Sequences = stringSequence.split(";"); QList SequenceList; - for (QStringList::const_iterator ss = Sequences.constBegin(); ss != Sequences.constEnd(); ++ss) { - SequenceList.append(QKeySequence(*ss, QKeySequence::PortableText)); + for (const QString &shortcut : stringSequence.split(sep)) { + SequenceList.append(QKeySequence(shortcut, QKeySequence::PortableText)); } return SequenceList; } -void ShortcutsSettings::setShortcuts(QString name, QList Sequence) +void ShortcutsSettings::setShortcuts(const QString &name, const QList &Sequence) { shortCuts[name] = Sequence; QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat); - shortCutsFile.beginGroup("Custom"); - QString stringSequence = stringifySequence(Sequence); - shortCutsFile.setValue(name, stringSequence); + shortCutsFile.beginGroup(custom); + shortCutsFile.setValue(name, stringifySequence(Sequence)); shortCutsFile.endGroup(); - emit shortCutchanged(); + emit shortCutChanged(); } -void ShortcutsSettings::setShortcuts(QString name, QKeySequence Sequence) +void ShortcutsSettings::setShortcuts(const QString &name, const QKeySequence &Sequence) { - setShortcuts(std::move(name), QList() << Sequence); + setShortcuts(name, QList{Sequence}); } -void ShortcutsSettings::setShortcuts(QString name, QString Sequences) +void ShortcutsSettings::setShortcuts(const QString &name, const QString &Sequences) { - setShortcuts(std::move(name), parseSequenceString(std::move(Sequences))); + setShortcuts(name, parseSequenceString(Sequences)); } -bool ShortcutsSettings::isKeyAllowed(QString name, QString Sequences) +void ShortcutsSettings::resetAllShortcuts() +{ + shortCuts = defaultShortCuts; + QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat); + shortCutsFile.beginGroup(custom); + shortCutsFile.remove(""); + shortCutsFile.endGroup(); + emit shortCutChanged(); + emit allShortCutsReset(); +} + +void ShortcutsSettings::clearAllShortcuts() +{ + QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat); + shortCutsFile.beginGroup(custom); + for (auto it = shortCuts.begin(); it != shortCuts.end(); ++it) { + it.value() = parseSequenceString(""); + shortCutsFile.setValue(it.key(), ""); + } + shortCutsFile.endGroup(); + emit shortCutChanged(); + emit allShortCutsClear(); +} + +bool ShortcutsSettings::isKeyAllowed(const QString &name, const QString &Sequences) const { // if the shortcut is not to be used in deck-editor then it doesn't matter if (name.startsWith("Player")) { return true; } - QString checkSequence = Sequences.split(";").last(); - QStringList forbiddenKeys = (QStringList() << "Del" - << "Backspace" - << "Down" - << "Up" - << "Left" - << "Right" - << "Return" - << "Enter" - << "Menu" - << "Ctrl+Alt+-" - << "Ctrl+Alt+=" - << "Ctrl+Alt+[" - << "Ctrl+Alt+]" - << "Tab" - << "Space" - << "Shift+S" - << "Shift+Left" - << "Shift+Right"); + QString checkSequence = Sequences.split(sep).last(); + QStringList forbiddenKeys{"Del", "Backspace", "Down", "Up", "Left", "Right", + "Return", "Enter", "Menu", "Ctrl+Alt+-", "Ctrl+Alt+=", "Ctrl+Alt+[", + "Ctrl+Alt+]", "Tab", "Space", "Shift+S", "Shift+Left", "Shift+Right"}; return !forbiddenKeys.contains(checkSequence); } -bool ShortcutsSettings::isValid(QString name, QString Sequences) +bool ShortcutsSettings::isValid(const QString &name, const QString &Sequences) const { - QString checkSequence = Sequences.split(";").last(); + QString checkSequence = Sequences.split(sep).last(); QString checkKey = name.left(name.indexOf("/")); QList allKeys = shortCuts.keys(); for (const auto &key : allKeys) { if (key.startsWith(checkKey) || key.startsWith("MainWindow") || checkKey.startsWith("MainWindow")) { QString storedSequence = stringifySequence(shortCuts.value(key)); - QStringList stringSequences = storedSequence.split(";"); + QStringList stringSequences = storedSequence.split(sep); if (stringSequences.contains(checkSequence)) { return false; } @@ -170,161 +178,3 @@ bool ShortcutsSettings::isValid(QString name, QString Sequences) } return true; } - -void ShortcutsSettings::resetAllShortcuts() -{ - for (auto it = defaultShortCuts.begin(); it != defaultShortCuts.end(); ++it) { - setShortcuts(it.key(), it.value()); - } - emit allShortCutsReset(); -} - -void ShortcutsSettings::clearAllShortcuts() -{ - for (auto it = shortCuts.begin(); it != shortCuts.end(); ++it) { - setShortcuts(it.key(), ""); - } - emit allShortCutsClear(); -} - -void ShortcutsSettings::fillDefaultShorcuts() -{ - defaultShortCuts["MainWindow/aCheckCardUpdates"] = parseSequenceString(""); - defaultShortCuts["MainWindow/aConnect"] = parseSequenceString("Ctrl+L"); - defaultShortCuts["MainWindow/aDeckEditor"] = parseSequenceString(""); - defaultShortCuts["MainWindow/aDisconnect"] = parseSequenceString(""); - defaultShortCuts["MainWindow/aExit"] = parseSequenceString(""); - defaultShortCuts["MainWindow/aFullScreen"] = parseSequenceString("Ctrl+F"); - defaultShortCuts["MainWindow/aRegister"] = parseSequenceString(""); - defaultShortCuts["MainWindow/aSettings"] = parseSequenceString(""); - defaultShortCuts["MainWindow/aSinglePlayer"] = parseSequenceString(""); - defaultShortCuts["MainWindow/aWatchReplay"] = parseSequenceString(""); - - defaultShortCuts["TabDeckEditor/aAnalyzeDeck"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aClearFilterAll"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aClearFilterOne"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aClose"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aDecrement"] = parseSequenceString("-"); - defaultShortCuts["TabDeckEditor/aManageSets"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aEditTokens"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aExportDeckDecklist"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aIncrement"] = parseSequenceString("+"); - defaultShortCuts["TabDeckEditor/aLoadDeck"] = parseSequenceString("Ctrl+O"); - defaultShortCuts["TabDeckEditor/aLoadDeckFromClipboard"] = parseSequenceString("Ctrl+Shift+V"); - defaultShortCuts["TabDeckEditor/aNewDeck"] = parseSequenceString("Ctrl+N"); - defaultShortCuts["TabDeckEditor/aOpenCustomFolder"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aPrintDeck"] = parseSequenceString("Ctrl+P"); - defaultShortCuts["TabDeckEditor/aRemoveCard"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aResetLayout"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aSaveDeck"] = parseSequenceString("Ctrl+S"); - defaultShortCuts["TabDeckEditor/aSaveDeckAs"] = parseSequenceString(""); - defaultShortCuts["TabDeckEditor/aSaveDeckToClipboard"] = parseSequenceString("Ctrl+Shift+C"); - defaultShortCuts["TabDeckEditor/aSaveDeckToClipboardRaw"] = parseSequenceString("Ctrl+Shift+R"); - - defaultShortCuts["DeckViewContainer/loadLocalButton"] = parseSequenceString("Ctrl+O"); - defaultShortCuts["DeckViewContainer/loadRemoteButton"] = parseSequenceString("Ctrl+Alt+O"); - - defaultShortCuts["Player/aDec"] = parseSequenceString("F11"); - defaultShortCuts["Player/aInc"] = parseSequenceString("F12"); - defaultShortCuts["Player/aSet"] = parseSequenceString("Ctrl+L"); - defaultShortCuts["Player/aCloseMostRecentZoneView"] = parseSequenceString("Esc"); - defaultShortCuts["Player/IncP"] = parseSequenceString("Ctrl++"); - defaultShortCuts["Player/aAlwaysRevealTopCard"] = parseSequenceString("Ctrl+N"); - defaultShortCuts["Player/aAttach"] = parseSequenceString("Ctrl+Alt+A"); - defaultShortCuts["Player/aCCGreen"] = parseSequenceString(""); - defaultShortCuts["Player/aCCRed"] = parseSequenceString(""); - defaultShortCuts["Player/aCCYellow"] = parseSequenceString(""); - defaultShortCuts["Player/aClone"] = parseSequenceString("Ctrl+J"); - defaultShortCuts["Player/aCreateAnotherToken"] = parseSequenceString("Ctrl+G"); - defaultShortCuts["Player/aCreateToken"] = parseSequenceString("Ctrl+T"); - defaultShortCuts["Player/aCreateRelatedTokens"] = parseSequenceString("Ctrl+Shift+T"); - defaultShortCuts["Player/aDecP"] = parseSequenceString("Ctrl+-"); - defaultShortCuts["Player/aDecPT"] = parseSequenceString("Ctrl+Alt+-"); - defaultShortCuts["Player/aDecT"] = parseSequenceString("Alt+-"); - defaultShortCuts["Player/aDoesntUntap"] = parseSequenceString(""); - defaultShortCuts["Player/aDrawArrow"] = parseSequenceString(""); - defaultShortCuts["Player/aDrawCard"] = parseSequenceString("Ctrl+D"); - defaultShortCuts["Player/aDrawCards"] = parseSequenceString("Ctrl+E"); - defaultShortCuts["Player/aFlip"] = parseSequenceString(""); - defaultShortCuts["Player/aIncPT"] = parseSequenceString("Ctrl+Alt++"); - defaultShortCuts["Player/aIncT"] = parseSequenceString("Alt++"); - defaultShortCuts["Player/aMoveToBottomLibrary"] = parseSequenceString(""); - defaultShortCuts["Player/aMoveToExile"] = parseSequenceString(""); - defaultShortCuts["Player/aMoveToGraveyard"] = parseSequenceString("Ctrl+Del"); - defaultShortCuts["Player/aMoveToHand"] = parseSequenceString(""); - defaultShortCuts["Player/aMoveToTopLibrary"] = parseSequenceString(""); - defaultShortCuts["Player/aMulligan"] = parseSequenceString("Ctrl+M"); - defaultShortCuts["Player/aPeek"] = parseSequenceString(""); - defaultShortCuts["Player/aPlay"] = parseSequenceString(""); - defaultShortCuts["Player/aRCGreen"] = parseSequenceString(""); - defaultShortCuts["Player/aRCRed"] = parseSequenceString(""); - defaultShortCuts["Player/aRCYellow"] = parseSequenceString(""); - defaultShortCuts["Player/aRollDie"] = parseSequenceString("Ctrl+I"); - defaultShortCuts["Player/aSCGreen"] = parseSequenceString(""); - defaultShortCuts["Player/aSCRed"] = parseSequenceString(""); - defaultShortCuts["Player/aSCYellow"] = parseSequenceString(""); - defaultShortCuts["Player/aSetAnnotation"] = parseSequenceString(""); - defaultShortCuts["Player/aSetPT"] = parseSequenceString("Ctrl+P"); - defaultShortCuts["Player/aResetPT"] = parseSequenceString("Ctrl+Alt+0"); - defaultShortCuts["Player/aShuffle"] = parseSequenceString("Ctrl+S"); - defaultShortCuts["Player/aTap"] = parseSequenceString(""); - defaultShortCuts["Player/aUnattach"] = parseSequenceString(""); - defaultShortCuts["Player/aUndoDraw"] = parseSequenceString("Ctrl+Shift+D"); - defaultShortCuts["Player/aUntapAll"] = parseSequenceString("Ctrl+U"); - defaultShortCuts["Player/aViewGraveyard"] = parseSequenceString("F4"); - defaultShortCuts["Player/aViewLibrary"] = parseSequenceString("F3"); - defaultShortCuts["Player/aViewRfg"] = parseSequenceString(""); - defaultShortCuts["Player/aViewSideboard"] = parseSequenceString("Ctrl+F3"); - defaultShortCuts["Player/aViewTopCards"] = parseSequenceString("Ctrl+W"); - defaultShortCuts["Player/aConcede"] = parseSequenceString("F2"); - defaultShortCuts["Player/aLeaveGame"] = parseSequenceString("Ctrl+Q"); - defaultShortCuts["Player/aNextPhase"] = parseSequenceString("Ctrl+Space;Tab"); - defaultShortCuts["Player/aNextTurn"] = parseSequenceString("Ctrl+Return;Ctrl+Enter"); - defaultShortCuts["Player/aRemoveLocalArrows"] = parseSequenceString("Ctrl+R"); - defaultShortCuts["Player/aRotateViewCCW"] = parseSequenceString(""); - defaultShortCuts["Player/aRotateViewCW"] = parseSequenceString(""); - defaultShortCuts["Player/phase0"] = parseSequenceString("F5"); - defaultShortCuts["Player/phase1"] = parseSequenceString(""); - defaultShortCuts["Player/phase10"] = parseSequenceString("F10"); - defaultShortCuts["Player/phase2"] = parseSequenceString("F6"); - defaultShortCuts["Player/phase3"] = parseSequenceString("F7"); - defaultShortCuts["Player/phase4"] = parseSequenceString("F8"); - defaultShortCuts["Player/phase5"] = parseSequenceString(""); - defaultShortCuts["Player/phase6"] = parseSequenceString(""); - defaultShortCuts["Player/phase7"] = parseSequenceString(""); - defaultShortCuts["Player/phase8"] = parseSequenceString(""); - defaultShortCuts["Player/phase9"] = parseSequenceString("F9"); - - defaultShortCuts["Player/aIncCounter_w"] = parseSequenceString(""); - defaultShortCuts["Player/aDecCounter_w"] = parseSequenceString(""); - defaultShortCuts["Player/aSetCounter_w"] = parseSequenceString(""); - - defaultShortCuts["Player/aIncCounter_u"] = parseSequenceString(""); - defaultShortCuts["Player/aDecCounter_u"] = parseSequenceString(""); - defaultShortCuts["Player/aSetCounter_u"] = parseSequenceString(""); - - defaultShortCuts["Player/aIncCounter_b"] = parseSequenceString(""); - defaultShortCuts["Player/aDecCounter_b"] = parseSequenceString(""); - defaultShortCuts["Player/aSetCounter_b"] = parseSequenceString(""); - - defaultShortCuts["Player/aIncCounter_r"] = parseSequenceString(""); - defaultShortCuts["Player/aDecCounter_r"] = parseSequenceString(""); - defaultShortCuts["Player/aSetCounter_r"] = parseSequenceString(""); - - defaultShortCuts["Player/aIncCounter_g"] = parseSequenceString(""); - defaultShortCuts["Player/aDecCounter_g"] = parseSequenceString(""); - defaultShortCuts["Player/aSetCounter_g"] = parseSequenceString(""); - - defaultShortCuts["Player/aIncCounter_x"] = parseSequenceString(""); - defaultShortCuts["Player/aDecCounter_x"] = parseSequenceString(""); - defaultShortCuts["Player/aSetCounter_x"] = parseSequenceString(""); - - defaultShortCuts["Player/aIncCounter_storm"] = parseSequenceString(""); - defaultShortCuts["Player/aDecCounter_storm"] = parseSequenceString(""); - defaultShortCuts["Player/aSetCounter_storm"] = parseSequenceString(""); - - defaultShortCuts["tab_room/aClearChat"] = parseSequenceString("F12"); - defaultShortCuts["DlgLoadDeckFromClipboard/refreshButton"] = parseSequenceString("F5"); - defaultShortCuts["Player/aResetLayout"] = parseSequenceString(""); - defaultShortCuts["Player/aMoveTopToPlayFaceDown"] = parseSequenceString("Ctrl+Shift+E"); -} diff --git a/cockatrice/src/shortcutssettings.h b/cockatrice/src/shortcutssettings.h index 6d799448c..6887cf75d 100644 --- a/cockatrice/src/shortcutssettings.h +++ b/cockatrice/src/shortcutssettings.h @@ -1,8 +1,8 @@ #ifndef SHORTCUTSSETTINGS_H #define SHORTCUTSSETTINGS_H +#include #include -#include #include #include @@ -10,37 +10,186 @@ class ShortcutsSettings : public QObject { Q_OBJECT public: - ShortcutsSettings(QString settingsFilePath, QObject *parent = nullptr); + ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr); - QList getShortcut(QString name); - QKeySequence getSingleShortcut(QString name); + QList getDefaultShortcut(const QString &name) const; + QList getShortcut(const QString &name) const; + QKeySequence getSingleShortcut(const QString &name) const; + QString getDefaultShortcutString(const QString &name) const; + QString getShortcutString(const QString &name) const; - QString getDefaultShortcutString(QString name); - QString getShortcutString(QString name); + void setShortcuts(const QString &name, const QList &Sequence); + void setShortcuts(const QString &name, const QKeySequence &Sequence); + void setShortcuts(const QString &name, const QString &Sequences); - void setShortcuts(QString name, QList Sequence); - void setShortcuts(QString name, QKeySequence Sequence); - void setShortcuts(QString name, QString Sequences); - - bool isKeyAllowed(QString name, QString Sequences); - bool isValid(QString name, QString Sequences); + bool isKeyAllowed(const QString &name, const QString &Sequences) const; + bool isValid(const QString &name, const QString &Sequences) const; void resetAllShortcuts(); void clearAllShortcuts(); signals: - void shortCutchanged(); + void shortCutChanged(); void allShortCutsReset(); void allShortCutsClear(); private: + const QChar sep = ';'; + const QString custom = "Custom"; QString settingsFilePath; - QMap> shortCuts; - QMap> defaultShortCuts; - void fillDefaultShorcuts(); + QHash> shortCuts; - QString stringifySequence(QList Sequence) const; - QList parseSequenceString(QString stringSequence); + QString stringifySequence(const QList &Sequence) const; + QList parseSequenceString(const QString &stringSequence) const; + + const QHash> defaultShortCuts{ + {"MainWindow/aCheckCardUpdates", parseSequenceString("")}, + {"MainWindow/aConnect", parseSequenceString("Ctrl+L")}, + {"MainWindow/aDeckEditor", parseSequenceString("")}, + {"MainWindow/aDisconnect", parseSequenceString("")}, + {"MainWindow/aExit", parseSequenceString("")}, + {"MainWindow/aFullScreen", parseSequenceString("Ctrl+F")}, + {"MainWindow/aRegister", parseSequenceString("")}, + {"MainWindow/aSettings", parseSequenceString("")}, + {"MainWindow/aSinglePlayer", parseSequenceString("")}, + {"MainWindow/aWatchReplay", parseSequenceString("")}, + + {"TabDeckEditor/aAnalyzeDeck", parseSequenceString("")}, + {"TabDeckEditor/aClearFilterAll", parseSequenceString("")}, + {"TabDeckEditor/aClearFilterOne", parseSequenceString("")}, + {"TabDeckEditor/aClose", parseSequenceString("")}, + {"TabDeckEditor/aDecrement", parseSequenceString("-")}, + {"TabDeckEditor/aManageSets", parseSequenceString("")}, + {"TabDeckEditor/aEditTokens", parseSequenceString("")}, + {"TabDeckEditor/aExportDeckDecklist", parseSequenceString("")}, + {"TabDeckEditor/aIncrement", parseSequenceString("+")}, + {"TabDeckEditor/aLoadDeck", parseSequenceString("Ctrl+O")}, + {"TabDeckEditor/aLoadDeckFromClipboard", parseSequenceString("Ctrl+Shift+V")}, + {"TabDeckEditor/aNewDeck", parseSequenceString("Ctrl+N")}, + {"TabDeckEditor/aOpenCustomFolder", parseSequenceString("")}, + {"TabDeckEditor/aPrintDeck", parseSequenceString("Ctrl+P")}, + {"TabDeckEditor/aRemoveCard", parseSequenceString("")}, + {"TabDeckEditor/aResetLayout", parseSequenceString("")}, + {"TabDeckEditor/aSaveDeck", parseSequenceString("Ctrl+S")}, + {"TabDeckEditor/aSaveDeckAs", parseSequenceString("")}, + {"TabDeckEditor/aSaveDeckToClipboard", parseSequenceString("Ctrl+Shift+C")}, + {"TabDeckEditor/aSaveDeckToClipboardRaw", parseSequenceString("Ctrl+Shift+R")}, + + {"DeckViewContainer/loadLocalButton", parseSequenceString("Ctrl+O")}, + {"DeckViewContainer/loadRemoteButton", parseSequenceString("Ctrl+Alt+O")}, + + {"Player/aSet", parseSequenceString("Ctrl+L")}, + {"Player/aAlwaysRevealTopCard", parseSequenceString("Ctrl+N")}, + {"Player/aCloseMostRecentZoneView", parseSequenceString("Esc")}, + {"Player/aDrawCard", parseSequenceString("Ctrl+D")}, + {"Player/aDrawCards", parseSequenceString("Ctrl+E")}, + {"Player/aDec", parseSequenceString("F11")}, + {"Player/aInc", parseSequenceString("F12")}, + {"Player/aMoveTopCardToGraveyard", parseSequenceString("")}, + {"Player/aMoveTopCardsToGraveyard", parseSequenceString("Ctrl+Shift+M")}, + {"Player/aMoveTopCardToExile", parseSequenceString("")}, + {"Player/aMoveTopCardsToExile", parseSequenceString("")}, + {"Player/aMoveTopToPlayFaceDown", parseSequenceString("Ctrl+Shift+E")}, + {"Player/aMulligan", parseSequenceString("Ctrl+M")}, + {"Player/aPeek", parseSequenceString("")}, + {"Player/aPlay", parseSequenceString("")}, + {"Player/aResetLayout", parseSequenceString("")}, + {"Player/aRollDie", parseSequenceString("Ctrl+I")}, + {"Player/aShuffle", parseSequenceString("Ctrl+S")}, + {"Player/aUndoDraw", parseSequenceString("Ctrl+Shift+D")}, + {"Player/aUntapAll", parseSequenceString("Ctrl+U")}, + {"Player/aViewGraveyard", parseSequenceString("F4")}, + {"Player/aViewLibrary", parseSequenceString("F3")}, + {"Player/aViewRfg", parseSequenceString("")}, + {"Player/aViewSideboard", parseSequenceString("Ctrl+F3")}, + {"Player/aViewTopCards", parseSequenceString("Ctrl+W")}, + + {"Player/aAttach", parseSequenceString("Ctrl+Alt+A")}, + {"Player/aClone", parseSequenceString("Ctrl+J")}, + {"Player/aCreateAnotherToken", parseSequenceString("Ctrl+G")}, + {"Player/aCreateToken", parseSequenceString("Ctrl+T")}, + {"Player/aCreateRelatedTokens", parseSequenceString("Ctrl+Shift+T")}, + {"Player/aDoesntUntap", parseSequenceString("")}, + {"Player/aDrawArrow", parseSequenceString("")}, + {"Player/aFlip", parseSequenceString("")}, + {"Player/aMoveToBottomLibrary", parseSequenceString("")}, + {"Player/aMoveToExile", parseSequenceString("")}, + {"Player/aMoveToGraveyard", parseSequenceString("Ctrl+Del")}, + {"Player/aMoveToHand", parseSequenceString("")}, + {"Player/aMoveToTopLibrary", parseSequenceString("")}, + {"Player/aSetAnnotation", parseSequenceString("")}, + {"Player/aTap", parseSequenceString("")}, + {"Player/aUnattach", parseSequenceString("")}, + + {"Player/aCCGreen", parseSequenceString("")}, + {"Player/aCCRed", parseSequenceString("")}, + {"Player/aCCYellow", parseSequenceString("")}, + {"Player/aRCGreen", parseSequenceString("")}, + {"Player/aRCRed", parseSequenceString("")}, + {"Player/aRCYellow", parseSequenceString("")}, + {"Player/aSCGreen", parseSequenceString("")}, + {"Player/aSCRed", parseSequenceString("")}, + {"Player/aSCYellow", parseSequenceString("")}, + + {"Player/aDecP", parseSequenceString("Ctrl+-")}, + {"Player/aDecPT", parseSequenceString("Ctrl+Alt+-")}, + {"Player/aDecT", parseSequenceString("Alt+-")}, + {"Player/aIncP", parseSequenceString("Ctrl++")}, + {"Player/aIncPT", parseSequenceString("Ctrl+Alt++")}, + {"Player/aIncT", parseSequenceString("Alt++")}, + {"Player/aSetPT", parseSequenceString("Ctrl+P")}, + {"Player/aResetPT", parseSequenceString("Ctrl+Alt+0")}, + + {"Player/aConcede", parseSequenceString("F2")}, + {"Player/aLeaveGame", parseSequenceString("Ctrl+Q")}, + {"Player/aNextPhase", parseSequenceString("Ctrl+Space;Tab")}, + {"Player/aNextPhaseAction", parseSequenceString("Shift+Tab")}, + {"Player/aNextTurn", parseSequenceString("Ctrl+Return;Ctrl+Enter")}, + {"Player/aRemoveLocalArrows", parseSequenceString("Ctrl+R")}, + {"Player/aRotateViewCCW", parseSequenceString("")}, + {"Player/aRotateViewCW", parseSequenceString("")}, + {"Player/phase0", parseSequenceString("F5")}, + {"Player/phase1", parseSequenceString("")}, + {"Player/phase10", parseSequenceString("F10")}, + {"Player/phase2", parseSequenceString("F6")}, + {"Player/phase3", parseSequenceString("F7")}, + {"Player/phase4", parseSequenceString("F8")}, + {"Player/phase5", parseSequenceString("")}, + {"Player/phase6", parseSequenceString("")}, + {"Player/phase7", parseSequenceString("")}, + {"Player/phase8", parseSequenceString("")}, + {"Player/phase9", parseSequenceString("F9")}, + + {"Player/aDecCounter_w", parseSequenceString("")}, + {"Player/aIncCounter_w", parseSequenceString("")}, + {"Player/aSetCounter_w", parseSequenceString("")}, + + {"Player/aDecCounter_u", parseSequenceString("")}, + {"Player/aIncCounter_u", parseSequenceString("")}, + {"Player/aSetCounter_u", parseSequenceString("")}, + + {"Player/aDecCounter_b", parseSequenceString("")}, + {"Player/aIncCounter_b", parseSequenceString("")}, + {"Player/aSetCounter_b", parseSequenceString("")}, + + {"Player/aDecCounter_r", parseSequenceString("")}, + {"Player/aIncCounter_r", parseSequenceString("")}, + {"Player/aSetCounter_r", parseSequenceString("")}, + + {"Player/aDecCounter_g", parseSequenceString("")}, + {"Player/aIncCounter_g", parseSequenceString("")}, + {"Player/aSetCounter_g", parseSequenceString("")}, + + {"Player/aDecCounter_x", parseSequenceString("")}, + {"Player/aIncCounter_x", parseSequenceString("")}, + {"Player/aSetCounter_x", parseSequenceString("")}, + + {"Player/aDecCounter_storm", parseSequenceString("")}, + {"Player/aIncCounter_storm", parseSequenceString("")}, + {"Player/aSetCounter_storm", parseSequenceString("")}, + + {"tab_room/aClearChat", parseSequenceString("F12")}, + {"DlgLoadDeckFromClipboard/refreshButton", parseSequenceString("F5")}}; }; #endif // SHORTCUTSSETTINGS_H diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp index d44f8bab6..79556bdab 100644 --- a/cockatrice/src/tab_deck_editor.cpp +++ b/cockatrice/src/tab_deck_editor.cpp @@ -590,7 +590,7 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent) this->installEventFilter(this); retranslateUi(); - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); QTimer::singleShot(0, this, SLOT(loadLayout())); diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index 33be857f3..b659d6670 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -95,7 +95,7 @@ void ToggleButton::setState(bool _state) } DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) - : QWidget(0), parentGame(parent), playerId(_playerId) + : QWidget(nullptr), parentGame(parent), playerId(_playerId) { loadLocalButton = new QPushButton; loadRemoteButton = new QPushButton; @@ -112,7 +112,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked())); connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText())); - QHBoxLayout *buttonHBox = new QHBoxLayout; + auto *buttonHBox = new QHBoxLayout; buttonHBox->addWidget(loadLocalButton); buttonHBox->addWidget(loadRemoteButton); buttonHBox->addWidget(readyStartButton); @@ -123,14 +123,14 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged())); - QVBoxLayout *deckViewLayout = new QVBoxLayout; + auto *deckViewLayout = new QVBoxLayout; deckViewLayout->addLayout(buttonHBox); deckViewLayout->addWidget(deckView); deckViewLayout->setContentsMargins(0, 0, 0, 0); setLayout(deckViewLayout); retranslateUi(); - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); } @@ -209,6 +209,9 @@ void TabGame::refreshShortcuts() if (aNextPhase) { aNextPhase->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextPhase")); } + if (aNextPhaseAction) { + aNextPhaseAction->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextPhaseAction")); + } if (aNextTurn) { aNextTurn->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextTurn")); } @@ -299,8 +302,8 @@ void DeckViewContainer::sideboardPlanChanged() { Command_SetSideboardPlan cmd; const QList &newPlan = deckView->getSideboardPlan(); - for (int i = 0; i < newPlan.size(); ++i) - cmd.add_move_list()->CopyFrom(newPlan.at(i)); + for (const auto &i : newPlan) + cmd.add_move_list()->CopyFrom(i); parentGame->sendGameCommand(cmd, playerId); } @@ -330,7 +333,8 @@ void DeckViewContainer::setDeck(const DeckLoader &deck) TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) : Tab(_tabSupervisor), secondsElapsed(0), hostId(-1), localPlayerId(-1), isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(true), gameStateKnown(false), resuming(false), - currentPhase(-1), activeCard(0), gameClosed(false), replay(_replay), currentReplayStep(0), sayLabel(0), sayEdit(0) + currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(_replay), currentReplayStep(0), + sayLabel(nullptr), sayEdit(nullptr) { // THIS CTOR IS USED ON REPLAY gameInfo.CopyFrom(replay->game_info()); @@ -375,7 +379,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) createReplayMenuItems(); createViewMenuItems(); retranslateUi(); - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); messageLog->logReplayStarted(gameInfo.game_id()); @@ -389,8 +393,8 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, const QMap &_roomGameTypes) : Tab(_tabSupervisor), clients(_clients), gameInfo(event.game_info()), roomGameTypes(_roomGameTypes), hostId(event.host_id()), localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()), - spectator(event.spectator()), gameStateKnown(false), resuming(event.resuming()), currentPhase(-1), activeCard(0), - gameClosed(false), replay(0), replayDock(0) + spectator(event.spectator()), gameStateKnown(false), resuming(event.resuming()), currentPhase(-1), + activeCard(nullptr), gameClosed(false), replay(nullptr), replayDock(nullptr) { // THIS CTOR IS USED ON GAMES gameInfo.set_started(false); @@ -414,7 +418,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, createMenuItems(); createViewMenuItems(); retranslateUi(); - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); // append game to rooms game list for others to see @@ -486,6 +490,9 @@ void TabGame::retranslateUi() if (aNextPhase) { aNextPhase->setText(tr("Next &phase")); } + if (aNextPhaseAction) { + aNextPhaseAction->setText(tr("Next phase with &action")); + } if (aNextTurn) { aNextTurn->setText(tr("Next &turn")); } @@ -554,7 +561,7 @@ void TabGame::closeRequest() void TabGame::replayNextEvent() { - processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), 0); + processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), nullptr); } void TabGame::replayFinished() @@ -620,11 +627,23 @@ void TabGame::actGameInfo() void TabGame::actConcede() { - if (QMessageBox::question(this, tr("Concede"), tr("Are you sure you want to concede this game?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + Player *player = players.value(localPlayerId, nullptr); + if (player == nullptr) return; + if (!player->getConceded()) { + if (QMessageBox::question(this, tr("Concede"), tr("Are you sure you want to concede this game?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + return; - sendGameCommand(Command_Concede()); + sendGameCommand(Command_Concede()); + } else { + if (QMessageBox::question(this, tr("Unconcede"), + tr("You have already conceded. Do you want to return to this game?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + return; + + sendGameCommand(Command_Unconcede()); + } } void TabGame::actLeaveGame() @@ -659,7 +678,7 @@ void TabGame::actPhaseAction() { int phase = phaseActions.indexOf(static_cast(sender())); Command_SetActivePhase cmd; - cmd.set_phase(phase); + cmd.set_phase(static_cast(phase)); sendGameCommand(cmd); } @@ -669,10 +688,29 @@ void TabGame::actNextPhase() if (++phase >= phasesToolbar->phaseCount()) phase = 0; Command_SetActivePhase cmd; - cmd.set_phase(phase); + cmd.set_phase(static_cast(phase)); sendGameCommand(cmd); } +void TabGame::actNextPhaseAction() +{ + int phase = currentPhase + 1; + if (phase >= phasesToolbar->phaseCount()) { + phase = 0; + } + + if (phase == 0) { + Command_NextTurn cmd; + sendGameCommand(cmd); + } else { + Command_SetActivePhase cmd; + cmd.set_phase(static_cast(phase)); + sendGameCommand(cmd); + } + + phasesToolbar->triggerPhaseAction(phase); +} + void TabGame::actNextTurn() { sendGameCommand(Command_NextTurn()); @@ -713,7 +751,7 @@ void TabGame::actCompleterChanged() Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) { bool local = ((clients.size() > 1) || (playerId == localPlayerId)); - Player *newPlayer = new Player(info, playerId, local, this); + auto *newPlayer = new Player(info, playerId, local, this); connect(newPlayer, SIGNAL(openDeckEditor(const DeckLoader *)), this, SIGNAL(openDeckEditor(const DeckLoader *))); QString newPlayerName = "@" + newPlayer->getName(); if (sayEdit && !autocompleteUserList.contains(newPlayerName)) { @@ -729,7 +767,7 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) if (clients.size() == 1) newPlayer->setShortcutsActive(); - DeckViewContainer *deckView = new DeckViewContainer(playerId, this); + auto *deckView = new DeckViewContainer(playerId, this); connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); deckViewContainers.insert(playerId, deckView); deckViewContainerLayout->addWidget(deckView); @@ -750,7 +788,7 @@ void TabGame::processGameEventContainer(const GameEventContainer &cont, Abstract for (int i = 0; i < eventListSize; ++i) { const GameEvent &event = cont.event_list(i); const int playerId = event.player_id(); - const GameEvent::GameEventType eventType = static_cast(getPbExtension(event)); + const auto eventType = static_cast(getPbExtension(event)); if (spectators.contains(playerId)) { switch (eventType) { case GameEvent::GAME_SAY: @@ -822,7 +860,7 @@ AbstractClient *TabGame::getClientForPlayer(int playerId) const return clients.at(playerId); } else if (clients.isEmpty()) - return 0; + return nullptr; else return clients.first(); } @@ -859,7 +897,7 @@ void TabGame::commandFinished(const Response &response) PendingCommand *TabGame::prepareGameCommand(const ::google::protobuf::Message &cmd) { CommandContainer cont; - cont.set_game_id(gameInfo.game_id()); + cont.set_game_id(static_cast(gameInfo.game_id())); GameCommand *c = cont.add_game_command(); c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); return new PendingCommand(cont); @@ -868,13 +906,11 @@ PendingCommand *TabGame::prepareGameCommand(const ::google::protobuf::Message &c PendingCommand *TabGame::prepareGameCommand(const QList &cmdList) { CommandContainer cont; - cont.set_game_id(gameInfo.game_id()); - for (int i = 0; i < cmdList.size(); ++i) { + cont.set_game_id(static_cast(gameInfo.game_id())); + for (auto i : cmdList) { GameCommand *c = cont.add_game_command(); - c->GetReflection() - ->MutableMessage(c, cmdList[i]->GetDescriptor()->FindExtensionByName("ext")) - ->CopyFrom(*cmdList[i]); - delete cmdList[i]; + c->GetReflection()->MutableMessage(c, i->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*i); + delete i; } return new PendingCommand(cont); } @@ -1028,8 +1064,7 @@ void TabGame::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged & const ServerInfo_PlayerProperties &prop = event.player_properties(); playerListWidget->updatePlayerProperties(prop, eventPlayerId); - const GameEventContext::ContextType contextType = - static_cast(getPbExtension(context)); + const auto contextType = static_cast(getPbExtension(context)); switch (contextType) { case GameEventContext::READY_START: { bool ready = prop.ready_start(); @@ -1051,6 +1086,16 @@ void TabGame::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged & break; } + case GameEventContext::UNCONCEDE: { + messageLog->logUnconcede(player); + player->setConceded(false); + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + playerIterator.next().value()->updateZones(); + + break; + } case GameEventContext::DECK_SELECT: { Context_DeckSelect deckSelect = context.GetExtension(Context_DeckSelect::ext); messageLog->logDeckSelect(player, QString::fromStdString(deckSelect.deck_hash()), @@ -1173,7 +1218,7 @@ Player *TabGame::setActivePlayer(int id) { Player *player = players.value(id, 0); if (!player) - return 0; + return nullptr; activePlayer = id; playerListWidget->setActivePlayer(id); QMapIterator i(players); @@ -1237,11 +1282,11 @@ CardItem *TabGame::getCard(int playerId, const QString &zoneName, int cardId) co { Player *player = players.value(playerId, 0); if (!player) - return 0; + return nullptr; CardZone *zone = player->getZones().value(zoneName, 0); if (!zone) - return 0; + return nullptr; return zone->getCard(cardId, QString()); } @@ -1249,7 +1294,7 @@ CardItem *TabGame::getCard(int playerId, const QString &zoneName, int cardId) co QString TabGame::getTabText() const { QString gameTypeInfo; - if (gameTypes.size() != 0) { + if (!gameTypes.empty()) { gameTypeInfo = gameTypes.at(0); if (gameTypes.size() > 1) gameTypeInfo.append("..."); @@ -1290,7 +1335,7 @@ Player *TabGame::getActiveLocalPlayer() const return temp; } - return 0; + return nullptr; } void TabGame::updateCardMenu(AbstractCardItem *card) @@ -1307,6 +1352,8 @@ void TabGame::createMenuItems() { aNextPhase = new QAction(this); connect(aNextPhase, SIGNAL(triggered()), this, SLOT(actNextPhase())); + aNextPhaseAction = new QAction(this); + connect(aNextPhaseAction, SIGNAL(triggered()), this, SLOT(actNextPhaseAction())); aNextTurn = new QAction(this); connect(aNextTurn, SIGNAL(triggered()), this, SLOT(actNextTurn())); aRemoveLocalArrows = new QAction(this); @@ -1321,7 +1368,7 @@ void TabGame::createMenuItems() connect(aConcede, SIGNAL(triggered()), this, SLOT(actConcede())); aLeaveGame = new QAction(this); connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame())); - aCloseReplay = 0; + aCloseReplay = nullptr; phasesMenu = new QMenu(this); for (int i = 0; i < phasesToolbar->phaseCount(); ++i) { @@ -1333,6 +1380,7 @@ void TabGame::createMenuItems() phasesMenu->addSeparator(); phasesMenu->addAction(aNextPhase); + phasesMenu->addAction(aNextPhaseAction); gameMenu = new QMenu(this); playersSeparator = gameMenu->addSeparator(); @@ -1351,19 +1399,20 @@ void TabGame::createMenuItems() void TabGame::createReplayMenuItems() { - aNextPhase = 0; - aNextTurn = 0; - aRemoveLocalArrows = 0; - aRotateViewCW = 0; - aRotateViewCCW = 0; - aResetLayout = 0; - aGameInfo = 0; - aConcede = 0; - aLeaveGame = 0; + aNextPhase = nullptr; + aNextPhaseAction = nullptr; + aNextTurn = nullptr; + aRemoveLocalArrows = nullptr; + aRotateViewCW = nullptr; + aRotateViewCCW = nullptr; + aResetLayout = nullptr; + aGameInfo = nullptr; + aConcede = nullptr; + aLeaveGame = nullptr; aCloseReplay = new QAction(this); connect(aCloseReplay, SIGNAL(triggered()), this, SLOT(actLeaveGame())); - phasesMenu = 0; + phasesMenu = nullptr; gameMenu = new QMenu(this); gameMenu->addAction(aCloseReplay); addTabMenu(gameMenu); @@ -1637,7 +1686,7 @@ void TabGame::createCardInfoDock(bool bReplay) void TabGame::createPlayerListDock(bool bReplay) { if (bReplay) { - playerListWidget = new PlayerListWidget(0, 0, this); + playerListWidget = new PlayerListWidget(nullptr, nullptr, this); } else { playerListWidget = new PlayerListWidget(tabSupervisor, clients.first(), this); connect(playerListWidget, SIGNAL(openMessageDialog(QString, bool)), this, diff --git a/cockatrice/src/tab_game.h b/cockatrice/src/tab_game.h index 72d3331d9..41f56ddcc 100644 --- a/cockatrice/src/tab_game.h +++ b/cockatrice/src/tab_game.h @@ -163,8 +163,8 @@ private: QAction *playersSeparator; QMenu *gameMenu, *phasesMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu, *replayDockMenu; - QAction *aGameInfo, *aConcede, *aLeaveGame, *aCloseReplay, *aNextPhase, *aNextTurn, *aRemoveLocalArrows, - *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout; + QAction *aGameInfo, *aConcede, *aLeaveGame, *aCloseReplay, *aNextPhase, *aNextPhaseAction, *aNextTurn, + *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout; QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aMessageLayoutDockVisible, *aMessageLayoutDockFloating, *aPlayerListDockVisible, *aPlayerListDockFloating, *aReplayDockVisible, *aReplayDockFloating; QList phaseActions; @@ -233,6 +233,7 @@ private slots: void actSay(); void actPhaseAction(); void actNextPhase(); + void actNextPhaseAction(); void actNextTurn(); void addMentionTag(QString value); @@ -246,7 +247,7 @@ private slots: void actResetLayout(); void freeDocksSize(); - bool eventFilter(QObject *o, QEvent *e); + bool eventFilter(QObject *o, QEvent *e) override; void dockVisibleTriggered(); void dockFloatingTriggered(); void dockTopLevelChanged(bool topLevel); @@ -257,10 +258,10 @@ public: const Event_GameJoined &event, const QMap &_roomGameTypes); TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay); - ~TabGame(); - void retranslateUi(); + ~TabGame() override; + void retranslateUi() override; void updatePlayerListDockTitle(); - void closeRequest(); + void closeRequest() override; const QMap &getPlayers() const { return players; @@ -278,7 +279,7 @@ public: { return gameInfo.game_id(); } - QString getTabText() const; + QString getTabText() const override; bool getSpectator() const { return spectator; diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 603c44723..326e0dda1 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -299,16 +299,17 @@ void MainWindow::actAbout() QMessageBox::NoIcon, tr("About Cockatrice"), QString("Cockatrice (" + QString::fromStdString(BUILD_ARCHITECTURE) + ")
" + tr("Version") + QString(" %1").arg(VERSION_STRING) + "

" + - tr("Cockatrice Webpage") + "
" + "

" + tr("Project Manager:") + - "
Gavin Bisesi

" + "" + tr("Past Project Managers:") + - "
Max-Wilhelm Bruker
Marcus Schütz

" + "" + tr("Developers:") + "
" + - "" + tr("Our Developers") + "
" + "" + tr("Help Develop!") + "

" + "" + tr("Translators:") + - "
" + "" + tr("Our Translators") + - "
" + "" + tr("Help Translate!") + "

" + - "" + tr("Support:") + "
" + "" + tr("Report an Issue") + - "
" + "" + tr("Troubleshooting") + "
" + - "" + tr("F.A.Q.") + "
"), + tr("Cockatrice Webpage") + "
" + "
" + tr("Project Manager:") + + "
Zach Halpern

" + "" + tr("Past Project Managers:") + + "
Gavin Bisesi
Max-Wilhelm Bruker
Marcus Schütz

" + "" + tr("Developers:") + + "
" + "" + tr("Our Developers") + "
" + + "" + tr("Help Develop!") + "

" + "" + + tr("Translators:") + "
" + "" + + tr("Our Translators") + "
" + "" + + tr("Help Translate!") + "

" + "" + tr("Support:") + "
" + "" + tr("Report an Issue") + "
" + "" + tr("Troubleshooting") + "
" + "" + tr("F.A.Q.") + "
"), QMessageBox::Ok, this); mb.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64)); mb.setTextInteractionFlags(Qt::TextBrowserInteraction); @@ -317,9 +318,9 @@ void MainWindow::actAbout() void MainWindow::actTips() { - if (tip != NULL) { + if (tip != nullptr) { delete tip; - tip = NULL; + tip = nullptr; } tip = new DlgTipOfTheDay(); if (tip->successfulInit) { @@ -705,15 +706,33 @@ void MainWindow::createActions() aExit->setMenuRole(QAction::QuitRole); aAbout->setMenuRole(QAction::AboutRole); - char const *foo; // avoid "warning: expression result unused" under clang - foo = QT_TRANSLATE_NOOP("QMenuBar", "Services"); - foo = QT_TRANSLATE_NOOP("QMenuBar", "Hide %1"); - foo = QT_TRANSLATE_NOOP("QMenuBar", "Hide Others"); - foo = QT_TRANSLATE_NOOP("QMenuBar", "Show All"); - foo = QT_TRANSLATE_NOOP("QMenuBar", "Preferences..."); - foo = QT_TRANSLATE_NOOP("QMenuBar", "Quit %1"); - foo = QT_TRANSLATE_NOOP("QMenuBar", "About %1"); + Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Services")); + Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Hide %1")); + Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Hide Others")); + Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Show All")); + Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Preferences...")); + Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Quit %1")); + Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "About %1")); #endif + // translate Qt's dialogs "default button text"; list taken from QPlatformTheme::defaultStandardButtonText() + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "OK")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Save")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Save All")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Open")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "&Yes")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Yes to &All")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "&No")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "N&o to All")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Abort")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Retry")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Ignore")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Close")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Cancel")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Discard")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Help")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Apply")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Reset")); + Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Restore Defaults")); } void MainWindow::createMenus() @@ -811,7 +830,7 @@ MainWindow::MainWindow(QWidget *parent) createTrayIcon(); } - connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); + connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); connect(db, SIGNAL(cardDatabaseLoadingFailed()), this, SLOT(cardDatabaseLoadingFailed())); @@ -828,13 +847,30 @@ MainWindow::MainWindow(QWidget *parent) if (tip->successfulInit && settingsCache->getShowTipsOnStartup() && tip->newTipsAvailable) { tip->show(); } + + // Only run the check updater if the user wants it (defaults to on) + if (settingsCache->getNotifyAboutNewVersion()) { + auto versionUpdater = new MainUpdateHelper(); + connect(versionUpdater, SIGNAL(newVersionDetected(QString)), this, SLOT(alertForcedOracleRun(QString))); + QtConcurrent::run(versionUpdater, &MainUpdateHelper::testForNewVersion); + } +} + +void MainWindow::alertForcedOracleRun(const QString &newVersion) +{ + settingsCache->setClientVersion(newVersion); + QMessageBox::information(this, tr("New Version"), + tr("Congratulations on updating to Cockatrice %1!\n" + "Oracle will now launch to update your card database.") + .arg(newVersion)); + actCheckCardUpdates(); } MainWindow::~MainWindow() { - if (tip != NULL) { + if (tip != nullptr) { delete tip; - tip = NULL; + tip = nullptr; } if (trayIcon) { trayIcon->hide(); @@ -1271,3 +1307,10 @@ void MainWindow::promptForgotPasswordReset() dlg.getPlayerName(), dlg.getToken(), dlg.getPassword()); } } + +void MainUpdateHelper::testForNewVersion() +{ + if (settingsCache->getClientVersion() != VERSION_STRING) { + emit newVersionDetected(VERSION_STRING); + } +} diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index d6c7238b7..68067924f 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -101,6 +101,8 @@ private slots: void actManageSets(); void actEditTokens(); + void alertForcedOracleRun(const QString &); + private: static const QString appName; static const QStringList fileNameFilters; @@ -146,4 +148,17 @@ protected: QString extractInvalidUsernameMessage(QString &in); }; +class MainUpdateHelper : public QObject +{ + Q_OBJECT + +signals: + void newVersionDetected(QString); + +public: + explicit MainUpdateHelper() = default; + ~MainUpdateHelper() override = default; + void testForNewVersion(); +}; + #endif diff --git a/cockatrice/src/window_sets.cpp b/cockatrice/src/window_sets.cpp index 374b79b09..95ac6ba4f 100644 --- a/cockatrice/src/window_sets.cpp +++ b/cockatrice/src/window_sets.cpp @@ -127,21 +127,32 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) labNotes->setOpenExternalLinks(true); labNotes->setText( "" + tr("Deck Editor") + ": " + - tr("Only cards in enabled sets will appear in the deck editor card list") + "
" + tr("Card Art") + - ": " + tr("Image priority is decided in the following order") + "
  1. " + tr("The") + - " " + tr("CUSTOM Folder") + "
  2. " + tr("Enabled Sets (Top to Bottom)") + "
  3. " + tr("Disabled Sets (Top to Bottom)") + "
"); - sortWarning = new QLabel; - sortWarning->setWordWrap(true); - sortWarning->setText( - "" + tr("Warning: ") + "
" + - tr("While the set list is sorted by any of the columns, custom art priority setting is disabled.") + "
" + - tr("To disable sorting click on the same column header again until this message disappears.")); - sortWarning->setStyleSheet("QLabel { background-color:red;}"); + QGridLayout *hintsGrid = new QGridLayout; + hintsGrid->addWidget(labNotes, 0, 0); + hintsGroupBox = new QGroupBox(tr("Hints")); + hintsGroupBox->setLayout(hintsGrid); + + sortWarning = new QGroupBox(tr("Note")); + QGridLayout *sortWarningLayout = new QGridLayout; + sortWarningText = new QLabel; + sortWarningText->setWordWrap(true); + sortWarningText->setText(tr("Sorting by column allows you to find a set while not changing set priority.") + " " + + tr("To enable ordering again, click the column header until this message disappears.")); + sortWarningLayout->addWidget(sortWarningText, 0, 0, 1, 2); + sortWarningButton = new QPushButton; + sortWarningButton->setText(tr("Use the current sorting as the set priority instead")); + sortWarningButton->setToolTip(tr("Sorts the set priority using the same column")); + sortWarningButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(sortWarningButton, SIGNAL(released()), this, SLOT(actIgnoreWarning())); + sortWarningLayout->addWidget(sortWarningButton, 1, 0); + sortWarning->setLayout(sortWarningLayout); sortWarning->setVisible(false); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); @@ -157,7 +168,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) mainLayout->addWidget(enableSomeButton, 2, 1); mainLayout->addWidget(disableSomeButton, 2, 2); mainLayout->addWidget(sortWarning, 3, 1, 1, 2); - mainLayout->addWidget(labNotes, 4, 1, 1, 2); + mainLayout->addWidget(hintsGroupBox, 4, 1, 1, 2); mainLayout->addWidget(buttonBox, 5, 1, 1, 2); mainLayout->setColumnStretch(1, 1); mainLayout->setColumnStretch(2, 1); @@ -250,6 +261,17 @@ void WndSets::actSort(int index) } } +void WndSets::actIgnoreWarning() +{ + if (sortIndex < 0) { + return; + } + model->sort(sortIndex, sortOrder); + view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder); + sortIndex = -1; + sortWarning->setVisible(false); +} + void WndSets::actDisableSortButtons(int index) { if (index != SORT_RESET) { diff --git a/cockatrice/src/window_sets.h b/cockatrice/src/window_sets.h index 86632d7d0..1013bc43b 100644 --- a/cockatrice/src/window_sets.h +++ b/cockatrice/src/window_sets.h @@ -8,13 +8,14 @@ #include #include +class CardDatabase; +class QGroupBox; +class QItemSelection; +class QPushButton; +class QTreeView; +class SetsDisplayModel; class SetsModel; class SetsProxyModel; -class SetsDisplayModel; -class QPushButton; -class CardDatabase; -class QItemSelection; -class QTreeView; class WndSets : public QMainWindow { @@ -22,6 +23,7 @@ class WndSets : public QMainWindow private: SetsModel *model; SetsDisplayModel *displayModel; + QGroupBox *hintsGroupBox; QTreeView *view; QPushButton *toggleAllButton, *toggleSelectedButton; QPushButton *enableAllButton, *disableAllButton, *enableSomeButton, *disableSomeButton; @@ -29,7 +31,10 @@ private: QAction *aUp, *aDown, *aBottom, *aTop; QToolBar *setsEditToolBar; QDialogButtonBox *buttonBox; - QLabel *labNotes, *searchLabel, *sortWarning; + QLabel *labNotes, *searchLabel; + QGroupBox *sortWarning; + QLabel *sortWarningText; + QPushButton *sortWarningButton; QLineEdit *searchField; QGridLayout *mainLayout; QHBoxLayout *filterBox; @@ -65,6 +70,7 @@ private slots: void actRestoreOriginalOrder(); void actDisableResetButton(const QString &filterText); void actSort(int index); + void actIgnoreWarning(); }; #endif diff --git a/cockatrice/translations/cockatrice_en.ts b/cockatrice/translations/cockatrice_en.ts index be13c84bb..3c35f2d44 100644 --- a/cockatrice/translations/cockatrice_en.ts +++ b/cockatrice/translations/cockatrice_en.ts @@ -22,62 +22,62 @@ AppearanceSettingsPage - + Theme settings - + Current theme: - + Card rendering - + Display card names on cards having a picture - + Scale cards on mouse over - + Hand layout - + Display hand horizontally (wastes space) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -192,22 +192,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta Releases - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -215,50 +215,132 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name - + Sets - + Mana cost - + Card type - + P/T - + Color(s) + + CardFilter + + + AND + Logical conjunction operator used in card filter + + + + + OR + Logical disjunction operator used in card filter + + + + + AND NOT + Negated logical conjunction operator used in card filter + + + + + OR NOT + Negated logical disjunction operator used in card filter + + + + + Name + + + + + Type + + + + + Color + + + + + Text + + + + + Set + + + + + Mana Cost + + + + + CMC + + + + + Rarity + + + + + Power + + + + + Toughness + + + + + Loyalty + + + CardFrame - + Image - + Description - + Both @@ -266,40 +348,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoText - + + Related cards: + + + + Unknown card: - + Name: - - - Mana cost: - - - - - Color(s): - - - - - Card type: - - - - - P / T: - - - - - Loyalty: - - CardItem @@ -440,58 +502,120 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - Updating... + + + Success - - Choose path + + Download URLs have been reset. - - Spoilers + + Downloaded card pictures have been reset. - - Download Spoilers Automatically + + Error + + + + + One or more downloaded card pictures could not be cleared. + + + + + Add URL + + + + + + URL: + + + + + Edit URL + Updating... + + + + + Choose path + + + + + URL Download Priority + + + + + Spoilers + + + + + Download Spoilers Automatically + + + + Spoiler Location: - - Hey, something's here finally! + + Download card pictures on the fly - + + How to add a custom URL + + + + + Delete Downloaded Images + + + + + Reset Download URLs + + + + Last Updated - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update complete @@ -664,6 +788,21 @@ This is only saved for moderators and cannot be seen by the banned person.Login + + + Server URL + + + + + Communication Port + + + + + Unique Server Name + + Connection Warning @@ -708,77 +847,82 @@ This is only saved for moderators and cannot be seen by the banned person. - + + General + + + + Game type - + &Password: - + Only &buddies can join - + Only &registered users can join - + Joining restrictions - + &Spectators can watch - + Spectators &need a password to watch - + Spectators can &chat - + Spectators can see &hands - + Spectators - + &Clear - + Create game - + Game information - + Error - + Server error. @@ -1095,37 +1239,42 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - + &Creator name: - + + General + + + + &Game types - + at &least: - + at &most: - + Maximum player count - + Restrictions - + Filter games @@ -1391,12 +1540,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1407,7 +1556,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -1418,7 +1567,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1427,21 +1576,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues @@ -1450,59 +1599,59 @@ Would you like to change your database location setting? - - - + + + Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Appearance - + User Interface - - Deck Editor + + Card Sources - + Chat - + Sound - + Shortcuts @@ -1529,9 +1678,9 @@ Would you like to change your database location setting? DlgUpdate - - - + + + Error @@ -1567,98 +1716,98 @@ Please visit the download page to update manually. - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - - - Update Available - - - - - - A new version of Cockatrice is available! - - - - - - New version - - - Released + Update Available - Changelog + A new version of Cockatrice is available! + + + + + + New version + + Released + + + + + + Changelog + + + + Do you want to update now? - + Unfortunately there are no download packages available for your operating system. You may have to build from source yourself. - + Please check the download page manually and visit the wiki for instructions on compiling. - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -1673,14 +1822,14 @@ You may have to build from source yourself. - - - + + + Update Error - + Installing... @@ -1894,127 +2043,81 @@ You may have to build from source yourself. GeneralSettingsPage - - - - - + + + + + Choose path - - Success - - - - - Downloaded card pictures have been reset. - - - - - Error - - - - - One or more downloaded card pictures could not be cleared. - - - - + Personal settings - + Language: - - Download card pictures on the fly - - - - + Paths (editing disabled in portable mode) - + Paths - + Decks directory: - + Replays directory: - + Pictures directory: - + Card database: - + Token database: - + Picture cache size: - - Primary download URL: - - - - - Fallback download URL: - - - - - How to set a custom picture url - - - - - Reset/clear downloaded pictures - - - - + Update channel - + Notify if a feature supported by the server is missing in my client - - - Reset + + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2023,7 +2126,7 @@ You may have to build from source yourself. MainWindow - + The server has reached its maximum user capacity, please check back later. @@ -2054,7 +2157,7 @@ You may have to build from source yourself. - + Invalid username. @@ -2164,12 +2267,12 @@ Will now login. - + Translators: - + Help Translate! @@ -2179,199 +2282,199 @@ Will now login. - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - + + + + + + - - - - - - - - - - - + + + + + + + + + + + + Error - + Server timeout - + Failed Login - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client ID's. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please try closing and reopening your client and try again. If the error persists try updating your client to the most recent build and if need be contact your software provider. - + Account activation - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - + + - + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + Your client seems to be missing features this server requires for connection. @@ -2381,360 +2484,376 @@ This usually means that your client version is out of date, and the server sent - + Our Translators - + + To update your client, go to 'Help -> Check for Client Updates'. + + + + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + The email address provider used during registration has been blacklisted for use on this server. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgot password to %1 as %2... - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - + &Deck editor - + &Full screen - + &Register to server... - + &Settings... - - + + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Edit &tokens... - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + View &debug log - + &Help - + Check for card updates... - + + New Version + + + + + Congratulations on updating to Cockatrice %1! +Oracle will now launch to update your card database. + + + + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %1 new set(s) found in the card database Set code(s): %2 Do you want to enable it/them? - + View sets - + Welcome - - - + + + Information - + A card database update is already running. - + Unable to run the card database updater: - + failed to start. - + crashed. - + timed out. - + write error. - + read error. - + unknown error. - + The card database updater exited with an error: %1 - + Update completed successfully. Cockatrice will now reload the card database. - + You can only import XML databases at this time. - - - + + + Forgot Password - + Your password has been reset successfully, you now may log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - - - - - + + + + + Load sets/cards - + &Manage sets... - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -2742,18 +2861,18 @@ To update your client, go to Help -> Check for Updates. - + Selected file cannot be found. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. @@ -2761,17 +2880,17 @@ Cockatrice will now reload the card database. MessageLogWidget - + The game has been closed. - + You have been kicked out of the game. - + %1 is now watching the game. @@ -2786,7 +2905,7 @@ Cockatrice will now reload the card database. - + The game has started. @@ -2835,9 +2954,13 @@ Cockatrice will now reload the card database. from the stack + + + %1 attaches %2 to %3's %4. + + - a card @@ -2876,115 +2999,109 @@ Cockatrice will now reload the card database. %1 plays %2%3. + + + %1 is looking at the top %3 card(s) %2. + top card for singular, top %3 cards for plural + + + + + - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). + + + %1 card(s) + a card for singular, %1 cards for plural + + + + + - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup - + Unknown Phase - + %1's turn. - - - red - - - - - - - - yellow - - - - - - - - green - - - - - %1 is now keeping the top card %2 revealed. @@ -2996,32 +3113,32 @@ Cockatrice will now reload the card database. - + You are watching a replay of game #%1. - + %1 has joined the game. - + %1 is ready to start the game. - + %1 is not ready to start the game any more. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. @@ -3041,42 +3158,79 @@ Cockatrice will now reload the card database. - + + %1 places %2 %3 on %4 (now %5). + + + + + %1 removes %2 %3 from %4 (now %5). + + + + + red counter(s) + + + + + + + + yellow counter(s) + + + + + + + + green counter(s) + + + + + + + %1 shuffles %2. - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 draws %2 card(s). - + + + + - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3141,12 +3295,12 @@ Cockatrice will now reload the card database. - + %1 takes a mulligan to %2. - + %1 draws their initial hand. @@ -3156,7 +3310,7 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. @@ -3206,57 +3360,47 @@ Cockatrice will now reload the card database. - - %1 places %2 %3 counter(s) on %4 (now %5). - - - - - %1 removes %2 %3 counter(s) from %4 (now %5). - - - - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 sets PT of %2 to %3. - + %1 sets annotation of %2 to %3. @@ -3266,52 +3410,47 @@ Cockatrice will now reload the card database. - - %1 is looking at the top %2 card(s) %3. - - - - + %1 stops looking at %2. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. @@ -3319,83 +3458,157 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + + Word1 Word2 Word3 + + + + + Add New URL + + + + + Edit URL + + + + + Remove URL + + + + Add message - + + Message: - + + Edit message + + + + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only + + Mtg + + + Card type + + + + + Converted mana cost + + + + + Color(s) + + + + + Loyalty + + + + + Main card type + + + + + Mana cost + + + + + P / T + + + + + Side + + + + + Layout + + + PhasesToolbar @@ -3457,485 +3670,494 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - + Number of cards: (max. %1) - + &View graveyard - + &View exile - + Player "%1" - - - - + + + + &Graveyard - - - - + + + + &Exile - + &Move hand to... - - - - + + + + &Top of library - - - - + + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand - + &Move exile to... - + &View library - + View &top cards of library... - + Reveal &library to... - + Reveal t&op cards to... - + &Always reveal top card - + O&pen deck in deck editor - + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - + &Shuffle - + Play top card &face down - - - Move top cards to &graveyard... - - - - - Move top cards to &exile... - - - - - Put top card on &bottom - - - - - Put bottom card &in graveyard - - - - - &Reveal hand to... - - - - - Reveal r&andom card to... - - - - - Reveal random card to... - - - - - &Sideboard - - - - - &Library - - - - - &Counters - - - - - &Untap all permanents - - - R&oll die... + Move top card to grave&yard - &Create token... + Move top card to e&xile - C&reate another token + Move top cards to &graveyard... - Cr&eate predefined token + Move top cards to &exile... - S&ay + Put top card on &bottom + + + + + Put bottom card &in graveyard + + + + + &Reveal hand to... + + + + + Reveal r&andom card to... - C&ard + Reveal random card to... + + + + + &Sideboard + + + + + &Library - &All players + &Counters + + + + + &Untap all permanents - &Play + R&oll die... - &Hide + &Create token... - Play &Face Down + C&reate another token - - Toggle &normal untapping - - - - - &Peek at card face - - - - - &Clone - - - - - Attac&h to card... - - - - - Unattac&h - - - - - &Draw arrow... - - - - - &Increase power - - - - - &Decrease power - - - - - I&ncrease toughness - - - - - D&ecrease toughness - - - - - In&crease power and toughness - - - - - Dec&rease power and toughness - - - - - Set &power and toughness... - - - - - &Set annotation... - - - - - Red - - - - - Yellow - - - - - Green - - - - - X cards from the top of library... - - - - - - C&reate another %1 token - - - - - Create tokens - - - - - - - - - - Token: - - - - - Place card X cards from top of library - - - - - How many cards from the top of the deck should this card be placed: - - - - - View related cards - - - - - - Attach to - - - - - All tokens - - - - - View top cards of library + + Cr&eate predefined token + S&ay + + + + + C&ard + + + + + &All players + + + + + &Play + + + + + &Hide + + + + + Play &Face Down + + + + + Toggle &normal untapping + + + + + &Peek at card face + + + + + &Clone + + + + + Attac&h to card... + + + + + Unattac&h + + + + + &Draw arrow... + + + + + &Increase power + + + + + &Decrease power + + + + + I&ncrease toughness + + + + + D&ecrease toughness + + + + + In&crease power and toughness + + + + + Dec&rease power and toughness + + + + + Set &power and toughness... + + + + + Reset p&ower and toughness + + + + + &Set annotation... + + + + + Red + + + + + Yellow + + + + + Green + + + + + X cards from the top of library... + + + + + + C&reate another %1 token + + + + + Create tokens + + + + + Token: + + + + + Place card X cards from top of library + + + + + How many cards from the top of the deck should this card be placed: + + + + + View related cards + + + + + Attach to + + + + + All tokens + + + + + View top cards of library + + + + &Tap / Untap Turn sideways or back again - + T&urn Over Turn face up/face down - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + Number of cards: - + Draw cards - - - - - + + + + + Number: - + Move top cards to grave - + Move top cards to exile - + Roll die - + Number of sides: - + Set power/toughness - + Please enter the new PT: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -3943,37 +4165,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4002,21 +4224,114 @@ Cockatrice will now reload the card database. - + Maindeck - + Sideboard - + Tokens + + QPlatformTheme + + + Cancel + + + + + Discard + + + + + Help + + + + + Apply + + + + + &Yes + + + + + Save + + + + + Save All + + + + + Open + + + + + Yes to &All + + + + + &No + + + + + N&o to All + + + + + Abort + + + + + Retry + + + + + Ignore + + + + + Close + + + + + Reset + + + + + Restore Defaults + + + + + OK + + + RemoteDeckList_TreeModel @@ -4109,12 +4424,12 @@ Cockatrice will now reload the card database. SequenceEdit - + Shortcut already in use - + Invalid key @@ -4150,13 +4465,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -4206,27 +4521,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -4283,27 +4598,27 @@ Please check your shortcut settings! StableReleaseChannel - + Stable Releases - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. @@ -4349,194 +4664,194 @@ Please check your shortcut settings! TabDeckEditor - + &Clear all filters - + Delete selected - + Deck &name: - + &Comments: - + Hash: - + &New deck - + &Load deck... - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + &Print deck... - + Search by card name - + Add to Deck - + Add to Sideboard - + Show Related cards - + Save deck to clipboard - + Annotated - + Not Annotated - + &Send deck to online service - + Create decklist (decklist.org) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close - + Add card to &maindeck - + Add card to &sideboard - + &Remove row - + &Increment number - + &Decrement number - + &Deck Editor + + + + Card Info + + + + + + Deck + + - Card Info - - - - - - Deck - - - - - Filters - + &View - - - + + + Visible - - - + + + Floating - + Reset layout - + Deck: %1 @@ -4552,43 +4867,43 @@ Do you want to save the changes? - + Load deck - - - - - + + + + + Error - + The deck could not be saved. - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + There are no cards in your deck to be exported - + No deck was selected to be saved. @@ -5642,42 +5957,42 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + &Double-click cards to play them (instead of single-click) - + &Play all nonlands onto the stack (not the battlefield) by default - + Annotate card text on tokens - + Animation settings - + &Tap/untap animation @@ -5747,127 +6062,142 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Only cards in enabled sets will appear in the deck editor card list - + Card Art - + Image priority is decided in the following order - + The - + CUSTOM Folder - + Enabled Sets (Top to Bottom) - + Disabled Sets (Top to Bottom) - Warning: + Hints - - While the set list is sorted by any of the columns, custom art priority setting is disabled. + + Note - - To disable sorting click on the same column header again until this message disappears. + + Sorting by column allows you to find a set while not changing set priority. - + + To enable ordering again, click the column header until this message disappears. + + + + + Use the current sorting as the set priority instead + + + + + Sorts the set priority using the same column + + + + Manage sets - + Success - + The sets database has been saved successfully. @@ -5906,635 +6236,674 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - + Deck editor - + Local gameplay - + Watch replay - + Connect - + Register - + Full screen - + Settings - + Check for card updates - + Disconnect - + Exit - + Deck Editor - + Analyze deck - + Load deck (clipboard) - + Clear all filters - + New deck - + Clear selected filter - + Open custom pic folder - + Close - + Print deck - + Delete card - + Edit tokens - + Reset layout - + Add card - + Save deck - + Remove card - + Save deck as - + Load deck - - + + Counters - + Life - - - - - - - - - - - - - + + + + + + + + + + + + + Set - - - - - - - - - - - - + + + + + + + + + + + + Add - - - - - - - - - - - - + + + + + + + + + + + + Remove - + Red - + Green - + Yellow - + Storm - + W - + U - + B - + R - + G - + X - + Main Window | Deck Editor - + Power / Toughness - + Power and Toughness - + Add (+1/+1) - + Remove (-1/-1) - + + Reset + + + + Toughness - + Remove (-0/-1) - + Add (+0/+1) - + Power - + Remove (-1/-0) - + Add (+1/+0) - + Game Phases - + Untap - + Upkeep - - + Draw - + Main 1 - + Start combat - + Attack - + Block - + Damage - + End combat - + Main 2 - + End - + Next phase - + Next turn - + Playing Area - + + Move selected card to + + + + + Top cards of library + + + + + Gameplay | Draw | Move | View + + + + Manage sets - + Export deck - + Save deck (clip) - + Save deck (clip; no annotations) - + Tap / Untap Card - + Untap all - + Toggle untap - + Flip card - + Peek card - + Play card - + Attach card - + Unattach card - + Clone card - + Create token - + Create all related tokens - + Create another token - + Set annotation - + Phases | P/T | Playing Area - - Move card to - - - - + Bottom library - + Top library - - + + Graveyard - - + + Exile - + Hand - + + Play face down + + + + View - + Library - - Tops card of library - - - - + Sideboard - + Close recent view - + + Move top card to + + + + + Graveyard Once + + + + + Graveyard Multiple + + + + + Exile Once + + + + + Exile Multiple + + + + Game Lobby - + Load remote deck - + Load local deck - + Gameplay - + Draw arrow - + Leave game - + Remove local arrows - + Concede - + Roll dice - + Rotate view CW - + Shuffle library - + Rotate view CCW - + + Drawing + + + + Mulligan - + Draw card - + Draw cards - + Undo draw - + Always reveal top card - - Draw | Move | View | Gameplay - - - - + How to set custom shortcuts - + Restore all default shortcuts - + Clear all shortcuts diff --git a/cockatrice/translations/cockatrice_en@pirate.ts b/cockatrice/translations/cockatrice_en@pirate.ts index 618933c09..7c070715d 100644 --- a/cockatrice/translations/cockatrice_en@pirate.ts +++ b/cockatrice/translations/cockatrice_en@pirate.ts @@ -20,62 +20,62 @@ AppearanceSettingsPage - + Theme settings Theme settin's - + Current theme: - + Card rendering C-arrr-d Scribblin' - + Display card names on cards having a picture - + Scale cards on mouse over - + Hand layout - + Display hand horizontally (wastes space) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -85,17 +85,17 @@ ban &user name - + Curse da landlubber's name ban &IP address - + Curse da landlubbers port o call ban client I&D - + Curse da landlubber's Jolly Roger @@ -188,6 +188,29 @@ This be only saved for captains and cannot be seen by t' brutally banned sc + + BetaReleaseChannel + + + Beta Releases + + + + + No reply received from the release update server. + + + + + Invalid reply received from the release update server. + + + + + No reply received from the file update server. + + + CardDatabaseModel @@ -242,32 +265,37 @@ This be only saved for captains and cannot be seen by t' brutally banned sc CardInfoText - + + Unknown card: + + + + Name: Name: - + Mana cost: Magic tax: - + Color(s): Color(s): - + Card type: Type 'o magic: - + P / T: P / T: - + Loyalty: @@ -411,58 +439,58 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckEditorSettingsPage - - + + Update Spoilers - - Updating Spoilers + + Updating... - + Choose path Map yer scurvy way - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Hey, something's here finally! YARR CAP'N, THAR BE A NEW SHIP SAILIN'! - + Last Updated - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update complete @@ -470,12 +498,12 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckListModel - + Number - + Card @@ -537,136 +565,129 @@ This be only saved for captains and cannot be seen by t' brutally banned sc - - DevReleaseChannel - - - Development snapshots - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - DlgConnect - + New Host - + &Host: &Cap'n - + Known Hosts - Name: - - - - - &Port: + Refresh the server list with known public servers + Name: + + + + + &Port: + + + + Player &name: Matey &name: - + P&assword: &A stupendously secret code: - + &Save password Record t' &stupendously secret code - + A&uto connect - + Automatically connect to the most recent login when Cockatrice opens - - - Public Servers - - - - - Forgot password - - - Connect + If you have any trouble connecting or registering then contact the server staff for help! - - Cancel + + + Webpage - - Server + + Forgot Password + + + + + &Connect + + + + + Server Contact + + + + + Connect to Server + Server + + + + Login - - Connect to server - - - - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. + + + Downloading... + + DlgCreateGame @@ -858,7 +879,7 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DlgEditAvatar - + No image chosen. No pic be picked! @@ -879,17 +900,17 @@ To remove your current avatar, confirm without choosing a new image. - + Open Image Open Pic - + Image Files (*.png *.jpg *.bmp) - + Invalid image chosen. Invalid pic be picked! @@ -1369,12 +1390,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgSettings - + Unknown Error loading card database Cap'n? Thar be a unknown problem wi' t' dock - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1385,7 +1406,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -1396,7 +1417,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1405,21 +1426,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues @@ -1428,63 +1449,81 @@ Would you like to change your database location setting? - - - + + + Error Cap'n? Thar be a problem - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Cap'n's orders - + General - + Appearance - + User Interface - + Deck Editor Dock - + Chat - + Sound - + Shortcuts + + DlgTipOfTheDay + + + Next + + + + + Previous + + + + + Tip of the Day + + + DlgUpdate @@ -1821,32 +1860,32 @@ You may have to build from source yourself. - + Type - + Description - + Creator Cap'n - + Restrictions - + Players Mateys - + Spectators Landlubbers @@ -1854,852 +1893,844 @@ You may have to build from source yourself. GeneralSettingsPage - - - - - + + + + + Choose path - + Success - + Downloaded card pictures have been reset. - + Error Cap'n? Thar be a problem - + One or more downloaded card pictures could not be cleared. - + Personal settings Cap'n's orders for t' cap'n - + Language: - + Download card pictures on the fly - + Paths (editing disabled in portable mode) - + Paths - + Decks directory: - + Replays directory: - + Pictures directory: - + Card database: Dock: - + Token database: - + Picture cache size: - + Primary download URL: - + Fallback download URL: - + How to set a custom picture url - + Reset/clear downloaded pictures - + Update channel - + Notify if a feature supported by the server is missing in my client - - + + Reset - - - Logger - - Client Operating System - - - - - Build Architecture - - - - - Qt Version + + Show tips on startup MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator Cap'n's thrown ye outta tha ship! - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. Yarrr, that ain't ye name ye scurvy landlubber! - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players Number o' mateys - + Please enter the number of players. Me hearty, enter t' number o' mateys - - + + Player %1 Matey %1 - + Load replay - + About Cockatrice - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + + + + Error Cap'n? Thar be a problem - + Server timeout T' ship be timin' out! - + Failed Login - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client ID's. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please try closing and reopening your client and try again. If the error persists try updating your client to the most recent build and if need be contact your software provider. - + Account activation - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + Your client seems to be missing features this server requires for connection. - + Version - + Our Translators - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + The email address provider used during registration has been blacklisted for use on this server. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. The code be too piddly to be stupendously secret. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... Boarding %1 - + Registering to %1 as %2... - + Disconnected Disembarked - + Connected, logging in at %1 - - - + + + Requesting forgot password to %1 as %2... - + &Connect... &Board ye ship... - + &Disconnect &Disembark - + Start &local game... Built &local ship - + &Watch replay... - + &Deck editor &Dock - + &Full screen - + &Register to server... - + &Settings... Cap'n'&s orders - - + + &Exit Dis&embark - + A&ctions - + &Cockatrice - + C&ard Database - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Edit &tokens... - + &About Cockatrice - + + &Tip of the Day + + + + Check for Client Updates - + View &debug log - + &Help - + Check for card updates... - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes AYE! - - + + No NAY! - + Open settings - + New sets found - + %1 new set(s) found in the card database Set code(s): %2 Do you want to enable it/them? - + View sets - + Welcome - - - + + + Information - + A card database update is already running. A dock update be runnin' already! - + Unable to run the card database updater: Cap'n! We can't run the dock updater: - + failed to start. - + crashed. - + timed out. be timin' out! - + write error. Cap'n? Thar be a writin' problem wi' t' ship. - + read error. Cap'n? Thar be a readin' problem wi' t' ship. - + unknown error. Cap'n? Thar be a unknown problem wi' t' ship. - + The card database updater exited with an error: %1 The dock updater sunk. She be havin' a problem: %1 - + Update completed successfully. Cockatrice will now reload the card database. - + You can only import XML databases at this time. - - - + + + Forgot Password - + Your password has been reset successfully, you now may log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - - - - + + + + Load sets/cards - + &Manage sets... - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -2707,18 +2738,18 @@ To update your client, go to Help -> Check for Updates. - + Selected file cannot be found. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. @@ -2841,6 +2872,16 @@ Cockatrice will now reload the card database. %1 plays %2%3. + + + %1 turns %2 face-down. + + + + + %1 turns %2 face-up. + + %1 has left the game (%2). @@ -3096,16 +3137,6 @@ Cockatrice will now reload the card database. %1 draws their initial hand. - - - %1 flips %2 face-down. - - - - - %1 flips %2 face-up. - - %1 destroys %2. @@ -3275,79 +3306,79 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Add message - + Message: - + Chat settings Cap'n's orders for t' chat - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3413,478 +3444,485 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - + Number of cards: (max. %1) - + &View graveyard &View Davy Jones' Locker - + &View exile - + Player "%1" Matey "%1" - - - - + + + + &Graveyard Davy Jones' Locker - - - - + + + + &Exile - - - &Move hand to... - - - - - - &Top of library + &Move hand to... - + + &Top of library + + + + + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand &Hand - + &Move exile to... - + &View library &View library - + View &top cards of library... View &top cards o' library... - + Reveal &library to... - + Reveal t&op cards to... - + &Always reveal top card - + O&pen deck in deck editor O&pen flagship in dock - + &View sideboard &View sideplank - + &Draw card &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - + &Shuffle - + Play top card &face down - + Move top cards to &graveyard... Move top cards to Davy Jones' Locker - + Move top cards to &exile... - + Put top card on &bottom - + Put bottom card &in graveyard - + &Reveal hand to... &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard &Sideplank - + &Library &Library - + &Counters Pie&ces o' eight - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token - + S&ay - + C&ard - + &All players - + &Play - + &Hide - + Play &Face Down - + Toggle &normal untapping - - &Flip - - - - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Set &power and toughness... - + &Set annotation... - + Red Blood red - + Yellow Fool's gold - + Green Emerald green - + X cards from the top of library... - - + + C&reate another %1 token - + Create tokens - - - - - - + + + + + + Token: - + Place card X cards from top of library - + How many cards from the top of the deck should this card be placed: - - + + View related cards + + + + + Attach to - + All tokens - + View top cards of library View top cards o' library - + &Tap / Untap + Turn sideways or back again - + + T&urn Over + Turn face up/face down + + + + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + Number of cards: Number o' cards: - + Draw cards - - - - - + + + + + Number: - + Move top cards to grave - + Move top cards to exile - + Roll die - + Number of sides: - + Set power/toughness - + Please enter the new PT: - + Set annotation - + Please enter the new annotation: - + Set counters Set pieces o' eight @@ -3892,37 +3930,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -3930,18 +3968,18 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) @@ -4058,10 +4096,15 @@ Cockatrice will now reload the card database. SequenceEdit - + Shortcut already in use + + + Invalid key + + SetsModel @@ -4091,6 +4134,21 @@ Cockatrice will now reload the card database. + + ShortcutsSettings + + + Your configuration file contained invalid shortcuts. +Please check your shortcut settings! + + + + + The following shortcuts have been set to default: + + + + ShortcutsTab @@ -4104,12 +4162,12 @@ Cockatrice will now reload the card database. - + Clear all default shortcuts - + Do you really want to clear all shortcuts? @@ -4135,27 +4193,27 @@ Cockatrice will now reload the card database. SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings Cap'n's orders for t' shanties - + Master volume @@ -4163,48 +4221,48 @@ Cockatrice will now reload the card database. SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -4213,7 +4271,7 @@ Cockatrice will now reload the card database. StableReleaseChannel - Stable releases + Stable Releases @@ -4278,231 +4336,246 @@ Cockatrice will now reload the card database. TabDeckEditor - + &Clear all filters - + Delete selected - + Deck &name: Flagship &name: - + &Comments: - + Hash: - + &New deck Freshly formed flagship - + &Load deck... Find a flagship... - + &Save deck Record flag&ship - + Save deck &as... Record flagship &as... - + Load deck from cl&ipboard... Find a flagship from t' cl&ipplank - + &Print deck... &Print flagship... - + Search by card name - + + Add to Deck + + + + + Add to Sideboard + + + + + Show Related cards + + + + Save deck to clipboard - + Annotated - + Not Annotated - + &Send deck to online service - + Create decklist (decklist.org) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close - + Add card to &maindeck Add card to t' &mainplank - + Add card to &sideboard - + &Remove row - + &Increment number - + &Decrement number - + &Deck Editor &Dock - - + + Card Info - - + + Deck - - + + Filters - + &View - - - + + + Visible - - - + + + Floating - + Reset layout - + Deck: %1 Flagship: %1 - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - + Load deck Find a flagship - - - - - + + + + + Error Cap'n? Thar be a problem - + The deck could not be saved. T' flagship be failin' to be recordin'! - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck Record flagship - + There are no cards in your deck to be exported - + No deck was selected to be saved. @@ -4599,175 +4672,175 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Card Info - - + + Player List - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next &turn - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + &Concede - + &Leave game - + C&lose replay - + &Say: - + &View - - - + + + Visible - - - + + + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Leave game - + Are you sure you want to leave this game? - + You are flooding the game. Please wait a couple of seconds. - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5125,22 +5198,22 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Are you sure? - + There are still open games. Are you sure you want to quit? - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5148,38 +5221,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted to moderator. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -5217,6 +5290,21 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TipsOfTheDay + + + File does not exist. + + + + + + Failed to open file. + + + + UpdateDownloader @@ -5541,42 +5629,42 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + &Double-click cards to play them (instead of single-click) - + &Play all nonlands onto the stack (not the battlefield) by default - + Annotate card text on tokens - + Animation settings Cap'n's orders for t' things that be movin' - + &Tap/untap animation @@ -5646,97 +5734,127 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Move selected fleet to the top - + Move selected set up Move selected fleet up - + Move selected set down Move selected fleet down - + Move selected set to the bottom Move selected fleet to the bottom - - Enable all sets - Enable all fleets - - - - Disable all sets - Disable all fleets - - - - Enable selected set(s) + + Search by set name, code, or type - - Disable selected set(s) + + Default order - - Deck Editor - - - - - Only cards in enabled sets will appear in the deck editor card list - - - - - Card Art - - - - - Image priority is decided in the following order - - - - - The - - - - - CUSTOM Folder - - - - - Enabled Sets (Top to Bottom) + + Restore original art priority order + Enable all sets + Enable all fleets + + + + Disable all sets + Disable all fleets + + + + Enable selected set(s) + + + + + Disable selected set(s) + + + + + Deck Editor + + + + + Only cards in enabled sets will appear in the deck editor card list + + + + + Card Art + + + + + Image priority is decided in the following order + + + + + The + + + + + CUSTOM Folder + + + + + Enabled Sets (Top to Bottom) + + + + Disabled Sets (Top to Bottom) - + + Warning: + + + + + While the set list is sorted by any of the columns, custom art priority setting is disabled. + + + + + To disable sorting click on the same column header again until this message disappears. + + + + Manage sets - + Success - + The sets database has been saved successfully. @@ -5767,7 +5885,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English English, arr! (Pirate English) @@ -5840,97 +5958,113 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Load deck (clipboard) - + Clear all filters - + New deck - + Clear selected filter - + Open custom pic folder - + Close - + Print deck - + Delete card - + Edit tokens - + Reset layout - + Add card - + Save deck - + Remove card - + Save deck as - + Load deck - - + + Counters - + Life - + + + + + + + + + + + + + + Set + + + - + @@ -5938,16 +6072,15 @@ Please refrain from engaging in this activity or further actions may be taken ag - - Set + + Add - - + @@ -5955,455 +6088,440 @@ Please refrain from engaging in this activity or further actions may be taken ag - Add - - - - - - - - - - - - - - - + Remove - + Red - + Green - + Yellow - + Storm - + W - + U - + B - + R - + G - + X - + Main Window | Deck Editor - + Power / Toughness - + Power and Toughness - + Add (+1/+1) - + Remove (-1/-1) - + Toughness - + Remove (-0/-1) - + Add (+0/+1) - + Power - + Remove (-1/-0) - + Add (+1/+0) - + Game Phases - + Untap - + Upkeep - - + + Draw - + Main 1 - + Start combat - + Attack - + Block - + Damage - + End combat - + Main 2 - + End - + Next phase - + Next turn - + Playing Area - + Manage sets - + Export deck - + Save deck (clip) - + Save deck (clip; no annotations) - + Tap / Untap Card - + Untap all - + Toggle untap - + Flip card - + Peek card - + Play card - + Attach card - + Unattach card - + Clone card - + Create token - + Create all related tokens - + Create another token - + Set annotation - + Phases | P/T | Playing Area - + Move card to - + Bottom library - + Top library - - + + Graveyard - - + + Exile - + Hand - + View - + Library - + Tops card of library - + Sideboard - + Close recent view - + Game Lobby - + Load remote deck - + Load local deck - + Gameplay - + Draw arrow - + Leave game - + Remove local arrows - + Concede - + Parley - + Roll dice - + Toss da bones - + Rotate view CW - + Shuffle library - + Rotate view CCW - + Mulligan - + Draw card - + Draw cards - + Undo draw - + Always reveal top card - + Draw | Move | View | Gameplay - + How to set custom shortcuts - + Restore all default shortcuts - + Clear all shortcuts diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index 58b1cf7c7..5cc54b167 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -193,22 +193,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Beta Releases - + version bêta No reply received from the release update server. - + Le serveur de mise à jour ne répond pas. Invalid reply received from the release update server. - + Réponse reçue du serveur de mise à jour de version invalide . No reply received from the file update server. - + Le serveur de mise à jour de fichier ne répond pas. @@ -442,47 +442,47 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Update Spoilers - + mettre à jour les spoilers Updating... - + Mise à jours Choose path - + Choisir le chemin Spoilers - + Spoilers Download Spoilers Automatically - + Télécharger automatiquement les spoilers Spoiler Location: - + Emplacement des spoilers: Hey, something's here finally! - + Hey, quelque chose est enfin là! Last Updated - + dernière mise à jour Spoilers download automatically on launch - + spoilers téléchargé automatiquement au lancement @@ -585,7 +585,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Refresh the server list with known public servers - + Actualiser la liste de serveurs avec des serveurs publics connus @@ -625,33 +625,33 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa If you have any trouble connecting or registering then contact the server staff for help! - + Si vous rencontrez des difficultés pour vous connecter ou vous inscrire, contactez le personnel du serveur pour obtenir de l'aide! Webpage - + page internet Forgot Password - + Mot de passe oublié &Connect - + &Connexion Server Contact - + Contact du serveur Connect to Server - + Connecter au serveur @@ -686,7 +686,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Downloading... - + Téléchargement @@ -1041,7 +1041,8 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. - + Le nom choisi est en conflit avec une carte ou un jeton existant. +Assurez-vous d'activer les set de 'jeton' dans le menu "gérer les sets" afin de les afficher correctement. @@ -1536,17 +1537,17 @@ Voulez vous changer les paramètres d'emplacement de base de données ? Next - + suivant Previous - + précédent Tip of the Day - + Conseil du jour @@ -1637,7 +1638,7 @@ Please visit the download page to update manually. Released - + Sortie @@ -1715,7 +1716,7 @@ Vous devez compiler les sources vous-même. Clear log when closing - + Effacer le journal lors de la fermeture @@ -1728,7 +1729,7 @@ Vous devez compiler les sources vous-même. Type your filter here - + Tapez votre filtre ici @@ -2041,7 +2042,7 @@ Vous devez compiler les sources vous-même. Show tips on startup - + Afficher les astuces au démarrage @@ -2589,7 +2590,7 @@ La version locale est %1, la nouvelle version est %2. &Tip of the Day - + &Conseil du jour @@ -2764,14 +2765,14 @@ Cockatrice va maintenant recharger de nouveau la base de données de cartes. &Manage sets... - + &Gérer les sets... Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Salut! On dirait que vous utilisez cette version de Cockatrice pour la première fois. Tous les sets de la base de données de cartes ont été activés. Pour plus d'informations sur la modification de l'ordre des sets ou la désactivation de sets spécifiques et de leurs effets, consultez le menu "Gérer les sets". @@ -2920,12 +2921,12 @@ Cockactrice va maintenant recharger la base de données de cartes. %1 turns %2 face-down. - + %1 tourner %2 face cachée. %1 turns %2 face-up. - + %1 tourner %2 face visible. @@ -3862,7 +3863,7 @@ Cockactrice va maintenant recharger la base de données de cartes. View related cards - + Voir les cartes associées @@ -3890,7 +3891,7 @@ Cockactrice va maintenant recharger la base de données de cartes. T&urn Over Turn face up/face down - + Retourner @@ -4148,7 +4149,7 @@ Cockactrice va maintenant recharger la base de données de cartes. Invalid key - + Clé invalide @@ -4185,13 +4186,14 @@ Cockactrice va maintenant recharger la base de données de cartes. Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + Votre fichier de configuration contenait des raccourcis invalides. Merci de vérifier vos paramètres de raccourci ! The following shortcuts have been set to default: - + Les raccourcis suivants ont été définis par défaut: + @@ -4268,48 +4270,48 @@ Please check your shortcut settings! Spoilers season has ended - + La saison des spoilers est terminée Deleting spoiler.xml. Please run Oracle - + Suppression de spoiler.xml. S'il vous plaît exécuter Oracle Spoilers download failed - + Échec du téléchargement des spoilers No internet connection - + Pas de connexion Internet Error - + Erreur Spoilers already up to date - + Spoilers déjà à jour No new spoilers added - + Aucun nouveau spoilers ajouté Spoilers have been updated! - + Les spoilers ont été mis à jour! Last change: - + Dernier changement: @@ -4317,7 +4319,7 @@ Please check your shortcut settings! Stable Releases - + Version stable @@ -4438,57 +4440,57 @@ Please check your shortcut settings! Search by card name - + Rechercher par nom de carte Add to Deck - + Ajouter au Deck Add to Sideboard - + Ajouter à la réserve Show Related cards - + Afficher les cartes associées Save deck to clipboard - + Enregistrer le deck dans le presse-papier Annotated - + Annoté Not Annotated - + sans annotation &Send deck to online service - + & Envoyer le Deck au service en ligne Create decklist (decklist.org) - + Créer une liste de deck (decklist.org) Analyze deck (deckstats.net) - + Analyser le Deck (deckstats.net) Analyze deck (tappedout.net) - + Analyser le deck (tappedout.net) @@ -4619,12 +4621,12 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. There are no cards in your deck to be exported - + Il n'y a pas de cartes dans le deck à exporter No deck was selected to be saved. - + Aucun deck n'a été sélectionné pour être sauvegardé. @@ -5077,7 +5079,7 @@ Plus vous entrez d'informations, meilleurs seront les résultats. Private message from - + Message privé de @@ -5350,13 +5352,15 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre File does not exist. - + Le fichier n'existe pas. + Failed to open file. - + Échec de l'ouverture du fichier. + @@ -5810,17 +5814,17 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Search by set name, code, or type - + Rechercher par ensemble de nom, code ou type Default order - + Ordre par défaut Restore original art priority order - + Restaurer l'ordre originale de priorité artistique @@ -5885,22 +5889,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Warning: - + Attention: While the set list is sorted by any of the columns, custom art priority setting is disabled. - + lorsque la liste est triée dans une des colonnes, la priorité personnalisée est désactivée. To disable sorting click on the same column header again until this message disappears. - + Pour désactiver le tri, cliquez à nouveau sur la même en-tête de colonne jusqu'à ce que ce message disparaisse. Manage sets - + Gérer les sets @@ -6330,22 +6334,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Manage sets - + Gérer les sets Export deck - + Exporter le Deck Save deck (clip) - + Sauvegarder le Deck (du presse-papier) Save deck (clip; no annotations) - + Enregistrer le Deck (du presse papier; sans annotations) diff --git a/cockatrice/translations/cockatrice_ja.ts b/cockatrice/translations/cockatrice_ja.ts index 02011a2fa..d6c6f13e5 100644 --- a/cockatrice/translations/cockatrice_ja.ts +++ b/cockatrice/translations/cockatrice_ja.ts @@ -267,7 +267,7 @@ This is only saved for moderators and cannot be seen by the banned person. Unknown card: - + 謎のカード: @@ -585,7 +585,7 @@ This is only saved for moderators and cannot be seen by the banned person. Refresh the server list with known public servers - + デフォルトの公開サーバーリストに更新します @@ -625,33 +625,33 @@ This is only saved for moderators and cannot be seen by the banned person. If you have any trouble connecting or registering then contact the server staff for help! - + 接続や登録に問題がある場合は、サーバー管理者にお問い合わせください。 Webpage - + ウェブページ Forgot Password - + パスワード忘れちゃった &Connect - + 接続 Server Contact - + サーバーの連絡先 Connect to Server - + サーバーに接続 @@ -1030,7 +1030,7 @@ To remove your current avatar, confirm without choosing a new image. Please enter the name of the token: - トークン名を入力してください: + トークン名を入力: @@ -1771,7 +1771,7 @@ You may have to build from source yourself. The game does not exist any more. - このゲームは既に存在しません。 + このゲームはすでに存在しません。 @@ -2818,12 +2818,12 @@ Cockatrice will now reload the card database. %1 is now watching the game. - %1が観戦に参加した。 + %1 が観戦に参加した。 %1 has loaded a deck (%2). - %1はデッキをロードした。ハッシュ:%2 + %1 はデッキをロードした。ハッシュ:%2 @@ -2889,57 +2889,57 @@ Cockatrice will now reload the card database. %1 gives %2 control over %3. - %1は、%2に《%3》のコントロールを渡した。 + %1 は %2 に %3 のコントロールを渡した。 %1 puts %2 into play tapped%3. - %1は《%2》を%3タップ状態でプレイした。 + %1 は %2 を%3タップ状態でプレイした。 %1 puts %2 into play%3. - %1は《%2》を%3プレイした。 + %1 は %2 を%3プレイした。 %1 exiles %2%3. - %1は《%2》を%3追放した。 + %1 は %2 を%3追放した。 %1 puts %2%3 into their library %4 cards from the top. - %1は《%2》を%3ライブラリーの一番上から%4枚目に置いた。 + %1 は %2 を%3ライブラリーの一番上から%4枚目に置いた。 %1 moves %2%3 to sideboard. - %1は《%2》を%3サイドボードに置いた。 + %1 は %2 を%3サイドボードに置いた。 %1 plays %2%3. - %1は《%2》を%3プレイした。 + %1 は %2 を%3プレイした。 %1 turns %2 face-down. - %1は《%2》を裏向きにした。 + %1 は %2 を裏向きにした。 %1 turns %2 face-up. - %1は《%2》を表向きにした。 + %1 は %2 を表向きにした。 %1 has left the game (%2). - %1はゲームから離脱した(%2)。 + %1 はゲームから離脱した(%2)。 %1 is not watching the game any more (%2). - %1が観戦から離脱した(%2)。 + %1 が観戦から離脱した(%2)。 @@ -3004,7 +3004,7 @@ Cockatrice will now reload the card database. %1's turn. - ■%1のターン■ + ■ %1 のターン■ @@ -3024,12 +3024,12 @@ Cockatrice will now reload the card database. %1 is now keeping the top card %2 revealed. - %1は、%2一番上のカードを公開した状態でゲームをプレイしている。 + %1 は%2一番上のカードを公開した状態でゲームをプレイしている。 %1 is not revealing the top card %2 any longer. - %1は、%2一番上のカードの公開を終えた。 + %1 は%2一番上のカードの公開を終えた。 @@ -3039,47 +3039,47 @@ Cockatrice will now reload the card database. %1 has joined the game. - %1がゲームに参加した。 + %1 がゲームに参加した。 %1 is ready to start the game. - %1はゲーム開始の準備が完了した!! + %1 はゲーム開始の準備が完了した!! %1 is not ready to start the game any more. - %1は準備完了を解除した。 + %1 は準備完了を解除した。 %1 has locked their sideboard. - %1はサイドボードをロックした。 + %1 はサイドボードをロックした。 %1 has unlocked their sideboard. - %1はサイドボードを解禁した。 + %1 はサイドボードを解禁した。 %1 has conceded the game. - %1が投了した!!! + %1 が投了した!!! %1 has restored connection to the game. - %1がゲームに再接続した。 + %1 がゲームに再接続した。 %1 has lost connection to the game. - %1はゲームから切断された。 + %1 はゲームから切断された。 %1 shuffles %2. - %1は%2を切り直した。 + %1 は%2を切り直した。 @@ -3094,27 +3094,27 @@ Cockatrice will now reload the card database. %1 flipped a coin. It landed as %2. - %1はコインを投げた。結果は……【%2】だ! + %1 はコインを投げた。結果は……【%2】だ! %1 rolls a %2 with a %3-sided die. - %1は%3面ダイスをふり、【%2】を出した。 + %1 は%3面ダイスをふり、【%2】を出した。 %1 draws %2 card(s). - %1は%2枚カードを引いた。 + %1 は%2枚カードを引いた。 %1 undoes their last draw. - %1は最後に引いたカードを戻した。 + %1 は最後に引いたカードを戻した。 %1 undoes their last draw (%2). - %1は最後に引いたカード (《%2》) を戻した 。 + %1 は最後に引いたカード ( %2 ) を戻した 。 @@ -3154,202 +3154,202 @@ Cockatrice will now reload the card database. %1 puts %2%3 into their graveyard. - %1は《%2》を%3墓地に置いた。 + %1 は %2 を%3墓地に置いた。 %1 moves %2%3 to their hand. - %1は《%2》を%3手札に加えた。 + % 1は %2 を%3手札に加えた。 %1 puts %2%3 into their library. - %1は《%2》を%3ライブラリーに加えた。 + %1 は %2 を%3ライブラリーに加えた。 %1 puts %2%3 on bottom of their library. - %1は《%2》を%3ライブラリーの一番下に置いた。 + %1 は %2 を%3ライブラリーの一番下に置いた。 %1 puts %2%3 on top of their library. - %1は《%2》を%3ライブラリーの一番上に置いた。 + %1 は %2 を%3ライブラリーの一番上に置いた。 %1 takes a mulligan to %2. - %1は%2枚にマリガンした。 + %1 は%2枚にマリガンした。 %1 draws their initial hand. - %1は初期手札を引いた。 + %1 は初期手札を引いた。 %1 destroys %2. - %1は《%2》を取り除いた。 + %1 は %2 を取り除いた。 %1 unattaches %2. - %1は《%2》をはずした。 + %1 は %2 をはずした。 %1 creates token: %2%3. - %1はトークン:《%2》を%3作成した。 + %1 はトークン: %2 を%3作成した。 %1 points from their %2 to themselves. - %1は《%2》から自分自身へ対象を指定した。 + %1 は %2 から自分自身へ対象を指定した。 %1 points from their %2 to %3. - %1は、《%2》から%3へ対象を指定した。 + %1 は %2 から %3 へ対象を指定した。 %1 points from %2's %3 to themselves. - %1は、%2の《%3》から自分自身へ対象を指定した。 + %1 は %2 の %3 から自分自身へ対象を指定した。 %1 points from %2's %3 to %4. - %1は、%2の《%3》から%4へ対象を指定した。 + %1 は %2 の %3 から %4 へ対象を指定した。 %1 points from their %2 to their %3. - %1は、《%2》から%3へ対象を指定した。 + %1 は %2 から %3 へ対象を指定した。 %1 points from their %2 to %3's %4. - %1は、《%2》から%3の《%4》へ対象を指定した。 + %1 は %2 から %3 の %4 へ対象を指定した。 %1 points from %2's %3 to their own %4. - %1は、%2の《%3》から《%4》へ対象を指定した。 + %1 は %2 の %3 から %4 へ対象を指定した。 %1 points from %2's %3 to %4's %5. - %1は、%2の《%3》から%4の《%5》へ対象を指定した。 + %1 は %2 の %3 から %4 の %5 へ対象を指定した。 %1 places %2 %3 counter(s) on %4 (now %5). - %1は《%4》の上に%3カウンターを%2個置いた (計%5個) 。 + %1 は %4 の上に%3カウンターを%2個置いた (計%5個) 。 %1 removes %2 %3 counter(s) from %4 (now %5). - %1は《%4》の上から%3カウンターを%2個取り除いた (計%5個) 。 + %1 は %4 の上から%3カウンターを%2個取り除いた (計%5個) 。 %1 taps their permanents. - %1は自分のコントロールするパーマネントをタップした。 + %1 は自分のコントロールするパーマネントをタップした。 %1 untaps their permanents. - %1は自分のコントロールするパーマネントをアンタップした。 + %1 は自分のコントロールするパーマネントをアンタップした。 %1 taps %2. - %1は《%2》をタップした。 + %1 は %2 をタップした。 %1 untaps %2. - %1は《%2》をアンタップした。 + %1 は %2 をアンタップした。 %1 sets counter %2 to %3 (%4%5). - %1は%2カウンターを%3に設定した (%4%5)。 + %1 は%2カウンターを%3に設定した (%4%5)。 %1 sets %2 to not untap normally. - %1は《%2》をアンタップ・ステップの間にアンタップしないようにした。 + %1 は %2 をアンタップ・ステップの間にアンタップしないようにした。 %1 sets %2 to untap normally. - %1は《%2》を通常どおりアンタップするように設定した。 + %1 は %2 を通常どおりアンタップするように設定した。 %1 sets PT of %2 to %3. - %1は《%2》のP/Tを%3にした。 + %1 は %2 のP/Tを%3にした。 %1 sets annotation of %2 to %3. - %1は《%2》に注釈をつけた (%3)。 + %1 は %2 に注釈をつけた (%3)。 %1 is looking at %2. - %1は%2を見ている。 + %1 は%2を見ている。 %1 is looking at the top %2 card(s) %3. - %1は%3上から%2枚のカードを見ている。 + %1 は%3上から%2枚のカードを見ている。 %1 stops looking at %2. - %1は%2を見るのをやめた。 + %1 は%2を見るのをやめた。 %1 reveals %2 to %3. - %1は、%3に%2を見せた。 + %1 は %3 に%2を見せた。 %1 reveals %2. - %1は%2を公開した。 + %1 は%2を公開した。 %1 randomly reveals %2%3 to %4. - %1は%3無作為に《%2》を選び、%4に見せた。 + %1 は%3無作為に %2 を選び、%4に見せた。 %1 randomly reveals %2%3. - %1は%3無作為に《%2》を選び、公開した。 + %1 は%3無作為に %2 を選び、公開した。 %1 peeks at face down card #%2. - %1は裏向きのカード#%2の表面を確認した。 + %1 は裏向きのカード#%2の表面を確認した。 %1 peeks at face down card #%2: %3. - %1は裏向きのカード#%2の表面を確認した (《%3》) 。 + %1 は裏向きのカード#%2の表面を確認した ( %3 ) 。 %1 reveals %2%3 to %4. - %1は%3《%2》を%4に見せた。 + %1 は%3 %2 を %4 に見せた。 %1 reveals %2%3. - %1は%3《%2》を公開した + %1 は%3 %2 を公開した。 @@ -3515,7 +3515,7 @@ Cockatrice will now reload the card database. Player "%1" - プレイヤー "%1" + プレイヤー: %1 @@ -3836,7 +3836,7 @@ Cockatrice will now reload the card database. C&reate another %1 token - 別の%1トークンを出す + 別の %1 トークンを出す @@ -4274,7 +4274,7 @@ Please check your shortcut settings! Spoilers season has ended - + スポイラーシーズンが終了しました @@ -5063,7 +5063,7 @@ The more information you put in, the more specific your results will be. Private &chat - プライベートチャット + 個人チャット @@ -5073,7 +5073,7 @@ The more information you put in, the more specific your results will be. %1 - Private chat - %1 - プライベートチャット + %1 - 個人チャット @@ -5893,17 +5893,17 @@ Please refrain from engaging in this activity or further actions may be taken ag Warning: - + 警告: While the set list is sorted by any of the columns, custom art priority setting is disabled. - + セットリストがいずれかの列でソートされている間、セット読み込み順の優先度設定は無効です。 To disable sorting click on the same column header again until this message disappears. - + このメッセージが消えるまで同じ列見出しをクリックし続けると、ソートを無効にすることができます。 diff --git a/cockatrice/translations/cockatrice_ru.ts b/cockatrice/translations/cockatrice_ru.ts index 709166f7e..310765244 100644 --- a/cockatrice/translations/cockatrice_ru.ts +++ b/cockatrice/translations/cockatrice_ru.ts @@ -20,62 +20,62 @@ AppearanceSettingsPage - + Theme settings - Выбор темы + Настройки темы - + Current theme: - Тема + Текущая тема: - + Card rendering Отрисовка карт - + Display card names on cards having a picture - Отображать название карты поверх изображения + Отображать название карты вместе с изображением - + Scale cards on mouse over Увеличивать карты при наведении мыши - + Hand layout Расположение руки - + Display hand horizontally (wastes space) - Отбражать руку горизонтально + Отображать руку горизонтально (требукт место) - + Enable left justification Выравнивание по правому краю - + Table grid layout Сетка - + Invert vertical coordinate Инвертировать вертикальные координаты - + Minimum player count for multi-column layout: Минимальное количество игроков для столбчатого расположения: - + Maximum font size for information displayed on cards: Максимальный размер шрифта для текста карт: @@ -188,6 +188,29 @@ This is only saved for moderators and cannot be seen by the banned person.Необходимо ввести ID клиента при выборе опции бана по ID. + + BetaReleaseChannel + + + Beta Releases + Бета-версии + + + + No reply received from the release update server. + Нет ответа от сервера обновления версий. + + + + Invalid reply received from the release update server. + Неправильный ответ от сервера обновления версий. + + + + No reply received from the file update server. + Нет ответа от сервера обновления файлов. + + CardDatabaseModel @@ -242,32 +265,37 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoText - + + Unknown card: + Неизвестная карта: + + + Name: Название: - + Mana cost: Мановая стоимость: - + Color(s): Цвет(а): - + Card type: Тип: - + P / T: Сила/Выносливость: - + Loyalty: Преданность: @@ -411,71 +439,71 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - + Обновить спойлеры - - Updating Spoilers - + + Updating... + Обновление... - + Choose path - + Выбрать путь - + Spoilers - + Спойлеры - + Download Spoilers Automatically - + Скачивать спойлеры автоматически - + Spoiler Location: - + Расположение спойлеров: - + Hey, something's here finally! - + Хей, здесь что-то наконец есть! - + Last Updated - + Последнее обновление - + Spoilers download automatically on launch - + Спойлеры скачиваются автоматически при запуске - + Press the button to manually update without relaunching - + Нажмите, чтобы обновить вручную без перезапуска - + Do not close settings until manual update complete - + Не закрывать настройки до завершения ручного обновления DeckListModel - + Number Номер - + Card Название @@ -537,136 +565,129 @@ This is only saved for moderators and cannot be seen by the banned person.Выбранный файл не может быть загружен - - DevReleaseChannel - - - Development snapshots - Снимки разработчика - - - - No reply received from the release update server. - Сервер обновлений версии не отвечает. - - - - Invalid reply received from the release update server. - Ошибка отклика сервера обновлений версии. - - - - No reply received from the file update server. - Сервер обновлений файлов не отвечает. - - DlgConnect - + New Host Новый хост - + &Host: &Хост: - + Known Hosts Известные Хосты + Refresh the server list with known public servers + + + + Name: Название: - + &Port: &Порт: - + Player &name: &Имя пользователя: - + P&assword: П&ароль: - + &Save password &Сохранить пароль - + A&uto connect Присоединяться автоматически - + Automatically connect to the most recent login when Cockatrice opens Автоматически подключится с чаще запускаемым логином при открытии Кокатрис - - Public Servers - + + If you have any trouble connecting or registering then contact the server staff for help! + Если у вас есть любая проблема с присоединением или регистрацией - свяжитесь с сотрудниками для помощи! - - Forgot password + + + Webpage + Веб-страница + + + + Forgot Password Забыли пароль - - Connect - Соединение + + &Connect + &Подключиться - - Cancel - Отменить + + Server Contact + - + + Connect to Server + Подключиться к серверу + + + Server Сервер - + Login Логин - - Connect to server - Соединение - - - + Connection Warning Предупреждение при соединении - + You need to name your new connection profile. Назовите ваш профиль для соединения. - + Connect Warning Ошибка при соединении - + The player name can't be empty. Необходимо указать имя пользователя. + + + Downloading... + Загрузка... + DlgCreateGame @@ -858,7 +879,7 @@ This is only saved for moderators and cannot be seen by the banned person.DlgEditAvatar - + No image chosen. Изображение не выбрано. @@ -880,17 +901,17 @@ To remove your current avatar, confirm without choosing a new image. Сменить аватар - + Open Image Открыть изображение - + Image Files (*.png *.jpg *.bmp) Файлы изображений (*.png *.jpg *.bmp) - + Invalid image chosen. Выбрано не подходящее изображение. @@ -1370,12 +1391,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgSettings - + Unknown Error loading card database Неизвестная ошибка при загрузке базы карт. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1389,7 +1410,7 @@ Cockatrice не может корректно работать с испорче Вы хотите сменить расположение файла базы карт? - + Your card database version is too old. This can cause problems loading card information or images @@ -1403,7 +1424,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database did not finish loading Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1414,7 +1435,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + File Error loading your card database. Would you like to change your database location setting? @@ -1422,7 +1443,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1430,7 +1451,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Unknown card database load status Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues @@ -1441,63 +1462,81 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - - - + + + Error Ошибка - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ваши колоды отсутствуют в указанной папке. Вернуться и задать правильный путь? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Изображения карт не найдены. Вернуться и задать правильный путь? - + Settings Настройки - + General Основные - + Appearance Вид - + User Interface Интерфейс - + Deck Editor Редактор колод - + Chat Чат - + Sound Звук - + Shortcuts Горячие клавиши + + DlgTipOfTheDay + + + Next + + + + + Previous + + + + + Tip of the Day + + + DlgUpdate @@ -1834,32 +1873,32 @@ You may have to build from source yourself. Возраст - + Type Тип - + Description Описание - + Creator Создатель - + Restrictions Ограничения - + Players Количество игроков - + Spectators Зрители @@ -1867,202 +1906,189 @@ You may have to build from source yourself. GeneralSettingsPage - - - - - + + + + + Choose path Путь - + Success Выполнено - + Downloaded card pictures have been reset. Загруженные картинки были обновлены - + Error Ошибка - + One or more downloaded card pictures could not be cleared. Одно или более загруженных изображений карт не может быть удалено. - + Personal settings Настройки пользователя - + Language: Язык: - + Download card pictures on the fly Загружать изображения карт автоматически - + Paths (editing disabled in portable mode) - + Paths Пути расположения - + Decks directory: Колоды: - + Replays directory: Повторы: - + Pictures directory: Изображения карт: - + Card database: База данных карт: - + Token database: База данных токенов: - + Picture cache size: Размер кеша изображений: - + Primary download URL: Основной путь загрузки: - + Fallback download URL: Запасной путь загрузки: - + How to set a custom picture url Как установить пользовательский путь загрузки - + Reset/clear downloaded pictures Обновить/удалить загруженные картинки - + Update channel Обновить канал - + Notify if a feature supported by the server is missing in my client Предупреждать, если функции, используемые сервером, отсутствуют в этом клиенте. - - + + Reset Сбросить - - - Logger - - Client Operating System - - - - - Build Architecture - - - - - Qt Version + + Show tips on startup MainWindow - - + + The server has reached its maximum user capacity, please check back later. Этот сервер достиг максимального количества пользователей, попробуйте позже. - + There are too many concurrent connections from your address. Слишком много одновременных подключений с Вашего адреса. - + Banned by moderator Забанен модератором - + Expected end time: %1 Ожидаемое время: %1 - + This ban lasts indefinitely. Этот бан не ограничен по времени. - + Scheduled server shutdown. Плановый перерыв в работе сервера. - - + + Invalid username. Неверное имя пользователя. - + You have been logged out due to logging in at another location. Вы вышли из аккаунта, так как был выполнен вход из другого расположения. - + Connection closed Соединение прервано - + The server has terminated your connection. Reason: %1 Ваше подключение было прервано сервером. Причина: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2077,535 +2103,540 @@ Reason for shutdown: %1 Причина перезагрузки: %1 - + Scheduled server shutdown Плановый перерыв в работе сервера - - + + Success Успешно - + Registration accepted. Will now login. Регистрация прошла успешно. Идет соединение. - + Account activation accepted. Will now login. Активация аккаунта прошла успешно. Идет соединение. - + Number of players Количество игроков - + Please enter the number of players. Введите количество игроков. - - + + Player %1 Игрок %1 - + Load replay Загрузить повтор - + About Cockatrice О программе - + Cockatrice Webpage Страница Cockatrice - + Project Manager: Руководитель проекта: - + Past Project Managers: Предыдущие руководители проекта: - + Developers: Разработчики: - + Our Developers Наши разработчики - + Help Develop! Помочь в разработке! - + Translators: Переводчики: - + Help Translate! Помочь с переводом! - + Support: Поддержать проект: - + Report an Issue Сообщить о проблеме - + Troubleshooting Устранение неполадок - + F.A.Q. ЧАВо - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + + + + Error Ошибка - + Server timeout Нет связи с сервером - + Failed Login Не могу подключиться - + Incorrect username or password. Please check your authentication information and try again. Неверное имя пользователя или пароль. Пожалуйста, уточните регистрационные данные и попробуйте снова. - + There is already an active session using this user name. Please close that session first and re-login. Пользователь с таким именем уже подключен. Пожалуйста, закройте это подключение и войдите заново. - - + + You are banned until %1. Вы забанены до %1. - - + + You are banned indefinitely. Вы забанены бессрочно. - + This server requires user registration. Do you want to register now? На этом сервере требуется регистрация. Вы хотите зарегистрироваться сейчас? - + This server requires client ID's. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. На этом сервере используется ID клиентов. Ваш клиент не может сгенерировать ID, либо ваш клиент был модифицирован. Пожалуйста, перезапустите клиент и попробуйте снова. - + An internal error has occurred, please try closing and reopening your client and try again. If the error persists try updating your client to the most recent build and if need be contact your software provider. Возникла внутренняя ошибка. Пожалуйста, перезапустите клиент. Если ошибка повторится, попробуйте обновить клиент до последней версии или свяжитесь с разработчиком. - + Account activation Активация аккаунта - + Server Full Сервер заполнен - + Unknown login error: %1 Неизвестная ошибка входа: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Обычно это означает, что Вы используете устаревшую версию клиента, и тот не отвечает запросам сервера. - + Your username must respect these rules: Имя пользователя должно удовлетворять следующим условиям: - + is %1 - %2 characters long длиной в %1 - %2 символов - + can %1 contain lowercase characters %1 может содержать строчные буквы - - - + + + NOT НЕ - + can %1 contain uppercase characters %1 может содержать заглавные буквы - + can %1 contain numeric characters %1 может содержать цифры - + can contain the following punctuation: %1 Может содержать следующие символы: %1 - + first character can %1 be a punctuation mark Первый знак %1 может быть знаком пунктуации - + can not contain any of the following words: %1 не может содержать следующие слова: %1 - + can not match any of the following expressions: %1 не может соответствовать следующим выражениям: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Имя пользователя может включать только следующие символы: A-Z, a-z, 0-9, "_", "." и "-" - - - - - - + + + + + + Registration denied Запрос на регистрацию отклонен - + Registration is currently disabled on this server Регистрация на данном сервере в настоящий момент недоступна - + There is already an existing account with the same user name. Аккаунт с таким именем уже существует. - + It's mandatory to specify a valid email address when registering. При регистрации необходимо указать действующий e-mail. - + Your client seems to be missing features this server requires for connection. Ваш клиент не поддерживает некоторые функции, необходимые для соединения с сервером. - + Version - + Our Translators Наши переводчики - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Ваш аккаунт ещё не активирован. Вам нужно предоставить жетон/код активации, полученный по электронной почте. - + The email address provider used during registration has been blacklisted for use on this server. Домен электронной почты, использованной при регистрации, занесён в чёрный список на этом сервере. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. Слишком короткий пароль. - + Registration failed for a technical problem on the server. Регистрация не удалась из-за технических неполадок на сервере. - + Unknown registration error: %1 Неизвестная ошибка при регистрации: %1 - + Account activation failed Не удалось активировать аккаунт - + Socket error: %1 Ошибка сокета: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Вы пытаетесь подключиться к несуществующему серверу. Пожалуйста, обновите Cockatrice или выберите другой сервер. Локальная версия %1, удаленная версия %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Ваш клиент Cockatrice устарел. Пожалуйста, обновите Cockatrice. Локальная версия %1, удаленная версия %2. - + Connecting to %1... Подключение к %1... - + Registering to %1 as %2... Регистрация на %1 как %2... - + Disconnected Подключение прервано - + Connected, logging in at %1 Соединение установлено, выполняется вход на %1 - - - + + + Requesting forgot password to %1 as %2... Запрашиваем восстановление пароля у %1 от %2... - + &Connect... Подключение... &С - + &Disconnect П&рервать подключение - + Start &local game... &Начать локальную игру... - + &Watch replay... &Смотреть повтор... - + &Deck editor Редактор &колод - + &Full screen Полный экран &F - + &Register to server... &Регистрация на сервере... - + &Settings... &Настройки - - + + &Exit &Выход - + A&ctions &Действия - + &Cockatrice &Cockatrice - + C&ard Database &База карт - + Open custom image folder Открыть папку с изображениями - + Open custom sets folder Открыть папку с изданиями - + Add custom sets/cards Добавить издания/карты - + Edit &tokens... Редактировать &фишки - + &About Cockatrice О про&грамме - + + &Tip of the Day + + + + Check for Client Updates - + View &debug log Просмотреть &журнал отладки - + &Help &Справка - + Check for card updates... Проверить обновления базы карт... - + Card database База карт - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice не может загрузить базу карт. Обновить вашу базу карт сейчас? Если Вы не уверены, или впервые запустили программу, нажмите "Да" - - + + Yes Да - - + + No Нет - + Open settings Открыть настройки - + New sets found Найдены новые издания - + %1 new set(s) found in the card database Set code(s): %2 Do you want to enable it/them? @@ -2614,124 +2645,124 @@ Do you want to enable it/them? Включить их отображение? - + View sets Просмотреть издания - + Welcome Добро пожаловать - - - + + + Information Информация - + A card database update is already running. Обновление базы карт уже идет. - + Unable to run the card database updater: Не удалось запустить обновление базы карт: - + failed to start. ошибка при запуске. - + crashed. прервано. - + timed out. превышен лимит ожидания. - + write error. ошибка записи. - + read error. ошибка чтения. - + unknown error. неизвестная ошибка. - + The card database updater exited with an error: %1 Обновление базы карт прервано с ошибкой: %1 - + Update completed successfully. Cockatrice will now reload the card database. Обновление успешно завершено. Сейчас Cockatrice перезагрузит базу карт. - + You can only import XML databases at this time. На данный момент вы только можете предостовлять базы данных в формате XML. - - - + + + Forgot Password Забыли пароль - + Your password has been reset successfully, you now may log in using the new credentials. Смена вашего пароля прошла успешно, теперь вы можете войти используя новую информацию. - + Failed to reset user account password, please contact the server operator to reset your password. Невозможно сбросить пароль. Пожалуйста, свяжитесь с оператором сервера. - + Activation request received, please check your email for an activation token. Запрос активации получен. Вам отправлен код активации. Пожалуйста, проверьте свой e-mail. - - - - + + + + Load sets/cards Загрузить издания/карты - + &Manage sets... - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -2742,19 +2773,19 @@ To update your client, go to Help -> Check for Updates. Чтобы обновить клиент, перейдите в Справка-> Проверка обновлений. - + Selected file cannot be found. Выбранный файл не найден. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Новые издания/карты успешно добавлены. Сейчас Cockatrice перезагрузит базу карт. - + Sets/cards failed to import. Не удалось импортировать издания/карты. @@ -2877,6 +2908,16 @@ Cockatrice will now reload the card database. %1 plays %2%3. %1 разыгрывает %2%3. + + + %1 turns %2 face-down. + + + + + %1 turns %2 face-up. + + %1 has left the game (%2). @@ -3132,16 +3173,6 @@ Cockatrice will now reload the card database. %1 draws their initial hand. %1 берет свои стартовые руки. - - - %1 flips %2 face-down. - %1 переворачивает %2 лицом вниз. - - - - %1 flips %2 face-up. - %1 переворачивает %2 лицом вверх. - %1 destroys %2. @@ -3311,79 +3342,79 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Add message Добавить сообщение - + Message: Сообщение: - + Chat settings Настройки чата - + Custom alert words Пользовательские предупреждения - + Enable chat mentions Включить упоминания в чате - + Enable mention completer Включить автозавершение упоминаний - + In-game message macros Макросы сообщений игрового чата - + Ignore chat room messages sent by unregistered users Не уведомлять о сообщениях в чате от незарегистрированных пользователей - + Ignore private messages sent by unregistered users Не уведомлять о личных сообщениях от незарегистрированных пользователей - - + + Invert text color Инвертировать цвет текста - + Enable desktop notifications for private messages Включить оповещения о личных сообщениях - + Enable desktop notification for mentions Включить оповещения об упоминаниях - + Enable room message history on join Отображать историю сообщений при присоединении к комнате. - - + + (Color is hexadecimal) (Шестнадцатеричный цвет) - + Separate words with a space, alphanumeric characters only Разделять слова пробелом, только для букв и цифр @@ -3449,478 +3480,485 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library Показать верхние карты библиотеки - + Number of cards: (max. %1) Количество карт: (макс. %1) - + &View graveyard &Посмотреть кладбище - + &View exile По&смотреть карты в изгнании - + Player "%1" Игрок "%1" - - - - + + + + &Graveyard &Кладбище - - - - + + + + &Exile &Изгнание - + &Move hand to... &Поместить руку.. - - - - - - &Top of library - &на верх библиотеки - - + + &Top of library + &на верх библиотеки + + + + + + &Bottom of library &вниз библиотеки - + &Move graveyard to... &Поместить карты кладбища.. - - - - + + + + &Hand Р&ука - + &Move exile to... &Поместить карты из изгнания.. - + &View library Просмортеть &библиотеку - + View &top cards of library... Посмтореть верхние карт&ы... - + Reveal &library to... Показать &библиотеку.. - + Reveal t&op cards to... Показать &верхние карты.. - + &Always reveal top card &Всегда показывать верхнюю карту - + O&pen deck in deck editor &Открыть колоду в редакторе - + &View sideboard Просмотреть &сайд - + &Draw card В&зять карту - + D&raw cards... Вз&ять карты... - + &Undo last draw &Отменить последнее взятие - + Take &mulligan Взять стра&ховку - + &Shuffle Переме&шать - + Play top card &face down Разыграть верхнюю карту &рубашкой вверх - + Move top cards to &graveyard... Поместить верхние карты на кладби&ще... - + Move top cards to &exile... Поместить верхние карты в и&згнание... - + Put top card on &bottom Поместить верхн&юю карту на дно - + Put bottom card &in graveyard Поместить нижнюю карту &на кладбище - + &Reveal hand to... Показать руку игроку.. &R - + Reveal r&andom card to... Показать случайную карту игроку... - + Reveal random card to... - + &Sideboard &Сайд - + &Library &Библиотека - + &Counters &Жетоны - + &Untap all permanents &Развернуть все перманенты - + R&oll die... Бросить &кубик... - + &Create token... Создать &фишку... - + C&reate another token Создать &еще одну фишку - + Cr&eate predefined token Создать готовый токен - + S&ay Ска&зать - + C&ard Ка&рта - + &All players &Все игроки - + &Play Разыграть - + &Hide Скрыть - + Play &Face Down Разыграть рубашкой вверх - + Toggle &normal untapping Переключить &разворот как обычно - - &Flip - &Перевернуть - - - + &Peek at card face Посмотреть &лицевую сторону - + &Clone &Копировать - + Attac&h to card... При&соединить - + Unattac&h &Открепить - + &Draw arrow... &Нарисовать стрелку - + &Increase power Увеличить силу - + &Decrease power Уменьшить силу - + I&ncrease toughness Увеличить защиту - + D&ecrease toughness Уменьшить защиту - + In&crease power and toughness У&величить силу и защиту - + Dec&rease power and toughness У&меньшить силу и защиту - + Set &power and toughness... Установить силу и защиту - + &Set annotation... Добавить пометку - + Red Красный - + Yellow Желтый - + Green Зеленый - + X cards from the top of library... - - + + C&reate another %1 token - + Create tokens - - - - - - + + + + + + Token: Код: - + Place card X cards from top of library - + How many cards from the top of the deck should this card be placed: - - + + View related cards + + + + + Attach to - + All tokens Все фишки - + View top cards of library Просмотр верхних карт - + &Tap / Untap + Turn sideways or back again - + + T&urn Over + Turn face up/face down + + + + &Add counter (%1) &Добавить жетон (%1) - + &Remove counter (%1) &Удалить жетон (%1) - + &Set counters (%1)... &Установить жетоны (%1)... - + Number of cards: Количество: - + Draw cards Взять карты - - - - - + + + + + Number: Количество: - + Move top cards to grave Поместить верхние карты на кладбище - + Move top cards to exile Поместить верхние карты в изгнание - + Roll die Бросить кубик - + Number of sides: Количество граней: - + Set power/toughness Установить Силу/Защиту - + Please enter the new PT: Введите новые Силу/Защиту: - + Set annotation Пометка - + Please enter the new annotation: Введите текст: - + Set counters Установить жетоны @@ -3928,37 +3966,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services Сервисы - + Hide %1 Скрыть %1 - + Hide Others Скрыть остальные - + Show All Показать все - + Preferences... Параметры... - + Quit %1 Выйти %1 - + About %1 О %1 @@ -3966,18 +4004,18 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) База карт Cockatrice (*.xml) - + All files (*.*) Все файлы (*.*) - + Cockatrice replays (*.cor) Записи игр Cockatrice (*.cor) @@ -4094,10 +4132,15 @@ Cockatrice will now reload the card database. SequenceEdit - + Shortcut already in use Сочетание клавиш уже используется + + + Invalid key + + SetsModel @@ -4127,6 +4170,21 @@ Cockatrice will now reload the card database. Дата релиза + + ShortcutsSettings + + + Your configuration file contained invalid shortcuts. +Please check your shortcut settings! + + + + + The following shortcuts have been set to default: + + + + ShortcutsTab @@ -4140,12 +4198,12 @@ Cockatrice will now reload the card database. Вы действительно хотите восстановить все сочетания клавиш по умолчанию? - + Clear all default shortcuts Очистить все сочетания клавиш по умолчанию - + Do you really want to clear all shortcuts? Вы действительно хотите очистить все сочетания клавиш? @@ -4171,27 +4229,27 @@ Cockatrice will now reload the card database. SoundSettingsPage - + Enable &sounds Включить &звуки - + Current sounds theme: Звуковая тема: - + Test system sound engine Протестировать системный звук - + Sound settings Настройки звука - + Master volume Громкости @@ -4199,48 +4257,48 @@ Cockatrice will now reload the card database. SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -4249,8 +4307,8 @@ Cockatrice will now reload the card database. StableReleaseChannel - Stable releases - Стабильные версии + Stable Releases + @@ -4314,233 +4372,248 @@ Cockatrice will now reload the card database. TabDeckEditor - + &Clear all filters Очистить все фильтры - + Delete selected Удалить выбранное - + Deck &name: Имя колоды: - + &Comments: Комментарии: - + Hash: Хэш: - + &New deck Новая колода - + &Load deck... Загрузить колоду... - + &Save deck Сохранить колоду - + Save deck &as... Сохранить колоду как... - + Load deck from cl&ipboard... Загрузить колоду из буфера... - + &Print deck... Распечатать колоду... - + Search by card name - + + Add to Deck + + + + + Add to Sideboard + + + + + Show Related cards + + + + Save deck to clipboard - + Annotated - + Not Annotated - + &Send deck to online service - + Create decklist (decklist.org) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close Закрыть - + Add card to &maindeck Добавить карту в основную колоду - + Add card to &sideboard Добавить карту в сайдборд - + &Remove row Удалить строку - + &Increment number Увеличить количество - + &Decrement number Уменьшить количество - + &Deck Editor Редактор колод - - + + Card Info Информация о карте - - + + Deck Колода - - + + Filters Фильтры - + &View Вид - - - + + + Visible Видимый - - - + + + Floating Плавающий - + Reset layout Сбросить слой - + Deck: %1 Колода: %1 - + Are you sure? Вы уверены? - + The decklist has been modified. Do you want to save the changes? Деклист был изменен. Вы хотите сохранить изменения? - + Load deck Загрузить колоду - - - - - + + + + + Error Ошибка - + The deck could not be saved. Колода не может быть сохранена. - - + + The deck could not be saved. Please check that the directory is writable and try again. Колода не может быть сохранена. Пожалуйста, проверьте расположение файла и попробуйте снова. - + Save deck Сохранить колоду - + There are no cards in your deck to be exported - + No deck was selected to be saved. Для сохранения не выбрано ни одной колоды @@ -4638,175 +4711,175 @@ Please enter a name: TabGame - - - + + + Replay Запись игры - - + + Game Игра - - + + Card Info Информация о карте - - + + Player List Список игроков - - + + Messages Сообщения - - + + Replay Timeline Полоса прокрутки записи - + &Phases &Фазы - + &Game &Игра - + Next &phase Следующая &фаза - + Next &turn Следующий &ход - + &Remove all local arrows &Удалить все указатели - + Rotate View Cl&ockwise Повернуть вид по часовой стрелке - + Rotate View Co&unterclockwise Повернуть вид против часовой стрелки - + Game &information Информация &об игре - + &Concede &Сдаться... - + &Leave game Покинуть и&гру - + C&lose replay З&акрыть повтор - + &Say: Ска&зать: - + &View Вид - - - + + + Visible Видимый - - - + + + Floating Плавающий - + Reset layout Сбросить слой - + Concede Сдаться - + Are you sure you want to concede this game? Вы точно хотите сдаться? - + Leave game Покинуть игру - + Are you sure you want to leave this game? Вы уверены, что хотите уйти? - + You are flooding the game. Please wait a couple of seconds. Кажется, Вы нафлудили. Пожалуйста, подождите пару секунд. - + kicked by game host or moderator - + player left the game игрок покинул игру - + player disconnected from server игрок отсоединился от сервера - + reason unknown причина неизвестна - + You have been kicked out of the game. Вы были отключены от игры @@ -5165,22 +5238,22 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Are you sure? Вы уверены? - + There are still open games. Are you sure you want to quit? У вас все еще есть активные игры. Уверены, что хотите выйти? - + Unknown Event Неопознанное событие - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5191,38 +5264,38 @@ To update your client, go to Help -> Check for Updates. Чтобы обновить клиент, перейдите в Справка-> Проверка обновлений. - + Idle Timeout Исчерпано время бездействия - + You are about to be logged out due to inactivity. Вы были отключены из-за отсутствия активности. - + Promotion Повышение - + You have been promoted to moderator. Please log out and back in for changes to take effect. Вас повысили до модератора. Пожалуйста, перезайдите в свою учетную запись, чтобы изменения вступили в силу. - + Warned Предупрежден - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Вы получили предупреждение по причине %1. Пожалуйста, воздержитесь от подобных действий, иначе в ваш адрес будут применены дальнейшие меры. При возникновении каких-либо вопросов напишите личное сообщение любому модератору. - + You have received the following message from the server. (custom messages like these could be untranslated) Вы получили сообщение от сервера. @@ -5261,6 +5334,21 @@ Please refrain from engaging in this activity or further actions may be taken ag Невозможно проанализировать колоду + + TipsOfTheDay + + + File does not exist. + + + + + + Failed to open file. + + + + UpdateDownloader @@ -5585,42 +5673,42 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Основные настройки интерфейса - + Enable notifications in taskbar Включить оповещения в панели задач. - + Notify in the taskbar for game events while you are spectating Уведомлять об игровых событиях в панели задач, когда вы наблюдаете за игрой - + &Double-click cards to play them (instead of single-click) &Двойной клик, чтобы разыграть карту (вместо одинарного) - + &Play all nonlands onto the stack (not the battlefield) by default &Помещать все заклинания в стек при разыгрывании (вместо поля боя) - + Annotate card text on tokens Изменить текст карты на токенах - + Animation settings Настройки анимации - + &Tap/untap animation &Анимировать поворот/разворот карты @@ -5690,97 +5778,127 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Переместить выбранное издание наверх - + Move selected set up Переместить выбранное издание вверх - + Move selected set down Переместить выбранное издание вниз - + Move selected set to the bottom Переместить выбранное издание в конец. - - Enable all sets - Включить все издания - - - - Disable all sets - Отключить все издания - - - - Enable selected set(s) - Включить выбранные издания - - - - Disable selected set(s) - Отключить выбранные издания - - - - Deck Editor - Редактор колод - - - - Only cards in enabled sets will appear in the deck editor card list - Только карты включенных изданий появятся в редакторе колод - - - - Card Art + + Search by set name, code, or type - - Image priority is decided in the following order + + Default order - - The - - - - - CUSTOM Folder - - - - - Enabled Sets (Top to Bottom) + + Restore original art priority order + Enable all sets + Включить все издания + + + + Disable all sets + Отключить все издания + + + + Enable selected set(s) + Включить выбранные издания + + + + Disable selected set(s) + Отключить выбранные издания + + + + Deck Editor + Редактор колод + + + + Only cards in enabled sets will appear in the deck editor card list + Только карты включенных изданий появятся в редакторе колод + + + + Card Art + + + + + Image priority is decided in the following order + + + + + The + + + + + CUSTOM Folder + + + + + Enabled Sets (Top to Bottom) + + + + Disabled Sets (Top to Bottom) - + + Warning: + + + + + While the set list is sorted by any of the columns, custom art priority setting is disabled. + + + + + To disable sorting click on the same column header again until this message disappears. + + + + Manage sets - + Success Выполнено - + The sets database has been saved successfully. База данных изданий успешно сохранена. @@ -5811,7 +5929,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Русский (Russian) @@ -5884,97 +6002,113 @@ Please refrain from engaging in this activity or further actions may be taken ag Анализировать колоду - + Load deck (clipboard) Загрузить колоду (буфер) - + Clear all filters Отключить все фильтры - + New deck Новая колода - + Clear selected filter Отключить выбранный фильтр - + Open custom pic folder Открыть папку авторских картинок - + Close Закрыть - + Print deck Распечатать колоду - + Delete card Удалить карту - + Edit tokens Изменить фишки - + Reset layout Переназначить вид - + Add card Добавить карту - + Save deck Сохранить колоду - + Remove card Удалить карту - + Save deck as Сохранить колоду как - + Load deck Загрузить колоду - - + + Counters Жетоны - + Life Жизни - + + + + + + + + + + + + + + Set + Установить С/В + + - + @@ -5982,16 +6116,15 @@ Please refrain from engaging in this activity or further actions may be taken ag - - Set - Установить С/В + + Add + Добавить - - + @@ -5999,455 +6132,440 @@ Please refrain from engaging in this activity or further actions may be taken ag - Add - Добавить - - - - - - - - - - - - - - + Remove Удалить - + Red Красный - + Green Зеленый - + Yellow Желтый - + Storm - + W Белый - + U Синий - + B Черный - + R Красный - + G Зеленый - + X - + Main Window | Deck Editor Главное окно | Редактор - + Power / Toughness Сила/Выносливость - + Power and Toughness Сила и Выносливость - + Add (+1/+1) Добавить (+1/+1) - + Remove (-1/-1) Уменьшить (-1/-1) - + Toughness Выносливость - + Remove (-0/-1) Уменьшить (-0/-1) - + Add (+0/+1) Добавить (+0/+1) - + Power Сила - + Remove (-1/-0) Уменьшить (-1/-0) - + Add (+1/+0) Добавить (+1/+0) - + Game Phases Игровые фазы - + Untap Шаг разворота - + Upkeep Шаг поддержки - - + + Draw Шаг взятия карты - + Main 1 Первая основная фаза - + Start combat Шаг начала боя - + Attack Атака - + Block Блок - + Damage Повреждения - + End combat Конец боя - + Main 2 Вторая основная фаза - + End Конец хода - + Next phase Следующая фаза - + Next turn Предыдущая фаза - + Playing Area Игровое поле - + Manage sets - + Export deck Экспортировать колоду - + Save deck (clip) - + Save deck (clip; no annotations) - + Tap / Untap Card Повернуть / развернуть карту - + Untap all Развернуть все - + Toggle untap Переключить разворот - + Flip card Перевернуть карту - + Peek card Посмотреть карту - + Play card Разыграть карту - + Attach card Прикрепить карту - + Unattach card Открепить карту - + Clone card Копировать карту - + Create token Создать фишку - + Create all related tokens Создать все связанные фишки - + Create another token Создать другую фишку - + Set annotation Добавить пометку - + Phases | P/T | Playing Area Фазы | Сила/Защита | Игровое поле - + Move card to Переместить карту в - + Bottom library Низ библиотеки - + Top library Верх библиотеки - - + + Graveyard Кладбище - - + + Exile Изгнание - + Hand Рука - + View Вид - + Library Библиотека - + Tops card of library Верхние карты библиотеки - + Sideboard Сайдборд - + Close recent view Закрыть предыдущий вид - + Game Lobby Вкладка игры - + Load remote deck Загрузить колоду с сервера - + Load local deck Загрузить &колоду с диска - + Gameplay Процесс игры - + Draw arrow Нарисовать стрелку - + Leave game Покинуть игру - + Remove local arrows Удалить все стрелки - + Concede Сдаться - + Roll dice Бросить кубик - + Rotate view CW Повернуть вид по часовой стрелке - + Shuffle library Перемешать библиотеку - + Rotate view CCW Повернуть вид против часовой стрелки - + Mulligan Муллиган - + Draw card Взять карту - + Draw cards Взять карты - + Undo draw Отменить взятие карты - + Always reveal top card Держать открытой верхнюю карту - + Draw | Move | View | Gameplay Нарисовать | Переместить | Вид | Процесс игры - + How to set custom shortcuts Как установитьт пользовательские сочетания клавиш - + Restore all default shortcuts Восстановить сочетания клавиш по умолчанию - + Clear all shortcuts Очистить все сочетания клавиш diff --git a/cockatrice/translations/cockatrice_sv.ts b/cockatrice/translations/cockatrice_sv.ts index 710be8fce..5773df2b7 100644 --- a/cockatrice/translations/cockatrice_sv.ts +++ b/cockatrice/translations/cockatrice_sv.ts @@ -20,64 +20,64 @@ AppearanceSettingsPage - + Theme settings - + Temainställningar - + Current theme: - + Aktuellt tema: - + Card rendering Kortrendering - + Display card names on cards having a picture Visa kortnamn på kort som har bilder - + Scale cards on mouse over - + Skala kort på musen över - + Hand layout Handlayout - + Display hand horizontally (wastes space) Visa hand horisontellt (slösar plats) - + Enable left justification - + Aktivera vänsterjustering - + Table grid layout Bordets rutnätlayout - + Invert vertical coordinate Invertera vertikal koordinat - + Minimum player count for multi-column layout: Minst antal spelare för multi-kolumnlayout: - + Maximum font size for information displayed on cards: - + Maximal teckenstorlek för information som visas på kort: @@ -95,7 +95,7 @@ ban client I&D - + förbjuda klienten I & D @@ -170,23 +170,46 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + Du måste välja en namnbaserad, IP-baserad, clientId-baserad eller en kombination av de tre för att lägga ett förbud. You must have a value in the name ban when selecting the name ban checkbox. - + Du måste ha ett värde i namnförbudet när du markerar kryssrutan för namnförbud. You must have a value in the ip ban when selecting the ip ban checkbox. - + Du måste ha ett värde i ip-förbudet när du markerar kryssrutan ip-banan. You must have a value in the clientid ban when selecting the clientid ban checkbox. + Du måste ha ett värde i clientidförbudet när du markerar kryssrutan för klientidförbud. + + + + BetaReleaseChannel + + + Beta Releases + + + No reply received from the release update server. + Inget svar mottaget från uppdateringsservern. + + + + Invalid reply received from the release update server. + Ogiltigt svar mottaget från uppdateringsserveren. + + + + No reply received from the file update server. + Inget svar mottaget från filuppdateringsservern. + CardDatabaseModel @@ -242,32 +265,37 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardInfoText - + + Unknown card: + Okänt kort: + + + Name: Namn: - + Mana cost: Manakostnad: - + Color(s): Färg(er): - + Card type: Korttyp: - + P / T: P / T: - + Loyalty: Lojalitet: @@ -387,7 +415,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. their sideboard look at zone - + deras skänk @@ -399,7 +427,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. their sideboard nominative - + deras skänk @@ -411,71 +439,71 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorSettingsPage - - + + Update Spoilers - + Uppdatera Spoilers - - Updating Spoilers - + + Updating... + Uppdatering ... - + Choose path - + Välj sökväg - + Spoilers - + Download Spoilers Automatically - + Ladda ner Spoilers automatiskt - + Spoiler Location: - + Spoiler Plats: - + Hey, something's here finally! - + Hej, något är här äntligen! - + Last Updated - + Senast uppdaterad - + Spoilers download automatically on launch - + Spoilers laddas ner automatiskt vid lanseringen - + Press the button to manually update without relaunching - + Tryck på knappen för att manuellt uppdatera utan omstart - + Do not close settings until manual update complete - + Stäng inte inställningarna förrän manuell uppdatering är klar DeckListModel - + Number Nummer - + Card Kort @@ -499,12 +527,12 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. Load deck... - + Lastdäck ... Load remote deck... - + Ladda fjärrdäck ... @@ -537,136 +565,129 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. Den valda filen kunde ej laddas. - - DevReleaseChannel - - - Development snapshots - - - - - No reply received from the release update server. - - - - - Invalid reply received from the release update server. - - - - - No reply received from the file update server. - - - DlgConnect - + New Host Ny värd - + &Host: &Värd: - + Known Hosts - + Kända värdar - Name: - + Refresh the server list with known public servers + Uppdatera servern listan med kända offentliga servrar - + + Name: + Namn: + + + &Port: &Port: - + Player &name: Spelar&namn: - + P&assword: &Lösenord: - + &Save password &Spara lösenord - + A&uto connect - + Automatically connect to the most recent login when Cockatrice opens - - - - - Public Servers - - - - - Forgot password - + Koppla automatiskt till den senaste inloggningen när Cockatrice öppnas - Connect - + If you have any trouble connecting or registering then contact the server staff for help! + Om du har problem med att ansluta eller registrera kontaktar du serverns personal för hjälp! - - Cancel - Avbryt + + + Webpage + Webbsida - + + Forgot Password + Glömt ditt lösenord + + + + &Connect + &Ansluta + + + + Server Contact + Serverkontakt + + + + Connect to Server + Anslut till servern + + + Server - + Login - + Logga in - - Connect to server - Anslut till server - - - + Connection Warning - + Anslutning Varning - + You need to name your new connection profile. - + Du måste ange din nya anslutningsprofil. - + Connect Warning Uppkopplingsvarning - + The player name can't be empty. Fältet för spelarnamn kan inte lämnas tomt. + + + Downloading... + Hämtar ... + DlgCreateGame @@ -858,7 +879,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DlgEditAvatar - + No image chosen. Ingen bild vald @@ -880,17 +901,17 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl Ändra avatar - + Open Image Öppna bild - + Image Files (*.png *.jpg *.bmp) Bildfiler (*.png *.jpg *.bmp) - + Invalid image chosen. Ogiltigt val av bild. @@ -1020,7 +1041,8 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. - + Det valda namnet står i konflikt med ett befintligt kort eller token. +Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättningar" för att visa dem korrekt. @@ -1101,7 +1123,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Restrictions - + begränsningar @@ -1115,22 +1137,22 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Forgot Password Challenge Warning - + Glömt lösenord Utmaning Varning Oops, looks like something has gone wrong. Please restart the forgot password process by using the forgot password button on the connection screen. - + Oj, ser ut som att något har gått fel. Vänligen starta om glömt lösenordsprocessen med hjälp av knappen Glömt lösenord på anslutningsskärmen. &Host: - + &Värd: &Port: - + &Hamn: @@ -1140,17 +1162,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Email: - + E-post: Forgot Password Challenge - + Glömt lösenordsutmaning The email address can't be empty. - + E-postadressen kan inte vara tom. @@ -1158,12 +1180,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia &Host: - + &Värd: &Port: - + &Hamn: @@ -1173,17 +1195,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Forgot Password Request - + Glömt lösenordsförfrågan Forgot Password Request Warning - + Glömt lösenordsförfrågan Varning The player name can't be empty. - + Spelarens namn kan inte vara tomt. @@ -1195,22 +1217,22 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Forgot Password Reset Warning - + Glömt lösenord Reset Warning Opps, looks like something has gone wrong. Please re-start the forgot password process by using the forgot password button on the connection screen. - + Opps, ser ut som något har gått fel. Vänligen starta glömt lösenordsprocessen med hjälp av knappen Glömt lösenord på anslutningsskärmen. &Host: - + &Värd: &Port: - + &Hamn: @@ -1220,38 +1242,38 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Token: - + Tecken: New Password: - + Nytt lösenord: Forgot Password Reset - + Glömt lösenordsåterställning The player name can't be empty. - + Spelarens namn kan inte vara tomt. The token can't be empty. - + Token kan inte vara tomt. The new password can't be empty. - + Det nya lösenordet kan inte vara tomt. The passwords do not match. - + Lösenorden stämmer inte överens. @@ -1370,12 +1392,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgSettings - + Unknown Error loading card database Ett okänt fel har inträffat vid laddande av kortdatabasen - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1392,7 +1414,7 @@ Möjligen kan du behöva starta om oracle för att uppdatera din kortdatabas. Vill du ändra dina databaslokaliseringsinställningar? - + Your card database version is too old. This can cause problems loading card information or images @@ -1409,7 +1431,7 @@ Detta kan oftast fixas genom att starta om oracle för att uppdatera din kortdat Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database did not finish loading Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1422,7 +1444,7 @@ Var god skicka en hjälpförfrågan på http://github.com/Cockatrice/Cockatrice/ Vill du ändra dina kortdatabaslokaliseringsinställningar? - + File Error loading your card database. Would you like to change your database location setting? @@ -1431,7 +1453,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1440,7 +1462,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Unknown card database load status Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues @@ -1453,63 +1475,81 @@ Var god skicka en hjälpförfrågan på http://github.com/Cockatrice/Cockatrice/ Vill du ändra dina kortdatabaslokaliseringsinställningar? - - - + + + Error Fel - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Sökvägen till din lekkatalog är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Sökvägen till din kortbildsdatabas är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + Settings Inställningar - + General Allmänt - + Appearance Utseende - + User Interface Gränssnitt - + Deck Editor Leklådan - + Chat Chatt - + Sound Ljud - + Shortcuts Genvägar + + DlgTipOfTheDay + + + Next + Nästa + + + + Previous + Tidigare + + + + Tip of the Day + Dagens Tips + + DlgUpdate @@ -1523,17 +1563,17 @@ Vill du ändra dina kortdatabaslokaliseringsinställningar? Current release channel - + Nuvarande släppkanal Reinstall - + Installera Cancel Download - + Avbryt Ladda ner @@ -1544,108 +1584,110 @@ Vill du ändra dina kortdatabaslokaliseringsinställningar? Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Cockatrice byggdes inte med SSL-stöd, därför kan du inte ladda ner uppdateringar automatiskt! +Besök sidan för nedladdning för att uppdatera manuellt. Finished checking for updates - + Slutfört efter uppdateringar No Update Available - + Ingen uppdatering tillgänglig Cockatrice is up to date! - + Cockatris är aktuell! You are already running the latest version available in the chosen release channel. - + Du kör redan den senaste versionen i den valda utgåvan. Current version - + Aktuell version Selected release channel - + Vald utgivningskanal Update Available - + Uppdatering tillgänglig A new version of Cockatrice is available! - + En ny version av Cockatrice är tillgänglig! New version - + Ny version Released - + Släppte Changelog - + Ändringslogg Do you want to update now? - + Vill du uppdatera nu? Unfortunately there are no download packages available for your operating system. You may have to build from source yourself. - + Tyvärr finns inga nedladdningspaket tillgängliga för ditt operativsystem. +Du kanske måste bygga från källan själv. Please check the download page manually and visit the wiki for instructions on compiling. - + Vänligen kontrollera nedladdningssidan manuellt och besök wiki för instruktioner om sammanställning. An error occurred while checking for updates: - + Ett fel uppstod när du letade efter uppdateringar: An error occurred while downloading an update: - + Ett fel uppstod när du hämtade en uppdatering: Cockatrice is unable to open the installer. - + Cockatrice kan inte öppna installatören. Try to update manually by closing Cockatrice and running the installer. - + Försök att uppdatera manuellt genom att stänga Cockatrice och köra installationsprogrammet. Download location - + Hämta plats @@ -1675,12 +1717,12 @@ You may have to build from source yourself. Clear log when closing - + Tydlig logg vid stängning Debug Log - + Felsökningslogg @@ -1688,7 +1730,7 @@ You may have to build from source yourself. Type your filter here - + Skriv ditt filter här @@ -1797,7 +1839,7 @@ You may have to build from source yourself. New - + Ny @@ -1843,35 +1885,35 @@ You may have to build from source yourself. Age - + Ålder - + Type - + Typ - + Description Beskrivning - + Creator Skapare - + Restrictions Begränsningar - + Players Spelare - + Spectators Åskådare @@ -1879,202 +1921,189 @@ You may have to build from source yourself. GeneralSettingsPage - - - - - + + + + + Choose path Välj sökväg - + Success Framgång - + Downloaded card pictures have been reset. De nedladdade kortbilderna har återställts. - + Error Fel - + One or more downloaded card pictures could not be cleared. En eller fler nedladdade kortbilder kunde inte rensas. - + Personal settings Personliga inställningar - + Language: Språk: - + Download card pictures on the fly Ladda ner kortbilder på direkten - + Paths (editing disabled in portable mode) - + Banor (redigering inaktiverad i bärbart läge) - + Paths Sökvägar - + Decks directory: Lekkatalog: - + Replays directory: Repriskatalog: - + Pictures directory: Bildkatalog: - + Card database: Kortdatabas: - + Token database: Token-databas: - + Picture cache size: Storlek på bild-cache: - + Primary download URL: Primär nedladdnings-URL: - + Fallback download URL: Reservnedladdnings-URL: - + How to set a custom picture url Hur man väljer en egen bild-url - - Reset/clear downloaded pictures - - - - - Update channel - - - - - Notify if a feature supported by the server is missing in my client - - - - + Reset/clear downloaded pictures + Återställ / ta bort nedladdade bilder + + + + Update channel + Uppdatera kanal + + + + Notify if a feature supported by the server is missing in my client + Meddela om en funktion som stöds av servern saknas i min klient + + + + Reset Återställ - - - Logger - - Client Operating System - - - - - Build Architecture - - - - - Qt Version - + + Show tips on startup + Visa tips vid uppstart MainWindow - - + + The server has reached its maximum user capacity, please check back later. Servern har nått sin maximala kapacitet för användare, var god försök igen vid ett senare tillfälle. - + There are too many concurrent connections from your address. Din adress har för många uppkopplingar samtidigt. - + Banned by moderator Bannlyst av moderator - + Expected end time: %1 Förväntad sluttid: %1 - + This ban lasts indefinitely. Denna bannlysning varar för evigt. - + Scheduled server shutdown. Schemalagd serverstängning. - - + + Invalid username. Ogiltigt användarnamn. - + You have been logged out due to logging in at another location. Du loggades ut på grund av att du loggade in via en annan plats. - + Connection closed Uppkoppling avslutad - + The server has terminated your connection. Reason: %1 Servern har avslutat din uppkoppling. Anledning: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2085,656 +2114,661 @@ Alla pågående spel kommer att gå förlorade. Anledning till nedstängning: %1 - + Scheduled server shutdown Schemalagd serverstängning - - + + Success Framgång - + Registration accepted. Will now login. Registrering lyckad. Inloggning påbörjad. - + Account activation accepted. Will now login. Aktivering tillåten. Inloggning påbörjad. - + Number of players Antal spelare - + Please enter the number of players. Vänligen ange antal spelare. - - + + Player %1 Spelare %1 - + Load replay Ladda repris - + About Cockatrice Om Cockatrice - + Cockatrice Webpage - - - Project Manager: - - - - - Past Project Managers: - - - - - Developers: - - - Our Developers - + Project Manager: + Projektledare: + Past Project Managers: + Tidigare projektledare: + + + + Developers: + utvecklare: + + + + Our Developers + Våra utvecklare + + + Help Develop! - + Translators: Översättare: - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + + + + Error Fel - + Server timeout Server timeout - + Failed Login - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. Det finns redan en aktiv session med det användarnamnet. Vänligen stäng den sessionen först och försök igen. - - + + You are banned until %1. Du är bannlyst till %1. - - + + You are banned indefinitely. Du är bannlyst för evigt. - + This server requires user registration. Do you want to register now? - + This server requires client ID's. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please try closing and reopening your client and try again. If the error persists try updating your client to the most recent build and if need be contact your software provider. - + Account activation - + Server Full - + Unknown login error: %1 Okänt inloggningsfel: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + Your client seems to be missing features this server requires for connection. - + Version - + Our Translators - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + The email address provider used during registration has been blacklisted for use on this server. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 Socketfel: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Du försöker koppla upp dig till en föråldrad server. Vänligen nergradera din version av Cockatrice eller koppla upp dig till en lämplig server. Lokal version är %1, avlägsen version är %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Din version av Cockatrice är föråldrad. Vänligen uppdatera din version av Cockatrice. Lokal version är %1, avlägsen version är %2. - + Connecting to %1... Ansluter till %1... - + Registering to %1 as %2... - + Disconnected Frånkopplad - + Connected, logging in at %1 Uppkopplad, loggar in hos %1 - - - + + + Requesting forgot password to %1 as %2... - + &Connect... &Anslut... - + &Disconnect &Frånkoppla - + Start &local game... Starta &lokalt spel... - + &Watch replay... &Titta på repris... - + &Deck editor Lek&redigeraren - + &Full screen &Fullskärmsläge - + &Register to server... - + &Settings... &Inställningar... - - + + &Exit A&vsluta - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Edit &tokens... - + &About Cockatrice &Om Cockatrice - + + &Tip of the Day + + + + Check for Client Updates - + View &debug log - + &Help &Hjälp - + Check for card updates... - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %1 new set(s) found in the card database Set code(s): %2 Do you want to enable it/them? - + View sets - + Welcome - - - + + + Information - + A card database update is already running. - + Unable to run the card database updater: - + failed to start. - + crashed. - + timed out. - + write error. - + read error. - + unknown error. - + The card database updater exited with an error: %1 - + Update completed successfully. Cockatrice will now reload the card database. - + You can only import XML databases at this time. - - - + + + Forgot Password - + Your password has been reset successfully, you now may log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - - - - + + + + Load sets/cards - + &Manage sets... - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -2742,18 +2776,18 @@ To update your client, go to Help -> Check for Updates. - + Selected file cannot be found. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. @@ -2876,6 +2910,16 @@ Cockatrice will now reload the card database. %1 plays %2%3. %1 spelar %2%3. + + + %1 turns %2 face-down. + + + + + %1 turns %2 face-up. + + %1 has left the game (%2). @@ -3131,16 +3175,6 @@ Cockatrice will now reload the card database. %1 draws their initial hand. - - - %1 flips %2 face-down. - - - - - %1 flips %2 face-up. - - %1 destroys %2. @@ -3310,79 +3344,79 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Add message Lägg till meddelande - + Message: Meddelande: - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3448,478 +3482,485 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - + Number of cards: (max. %1) - + &View graveyard &Titta på kyrkogård - + &View exile &Titta på exil - + Player "%1" Spelare "%1" - - - - + + + + &Graveyard &Kyrkogård - - - - + + + + &Exile &Exil - - - &Move hand to... - - - - - - &Top of library + &Move hand to... - + + &Top of library + + + + + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand &Hand - + &Move exile to... - + &View library &Titta på leken - + View &top cards of library... Titta på de &översta korten i leken... - + Reveal &library to... - + Reveal t&op cards to... - + &Always reveal top card &Avslöja alltid det översta kortet - + O&pen deck in deck editor &Öppna lek i lekredigeraren - + &View sideboard &Titta på sidbrädan - + &Draw card &Dra kort - + D&raw cards... D&ra kort... - + &Undo last draw &Ångra senaste drag - + Take &mulligan &Mulligan - + &Shuffle &Blanda - + Play top card &face down - + Move top cards to &graveyard... Flytta översta korten till &kyrkogården... - + Move top cards to &exile... Flytta översta korten till &exil... - + Put top card on &bottom Placera översta kortet &underst - + Put bottom card &in graveyard - + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard S&idbräda - + &Library &Lek - + &Counters &Poletter - + &Untap all permanents Tappa upp alla perma&nenta kort - + R&oll die... Rulla t&ärning... - + &Create token... &Skapa jetong... - + C&reate another token S&kapa en till jetong - + Cr&eate predefined token Skapa fördefinierad &jetong - + S&ay S&äg - + C&ard K&ort - + &All players A&lla spelare - + &Play &Spela - + &Hide &Göm - + Play &Face Down - + Toggle &normal untapping Växla &normal upptappning - - &Flip - &Vänd - - - + &Peek at card face Kika på kort&framsida - + &Clone &Klona - + Attac&h to card... &Fäst på kort... - + Unattac&h Se&parera - + &Draw arrow... &Rita pil... - + &Increase power &Öka power - + &Decrease power &Minska power - + I&ncrease toughness Öka toug&hness - + D&ecrease toughness Minska t&oughness - + In&crease power and toughness Öka power o&ch toughness - + Dec&rease power and toughness M&inska power och toughness - + Set &power and toughness... An&ge power och toughness... - + &Set annotation... Ang&e annotering... - + Red - + Yellow - + Green - + X cards from the top of library... - - + + C&reate another %1 token - + Create tokens - - - - - - + + + + + + Token: - + Place card X cards from top of library - + How many cards from the top of the deck should this card be placed: - - + + View related cards + + + + + Attach to - + All tokens - + View top cards of library Titta på de översta korten i leken - + &Tap / Untap + Turn sideways or back again - + + T&urn Over + Turn face up/face down + + + + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + Number of cards: Antal kort: - + Draw cards Dra kort - - - - - + + + + + Number: Antal: - + Move top cards to grave Flytta översta korten till kyrkogården - + Move top cards to exile Flytta översta korten till exil - + Roll die Rulla tärning - + Number of sides: Antal sidor: - + Set power/toughness Ange power/toughness - + Please enter the new PT: Vänligen ange ny PT: - + Set annotation Ange annotering - + Please enter the new annotation: Vänligen ange den nya annoteringen: - + Set counters Placera poletter @@ -3927,37 +3968,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -3965,18 +4006,18 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) Alla filer (*.*) - + Cockatrice replays (*.cor) Cockatricerepriser (*.cor) @@ -4093,10 +4134,15 @@ Cockatrice will now reload the card database. SequenceEdit - + Shortcut already in use + + + Invalid key + + SetsModel @@ -4126,6 +4172,21 @@ Cockatrice will now reload the card database. + + ShortcutsSettings + + + Your configuration file contained invalid shortcuts. +Please check your shortcut settings! + + + + + The following shortcuts have been set to default: + + + + ShortcutsTab @@ -4139,12 +4200,12 @@ Cockatrice will now reload the card database. - + Clear all default shortcuts - + Do you really want to clear all shortcuts? @@ -4170,27 +4231,27 @@ Cockatrice will now reload the card database. SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -4198,48 +4259,48 @@ Cockatrice will now reload the card database. SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -4248,7 +4309,7 @@ Cockatrice will now reload the card database. StableReleaseChannel - Stable releases + Stable Releases @@ -4313,232 +4374,247 @@ Cockatrice will now reload the card database. TabDeckEditor - + &Clear all filters - + Delete selected - + Deck &name: &Leknamn: - + &Comments: &Kommentarer: - + Hash: Hash: - + &New deck &Ny lek - + &Load deck... &Ladda lek... - + &Save deck S&para lek - + Save deck &as... Spa&ra lek som... - + Load deck from cl&ipboard... Ladda lek &från urklipp... - + &Print deck... Skri&v ut lek... - + Search by card name - + + Add to Deck + + + + + Add to Sideboard + + + + + Show Related cards + + + + Save deck to clipboard - + Annotated - + Not Annotated - + &Send deck to online service - + Create decklist (decklist.org) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close S&täng - + Add card to &maindeck Lägg till kort till &huvudlek - + Add card to &sideboard Lägg till kort i sidbr&äda - + &Remove row Ta bort ra&d - + &Increment number &Öka antal - + &Decrement number &Minska antal - + &Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - - - + + + Visible - - - + + + Floating - + Reset layout - + Deck: %1 Lek: %1 - + Are you sure? Är du säker? - + The decklist has been modified. Do you want to save the changes? Denna leklista har modifierats. Vill du spara ändringarna? - + Load deck Ladda lek - - - - - + + + + + Error Fel - + The deck could not be saved. Leken kunde inte sparas. - - + + The deck could not be saved. Please check that the directory is writable and try again. Leken kunde inte sparas. Vänligen se till att katalogen är skrivbar och försök igen. - + Save deck Spara lek - + There are no cards in your deck to be exported - + No deck was selected to be saved. @@ -4635,175 +4711,175 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Card Info - - + + Player List - - + + Messages - - + + Replay Timeline - + &Phases &Faser - + &Game &Spel - + Next &phase Nästa &fas - + Next &turn Nästa &tur - + &Remove all local arrows Ta &bort alla lokala pilar - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information Spel&information - + &Concede &Ge upp - + &Leave game &Lämna spel - + C&lose replay S&täng repris - + &Say: S&äg: - + &View - - - + + + Visible - - - + + + Floating - + Reset layout - + Concede Ge upp - + Are you sure you want to concede this game? Är du säker på att du vill ge upp detta spel? - + Leave game Lämna spel - + Are you sure you want to leave this game? Är du säker på att du vill lämna detta spel? - + You are flooding the game. Please wait a couple of seconds. - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5161,22 +5237,22 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Are you sure? - + There are still open games. Are you sure you want to quit? - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5184,38 +5260,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted to moderator. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -5253,6 +5329,21 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TipsOfTheDay + + + File does not exist. + + + + + + Failed to open file. + + + + UpdateDownloader @@ -5577,42 +5668,42 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Allmänna gränssnittsinställningar - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + &Double-click cards to play them (instead of single-click) &Dubbelklicka på kort för att spela dem (istället för enkelklick) - + &Play all nonlands onto the stack (not the battlefield) by default - + Annotate card text on tokens - + Animation settings Animationsinställningar - + &Tap/untap animation &Tappnings/Upptappningsanimation @@ -5682,97 +5773,127 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - - Enable all sets + + Search by set name, code, or type - - Disable all sets + + Default order - - Enable selected set(s) - - - - - Disable selected set(s) - - - - - Deck Editor - - - - - Only cards in enabled sets will appear in the deck editor card list - - - - - Card Art - - - - - Image priority is decided in the following order - - - - - The - - - - - CUSTOM Folder - - - - - Enabled Sets (Top to Bottom) + + Restore original art priority order + Enable all sets + + + + + Disable all sets + + + + + Enable selected set(s) + + + + + Disable selected set(s) + + + + + Deck Editor + + + + + Only cards in enabled sets will appear in the deck editor card list + + + + + Card Art + + + + + Image priority is decided in the following order + + + + + The + + + + + CUSTOM Folder + + + + + Enabled Sets (Top to Bottom) + + + + Disabled Sets (Top to Bottom) - + + Warning: + + + + + While the set list is sorted by any of the columns, custom art priority setting is disabled. + + + + + To disable sorting click on the same column header again until this message disappears. + + + + Manage sets - + Success - + The sets database has been saved successfully. @@ -5803,7 +5924,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Svenska (Swedish) @@ -5876,97 +5997,113 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Load deck (clipboard) - + Clear all filters - + New deck - + Clear selected filter - + Open custom pic folder - + Close - + Print deck - + Delete card - + Edit tokens - + Reset layout - + Add card - + Save deck - + Remove card - + Save deck as - + Load deck - - + + Counters - + Life - + + + + + + + + + + + + + + Set + + + - + @@ -5974,16 +6111,15 @@ Please refrain from engaging in this activity or further actions may be taken ag - - Set + + Add - - + @@ -5991,457 +6127,442 @@ Please refrain from engaging in this activity or further actions may be taken ag - Add - - - - - - - - - - - - - - - + Remove - + Red - + Green - + Yellow - + Storm - + W - + U - + B - + R - + G - + X - + Main Window | Deck Editor - + Power / Toughness - + Power and Toughness - + Add (+1/+1) - + Remove (-1/-1) - + Toughness - + Remove (-0/-1) - + Add (+0/+1) - + Power - + Remove (-1/-0) - + Add (+1/+0) - + Game Phases - + Untap - + Upkeep - - + + Draw - + Main 1 - + Start combat - + Attack - + Block - + Damage - + End combat - + Main 2 - + End - + Next phase - + Next turn - + Playing Area - + Manage sets - + Export deck - + Save deck (clip) - + Save deck (clip; no annotations) - + Tap / Untap Card - + Untap all - + Toggle untap - + Flip card - - - Peek card - - - - - Play card - - - - - Attach card - - - - - Unattach card - - - Clone card - + Peek card + Peek-kort - Create token - + Play card + Spelkort - Create all related tokens - + Attach card + Fäst kort - Create another token - + Unattach card + Unattach-kortet - Set annotation - + Clone card + Klon kort + Create token + Skapa token + + + + Create all related tokens + Skapa alla relaterade tokens + + + + Create another token + Skapa en annan token + + + + Set annotation + Ange anteckningar + + + Phases | P/T | Playing Area - + Move card to - + Flytta kortet till - + Bottom library - + Top library - + Top bibliotek - - + + Graveyard - + Kyrkogård - - + + Exile - + Exil - + Hand - - - View - - - - - Library - - - - - Tops card of library - - - - - Sideboard - - - Close recent view - + View + Se - + + Library + Bibliotek + + + + Tops card of library + Tops kort av biblioteket + + + + Sideboard + Skänk + + + + Close recent view + Stäng ny vy nyligen + + + Game Lobby - - - Load remote deck - - - - - Load local deck - - - - - Gameplay - - - - - Draw arrow - - - - - Leave game - - - - - Remove local arrows - - - Concede - + Load remote deck + Ladda fjärrdäck - Roll dice - + Load local deck + Ladda lokala däck + Gameplay + Gameplay + + + + Draw arrow + Rita pilen + + + + Leave game + Lämna spelet + + + + Remove local arrows + Ta bort lokala pilar + + + + Concede + Medge + + + + Roll dice + Kasta tärning + + + Rotate view CW - + Shuffle library - + Blanda biblioteket - + Rotate view CCW - + Mulligan - - - Draw card - - - - - Draw cards - - - - - Undo draw - - - - - Always reveal top card - - - - - Draw | Move | View | Gameplay - - - How to set custom shortcuts - + Draw card + Rita kort - Restore all default shortcuts - + Draw cards + Rita kort + Undo draw + Ångra rita + + + + Always reveal top card + Alltid avslöja bästa kort + + + + Draw | Move | View | Gameplay + Rita | Flytta | Visa | gameplay + + + + How to set custom shortcuts + Så här ställer du in anpassade genvägar + + + + Restore all default shortcuts + Återställ alla standardgenvägar + + + Clear all shortcuts - + Rensa alla genvägar \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_zh-Hans.ts b/cockatrice/translations/cockatrice_zh-Hans.ts index b7b114940..d9bb8b940 100644 --- a/cockatrice/translations/cockatrice_zh-Hans.ts +++ b/cockatrice/translations/cockatrice_zh-Hans.ts @@ -20,62 +20,62 @@ AppearanceSettingsPage - + Theme settings 主题设置 - + Current theme: 当前主题 - + Card rendering 牌面 - + Display card names on cards having a picture 显示有图卡牌的名称 - + Scale cards on mouse over 卡牌随指针缩放 - + Hand layout 手牌区域布局 - + Display hand horizontally (wastes space) 水平显示手牌区域 (浪费空间) - + Enable left justification 开启左对齐 - + Table grid layout 表格布局 - + Invert vertical coordinate 反转垂直坐标 - + Minimum player count for multi-column layout: 界面布局之内能够容纳的牌手栏数量: - + Maximum font size for information displayed on cards: 卡牌上显示的最大字号 @@ -188,6 +188,29 @@ This is only saved for moderators and cannot be seen by the banned person.在禁用客户端ID时必须填写一个客户端ID。 + + BetaReleaseChannel + + + Beta Releases + 测试版本 + + + + No reply received from the release update server. + 未收到更新服务器的响应。 + + + + Invalid reply received from the release update server. + 收到更新服务器的无效响应 + + + + No reply received from the file update server. + 未收到文件更新服务器的响应 + + CardDatabaseModel @@ -242,32 +265,37 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoText - + + Unknown card: + 未知牌: + + + Name: 名称: - + Mana cost: 法术力费用: - + Color(s): 颜色 - + Card type: 卡牌类别: - + P / T: 力量/防御力: - + Loyalty: 忠诚值: @@ -411,58 +439,58 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers 更新预览 - - Updating Spoilers - 更新预览中 + + Updating... + 更新中... - + Choose path 选择路径 - + Spoilers 偷跑 - + Download Spoilers Automatically 自动下载预览 - + Spoiler Location: Spoiler位置: - + Hey, something's here finally! 嘿,终于有东西来了! - + Last Updated 最后更新 - + Spoilers download automatically on launch 自动下载Spoilers运行 - + Press the button to manually update without relaunching 按下按钮手动更新而不重新启动 - + Do not close settings until manual update complete 手动更新完成之前,请勿关闭设置 @@ -470,12 +498,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Number 数量 - + Card 卡牌 @@ -537,136 +565,129 @@ This is only saved for moderators and cannot be seen by the banned person.此文件无法被载入 - - DevReleaseChannel - - - Development snapshots - 开发快照 - - - - No reply received from the release update server. - 未收到更新服务器的响应。 - - - - Invalid reply received from the release update server. - 收到更新服务器的无效响应。 - - - - No reply received from the file update server. - 未收到文件更新服务器的响应。 - - DlgConnect - + New Host 新主机 - + &Host: 主机: - + Known Hosts 已知主机: + Refresh the server list with known public servers + 从已知的服务器更新服务器列表 + + + Name: 名称: - + &Port: 端口: - + Player &name: 玩家名字: - + P&assword: 密码: - + &Save password 记住密码 - + A&uto connect 自动连接 - + Automatically connect to the most recent login when Cockatrice opens 当Cockatrice启动时自动连接到最近的登录 - - Public Servers - 公共服务器 + + If you have any trouble connecting or registering then contact the server staff for help! + 如果你在连接或者注册上遇到了任何麻烦,可以联系服务器的工作人员获得帮助。 - - Forgot password + + + Webpage + 网页 + + + + Forgot Password 忘记密码 - - Connect + + &Connect 连接 - - Cancel - 取消 + + Server Contact + 联系服务器 - + + Connect to Server + 连接服务器 + + + Server 服务器 - + Login 登录 - - Connect to server - 连接服务器 - - - + Connection Warning 连接警告 - + You need to name your new connection profile. 你需要给连接配置文件命名。 - + Connect Warning 连接警告 - + The player name can't be empty. 玩家名称不能为空 + + + Downloading... + 下载中... + DlgCreateGame @@ -858,7 +879,7 @@ This is only saved for moderators and cannot be seen by the banned person.DlgEditAvatar - + No image chosen. 未选择图像 @@ -880,17 +901,17 @@ To remove your current avatar, confirm without choosing a new image. 改变头像 - + Open Image 打开图片 - + Image Files (*.png *.jpg *.bmp) 图片文件(*.png *.jpg *.bmp格式) - + Invalid image chosen. 所选图片不可用。 @@ -1371,12 +1392,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgSettings - + Unknown Error loading card database 读取卡牌数据库时出现未知错误 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1393,7 +1414,7 @@ Would you like to change your database location setting? 您想要重新设置卡牌数据库路径么? - + Your card database version is too old. This can cause problems loading card information or images @@ -1410,7 +1431,7 @@ Would you like to change your database location setting? 您想要重新设置卡牌数据库路径么? - + Your card database did not finish loading Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1423,7 +1444,7 @@ Would you like to change your database location setting? 您想要重新设置卡牌数据库路径么? - + File Error loading your card database. Would you like to change your database location setting? @@ -1432,7 +1453,7 @@ Would you like to change your database location setting? 您想要重新设置卡牌数据库路径么? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1441,7 +1462,7 @@ Would you like to change your database location setting? 您想要重新设置卡牌数据库路径么? - + Unknown card database load status Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues @@ -1452,63 +1473,81 @@ Would you like to change your database location setting? 您想要重新设置卡牌数据库路径么? - - - + + + Error 错误 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 您的套牌路径无效。您想要重新设置正确的路径么? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 您想要重新设置卡牌数据库路径么? - + Settings 设置 - + General 常规 - + Appearance 外观 - + User Interface 用户界面 - + Deck Editor 套牌编辑 - + Chat 聊天 - + Sound 声音 - + Shortcuts 快捷键 + + DlgTipOfTheDay + + + Next + 下一个 + + + + Previous + 已知 + + + + Tip of the Day + 每日提示 + + DlgUpdate @@ -1847,32 +1886,32 @@ You may have to build from source yourself. 时长 - + Type 类型 - + Description 描述 - + Creator 创建者 - + Restrictions 限制 - + Players 玩家 - + Spectators 观看者 @@ -1880,202 +1919,189 @@ You may have to build from source yourself. GeneralSettingsPage - - - - - + + + + + Choose path 选择路径 - + Success 成功 - + Downloaded card pictures have been reset. 下载的卡牌图片已被重置。 - + Error 错误 - + One or more downloaded card pictures could not be cleared. 1个或多个卡牌图片未能被清除。 - + Personal settings 个人设置 - + Language: 语言: - + Download card pictures on the fly 下载卡牌上的图片 - + Paths (editing disabled in portable mode) 路径(在便携模式下禁止编辑) - + Paths 路径 - + Decks directory: 套牌路径: - + Replays directory: 游戏录像目录: - + Pictures directory: 图片目录: - + Card database: 卡牌数据库: - + Token database: 衍生物数据库: - + Picture cache size: 图片缓存大小: - + Primary download URL: 首选下载链接: - + Fallback download URL: 备用下载链接: - + How to set a custom picture url 如何设置自定义图片链接 - + Reset/clear downloaded pictures 重置/清除 已下载的图片 - + Update channel 更新通道 - + Notify if a feature supported by the server is missing in my client 当客户端缺少服务器支持的特性时提示我 - - + + Reset 重置 - - - Logger - - Client Operating System - 客户端操作系统 - - - - Build Architecture - 构建架构 - - - - Qt Version - QT版本 + + Show tips on startup + 显示启动时提示 MainWindow - - + + The server has reached its maximum user capacity, please check back later. 服务器已达到最大用户数,请稍微再试。 - + There are too many concurrent connections from your address. 你的地址有太多连接. - + Banned by moderator 已被版主禁止 - + Expected end time: %1 预计结束时间: %1 - + This ban lasts indefinitely. 这个封禁是永久的. - + Scheduled server shutdown. 预定服务器关闭. - - + + Invalid username. 不可用的用户名。 - + You have been logged out due to logging in at another location. 由于在其他地点登陆,您已被登出。 - + Connection closed 连接关闭 - + The server has terminated your connection. Reason: %1 服务器中断连接. 原因: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2084,507 +2110,512 @@ Reason for shutdown: %1 关闭原因: %1 - + Scheduled server shutdown 预定服务器关闭 - - + + Success 成功 - + Registration accepted. Will now login. 注册已成功。 现在登陆。 - + Account activation accepted. Will now login. 账号已激活。 现在登陆 - + Number of players 玩家人数 - + Please enter the number of players. 请输入玩家的数量. - - + + Player %1 玩家 %1 - + Load replay 载入游戏录像 - + About Cockatrice 关于Cockatrice鸡蛇 - + Cockatrice Webpage Cockatrice鸡蛇网站 - + Project Manager: 项目经理: - + Past Project Managers: 前项目经理: - + Developers: 开发者: - + Our Developers 我们的开发者: - + Help Develop! 协助开发! - + Translators: 翻译者: - + Help Translate! 协助翻译! - + Support: 支持: - + Report an Issue 报告错误 - + Troubleshooting 排除故障 - + F.A.Q. 问答 - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + + + + Error 错误 - + Server timeout 服务器超时 - + Failed Login 登录失败 - + Incorrect username or password. Please check your authentication information and try again. 用户名或密码错误。请检查账号信息并重试。 - + There is already an active session using this user name. Please close that session first and re-login. 已经有一个在线用户正在使用这个用户名. 首先请关闭这个对话框然后重新登陆. - - + + You are banned until %1. 你被禁止直到: %1. - - + + You are banned indefinitely. 你被永久封禁. - + This server requires user registration. Do you want to register now? 服务器需要注册。现在注册吗? - + This server requires client ID's. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. 服务器需要客户端ID。您的客户端未能生成ID或您正在运行修改过的客户端。 请关闭并重新打开客户端。 - + An internal error has occurred, please try closing and reopening your client and try again. If the error persists try updating your client to the most recent build and if need be contact your software provider. 出现内部错误,请关闭并重启客户端再尝试。如果错误仍然出现请将您的客户端升级到最新版本,如有需要请联系您的软件提供商。 - + Account activation 账号激活 - + Server Full 服务器已满 - + Unknown login error: %1 未知的登陆错误: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. 这通常表示您的客户端已经过于陈旧,未能响应服务器提出的请求。 - + Your username must respect these rules: 您的用户名须遵守以下规则: - + is %1 - %2 characters long 在%1 - %2个字符之间。 - + can %1 contain lowercase characters 能包含 %1 个小写字母 - - - + + + NOT - + can %1 contain uppercase characters 能包含%1个小写字母 - + can %1 contain numeric characters 能包含%1个数字 - + can contain the following punctuation: %1 能包含以下符号:%1 - + first character can %1 be a punctuation mark 第一个字符不能为%1 标点符号 - + can not contain any of the following words: %1 不能包含以下词:%1 - + can not match any of the following expressions: %1 不能包含以下语句:%1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. 您可以使用A-Z,a-z,0-9,_,.以及-作为用户名。 - - - - - - + + + + + + Registration denied 注册失败 - + Registration is currently disabled on this server 当前服务器无法注册 - + There is already an existing account with the same user name. 当前用户名已经被使用。 - + It's mandatory to specify a valid email address when registering. 注册时必须提供一个有效的电子邮箱。 - + Your client seems to be missing features this server requires for connection. 你的客户端缺少连接这个服务器所需要的要素 - + Version 版本 - + Our Translators 翻译者名单 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. 你的账号还未激活。 请提供激活邮件中的验证码。 - + The email address provider used during registration has been blacklisted for use on this server. 注册时使用的电子邮箱的提供方已被此服务器列入黑名单 - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. 看起来你正试图在这个服务器上注册一个新账户,但是你已经有了一个用提供的邮箱注册的账户。 此服务器限制每个邮箱地址可以注册用户帐户的数量。请联系服务器运营商以获得进一步帮助或获取您的凭据信息。 - + Password too short. 密码太短。 - + Registration failed for a technical problem on the server. 由于服务器出现技术问题,注册失败。 - + Unknown registration error: %1 未知的注册错误:%1 - + Account activation failed 账号激活失败 - + Socket error: %1 接口错误: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. 你正在连接到一个过时的服务器,请下调你的版本或者连接到一个匹配的服务器. 你当前的版本 %1, 服务器版本 %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. 你使用的客户端意见过时,请使用更新你版本. 你当前的版本 %1, 服务器版本 %2. - + Connecting to %1... 连接到 %1... - + Registering to %1 as %2... 正在将 %2 注册到 %1 - + Disconnected 断开连接 - + Connected, logging in at %1 连接登陆到 %1 - - - + + + Requesting forgot password to %1 as %2... 正在将%2向%1申请忘记密码流程。 - + &Connect... 连接... - + &Disconnect 断开连接 - + Start &local game... 开始本地游戏... - + &Watch replay... 观看录像... - + &Deck editor 套牌编辑 - + &Full screen 全屏 - + &Register to server... 注册到服务器 - + &Settings... 设置... - - + + &Exit 退出 - + A&ctions 动作 - + &Cockatrice Cockatrice鸡蛇 - + C&ard Database 卡牌数据库 - + Open custom image folder 打开自定义图片文件夹 - + Open custom sets folder 打开自定义系列文件夹 - + Add custom sets/cards 添加自定义系列/卡牌 - + Edit &tokens... 编辑衍生物 - + &About Cockatrice 关于Cockatrice鸡蛇 - + + &Tip of the Day + &每日提示 + + + Check for Client Updates 检查客户端更新 - + View &debug log 查看错误日志 - + &Help 帮助 - + Check for card updates... 检查卡牌更新 - + Card database 卡牌数据库 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -2593,29 +2624,29 @@ If unsure or first time user, choose "Yes" 如果不确定或者您是第一次使用,请选择“是” - - + + Yes - - + + No - + Open settings 打开设置 - + New sets found 发现新系列 - + %1 new set(s) found in the card database Set code(s): %2 Do you want to enable it/them? @@ -2624,117 +2655,117 @@ Do you want to enable it/them? 要将它们生效吗? - + View sets 查看系列 - + Welcome 欢迎 - - - + + + Information 信息 - + A card database update is already running. 数据库更新已经在运行中。 - + Unable to run the card database updater: 无法运行卡组数据库更新器: - + failed to start. 无法开始。 - + crashed. 程序崩溃。 - + timed out. 超时 - + write error. 写入错误。 - + read error. 读取错误。 - + unknown error. 未知错误 - + The card database updater exited with an error: %1 卡牌数据库更新器退出,错误:%1 - + Update completed successfully. Cockatrice will now reload the card database. 更新已完毕。 Cockatrice鸡蛇现在会重新载入卡组数据库。 - + You can only import XML databases at this time. 当前只能导入XML格式数据库。 - - - + + + Forgot Password 忘记密码 - + Your password has been reset successfully, you now may log in using the new credentials. 你的密码已重置,现在你可以使用新密码登录了。 - + Failed to reset user account password, please contact the server operator to reset your password. 重置密码失败,请联系服务器管理员重置密码。 - + Activation request received, please check your email for an activation token. 激活请求已收到,请查看你的电子邮箱获取激活验证码。 - - - - + + + + Load sets/cards 载入系列/卡牌 - + &Manage sets... 系列管理 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -2743,7 +2774,7 @@ Read more about changing the set order or disabling specific sets and consequent 了解更多用“系列管理”窗口更改系列顺序或者禁用某系列的信息。 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -2753,19 +2784,19 @@ To update your client, go to Help -> Check for Updates. 要升级客户端,请点击 帮助->检查更新 - + Selected file cannot be found. 找不到选择的文件。 - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 新的系列/卡牌已添加成功。 Cockatrice鸡蛇现在会重新载入卡组数据库。 - + Sets/cards failed to import. 系列/卡牌导入失败。 @@ -2888,6 +2919,16 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 %1 plays %2%3. %1使用 %2%3。 + + + %1 turns %2 face-down. + %1回合%2翻为面朝下。 + + + + %1 turns %2 face-up. + %1回合%2翻为面朝上。 + %1 has left the game (%2). @@ -3143,16 +3184,6 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 %1 draws their initial hand. %1 抓了起始手牌 - - - %1 flips %2 face-down. - %1将%2翻为面朝下。 - - - - %1 flips %2 face-up. - %1将%2翻为面朝下。 - %1 destroys %2. @@ -3322,79 +3353,79 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 MessagesSettingsPage - + Add message 添加信息 - + Message: 信息: - + Chat settings 聊天设置 - + Custom alert words 自定义警告语 - + Enable chat mentions 允许聊天中提到某人 - + Enable mention completer 允许自动完成提名 - + In-game message macros 游戏内消息宏 - + Ignore chat room messages sent by unregistered users 忽略未注册用户的聊天室消息。 - + Ignore private messages sent by unregistered users 忽略未注册用户发出的私人消息 - - + + Invert text color 反转文本颜色 - + Enable desktop notifications for private messages 开启私人消息桌面提醒 - + Enable desktop notification for mentions 开启聊天提名桌面提醒 - + Enable room message history on join 开启加入聊天室时的历史消息 - - + + (Color is hexadecimal) (颜色为16进制) - + Separate words with a space, alphanumeric characters only 将单词以空格区分,仅支持字母 @@ -3460,478 +3491,485 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 Player - + Reveal top cards of library 查看牌库顶的牌 - + Number of cards: (max. %1) 卡牌数量:(最大%1) - + &View graveyard 查看坟墓场 - + &View exile 查看放逐区 - + Player "%1" 玩家 "%1" - - - - + + + + &Graveyard 坟墓场 - - - - + + + + &Exile 放逐区 - + &Move hand to... 将手牌移动到 - - - - - - &Top of library - 牌库顶 - - + + &Top of library + 牌库顶 + + + + + + &Bottom of library 牌库底 - + &Move graveyard to... 将坟墓场的牌移到 - - - - + + + + &Hand 手牌 - + &Move exile to... 将放逐区的卡移到 - + &View library 查看套牌 - + View &top cards of library... 检视牌库顶的牌 - + Reveal &library to... 将套牌展示给 - + Reveal t&op cards to... 将牌库顶牌展示给 - + &Always reveal top card 一直展示牌库顶牌 - + O&pen deck in deck editor 打开套牌编辑 - + &View sideboard 查看备牌 - + &Draw card 抓牌 - + D&raw cards... 抓多张牌... - + &Undo last draw 撤销最后抓牌 - + Take &mulligan 抓起始手牌 - + &Shuffle 切洗套牌 - + Play top card &face down 将牌库顶牌面朝下打出 - + Move top cards to &graveyard... 将牌库顶的牌置入坟墓场... - + Move top cards to &exile... 将牌库顶的牌置入放逐区... - + Put top card on &bottom 将牌库顶牌放到牌库底 - + Put bottom card &in graveyard 将牌库底牌置入坟墓场 - + &Reveal hand to... 将手牌展示给 - + Reveal r&andom card to... 展示随机卡牌到 - + Reveal random card to... 随机展示牌给... - + &Sideboard 备牌 - + &Library 牌库 - + &Counters 数值 - + &Untap all permanents 重置所有永久物 - + R&oll die... 抛骰子... - + &Create token... 创造一个衍生物... - + C&reate another token 将另一个衍生物放置进场 - + Cr&eate predefined token 将预设衍生物放置进场 - + S&ay - + C&ard 卡牌 - + &All players 全部玩家 - + &Play 开始 - + &Hide 隐藏 - + Play &Face Down 面朝下打出 - + Toggle &normal untapping 锁定通常重置 - - &Flip - 翻面 - - - + &Peek at card face 查看卡牌背面 - + &Clone 复制 - + Attac&h to card... 结附卡牌... - + Unattac&h 取消结附 - + &Draw arrow... 划箭头... - + &Increase power 增加力量 - + &Decrease power 降低防御 - + I&ncrease toughness 降低防御 - + D&ecrease toughness 降低防御 - + In&crease power and toughness 增加力量和防御 - + Dec&rease power and toughness 降低力量和防御 - + Set &power and toughness... 设置力量和防御... - + &Set annotation... 设置注释... - + Red - + Yellow - + Green 绿 - + X cards from the top of library... 从牌库顶X张牌... - - + + C&reate another %1 token 创造%1个衍生物 - + Create tokens 创造一个衍生物 - - - - - - + + + + + + Token: 衍生物: - + Place card X cards from top of library 查看牌库顶X张牌 - + How many cards from the top of the deck should this card be placed: 多少张牌应该放在牌库顶: - - + + View related cards + 查看关联牌 + + + + Attach to 结附于 - + All tokens 所有衍生物 - + View top cards of library 查看牌库顶的牌 - + &Tap / Untap + Turn sideways or back again 横置/重置 - + + T&urn Over + Turn face up/face down + 回合结束 + + + &Add counter (%1) 增加指示物 (%1) - + &Remove counter (%1) 移除指示物 (%1) - + &Set counters (%1)... 设置数值 (%1)... - + Number of cards: 卡牌数量: - + Draw cards 抓牌 - - - - - + + + + + Number: 数值: - + Move top cards to grave 将牌库顶的牌置入坟墓场 - + Move top cards to exile 将牌库顶的牌置入放逐区 - + Roll die 抛骰子 - + Number of sides: 面数: - + Set power/toughness 设置力量和防御 - + Please enter the new PT: 请输入力量和防御值: - + Set annotation 设置注释 - + Please enter the new annotation: 请输入注释: - + Set counters 设置数值 @@ -3939,37 +3977,37 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 QMenuBar - + Services 服务 - + Hide %1 隐藏 %1 - + Hide Others 隐藏其他 - + Show All 展示所有 - + Preferences... 参数 - + Quit %1 退出%1 - + About %1 关于 %1 @@ -3977,18 +4015,18 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 QObject - + Cockatrice card database (*.xml) Cockatrice鸡蛇卡牌数据库 (*.xml) - + All files (*.*) 全部文件 (*.*) - + Cockatrice replays (*.cor) 鸡蛇录像文件 (*.cor) @@ -4105,10 +4143,15 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 SequenceEdit - + Shortcut already in use 快捷键已被使用 + + + Invalid key + 无效密钥 + SetsModel @@ -4138,6 +4181,23 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 发布日期 + + ShortcutsSettings + + + Your configuration file contained invalid shortcuts. +Please check your shortcut settings! + 您的配置文件包涵了无效的快捷键 +请检查您的快捷键设置 + + + + The following shortcuts have been set to default: + + 以下快捷方式被设置为默认状态: + + + ShortcutsTab @@ -4151,12 +4211,12 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 您要恢复所有默认快捷键吗? - + Clear all default shortcuts 清除所有快捷键 - + Do you really want to clear all shortcuts? 您要清除所有快捷键吗? @@ -4182,27 +4242,27 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 SoundSettingsPage - + Enable &sounds 启用声效 - + Current sounds theme: 当前声效主题: - + Test system sound engine 测试系统声效 - + Sound settings 声效设置 - + Master volume 主音量 @@ -4210,48 +4270,48 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 SpoilerBackgroundUpdater - + Spoilers season has ended 偷跑时间已结束 - + Deleting spoiler.xml. Please run Oracle 删除spoiler.xml。请运行Oracle - - + + Spoilers download failed Spoilers下载错误 - + No internet connection 没有网络连接 - + Error 错误 - + Spoilers already up to date Spoilers更新已准备好 - + No new spoilers added 没有新的spoilers被添加 - + Spoilers have been updated! Spoilers已更新! - + Last change: 最后变更: @@ -4260,7 +4320,7 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 StableReleaseChannel - Stable releases + Stable Releases 稳定版本 @@ -4325,233 +4385,248 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 TabDeckEditor - + &Clear all filters 清除所有筛选项目 - + Delete selected 删除所选项 - + Deck &name: 套牌名称: - + &Comments: 评论: - + Hash: 哈希值: - + &New deck 创建新套牌 - + &Load deck... 读取套牌... - + &Save deck 保存套牌 - + Save deck &as... 另存为套牌... - + Load deck from cl&ipboard... 从剪贴板读取套牌... - + &Print deck... 打印套牌... - + Search by card name 按卡牌名称搜索 - + + Add to Deck + 添加至套牌 + + + + Add to Sideboard + 添加至备牌 + + + + Show Related cards + 显示关联牌 + + + Save deck to clipboard 保存卡组到剪切板 - + Annotated 注释 - + Not Annotated 没有注释 - + &Send deck to online service 发送卡组到在线服务器 - + Create decklist (decklist.org) 创建卡组(decklist.org) - + Analyze deck (deckstats.net) 分析卡组(deckstats.net) - + Analyze deck (tappedout.net) 分析卡组(tappedout.net) - + &Close 关闭 - + Add card to &maindeck 添加卡牌到主要套牌 - + Add card to &sideboard 添加卡牌到备牌 - + &Remove row 移除卡牌 - + &Increment number 添加卡牌张数 - + &Decrement number 减少卡牌张数 - + &Deck Editor 套牌编辑 - - + + Card Info 卡牌信息 - - + + Deck 套牌 - - + + Filters 过滤器 - + &View 视图 - - - + + + Visible 可见 - - - + + + Floating 移动窗口 - + Reset layout 重置界面 - + Deck: %1 套牌: %1 - + Are you sure? 你确定吗? - + The decklist has been modified. Do you want to save the changes? 套牌列表已被修改。 你想要保存更改吗? - + Load deck 读取套牌 - - - - - + + + + + Error 错误 - + The deck could not be saved. 未能保存套牌。 - - + + The deck could not be saved. Please check that the directory is writable and try again. 套牌未能保存. 请检查目录是否可用后再重试. - + Save deck 保存套牌 - + There are no cards in your deck to be exported 你的卡组中没有卡可以导出 - + No deck was selected to be saved. 没有选择卡组用于保存 @@ -4649,175 +4724,175 @@ Please enter a name: TabGame - - - + + + Replay 录像 - - + + Game 游戏 - - + + Card Info 卡牌信息 - - + + Player List 玩家列表 - - + + Messages 消息 - - + + Replay Timeline 录像时间线 - + &Phases 阶段 - + &Game 游戏 - + Next &phase 下个阶段 - + Next &turn 下个回合 - + &Remove all local arrows 重置所有箭头 - + Rotate View Cl&ockwise 顺时针旋转视角 - + Rotate View Co&unterclockwise 逆时针旋转视角 - + Game &information 游戏信息 - + &Concede 放弃游戏 - + &Leave game 离开游戏 - + C&lose replay 关闭游戏录像 - + &Say: 说: - + &View 查看 - - - + + + Visible 可见 - - - + + + Floating 移动窗口 - + Reset layout 重置界面 - + Concede 放弃游戏 - + Are you sure you want to concede this game? 你确定放弃这个游戏? - + Leave game 离开游戏 - + Are you sure you want to leave this game? 你确定离开这这个游戏? - + You are flooding the game. Please wait a couple of seconds. 您正在刷屏。请稍等几秒钟。 - + kicked by game host or moderator 被游戏主机或管理器踢出 - + player left the game 玩家离开游戏 - + player disconnected from server 玩家与服务器断开连接 - + reason unknown 原因不明 - + You have been kicked out of the game. 你已被踢出游戏。 @@ -5176,22 +5251,22 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Are you sure? 你确定吗? - + There are still open games. Are you sure you want to quit? 游戏还在继续, 你确定要退出吗? - + Unknown Event 未知事件 - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5202,39 +5277,39 @@ To update your client, go to Help -> Check for Updates. 要升级你的客户端,请点击“帮助”->“检查更新” - + Idle Timeout 闲置超时 - + You are about to be logged out due to inactivity. 你即将因为长时间未操作而被退出。 - + Promotion 晋升 - + You have been promoted to moderator. Please log out and back in for changes to take effect. 您已被提升为版主。请退出再登陆以使修改生效。 - + Warned 被警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 你因为%1被警告。 请杜绝此类行为否则会被采取进一步行动。如果有任何疑问,请私信版主。 - + You have received the following message from the server. (custom messages like these could be untranslated) 你收到来自服务器的如下消息。 @@ -5273,6 +5348,23 @@ Please refrain from engaging in this activity or further actions may be taken ag 无法分析套牌。 + + TipsOfTheDay + + + File does not exist. + + 文件不存在 + + + + + Failed to open file. + + 打开文件失败。 + + + UpdateDownloader @@ -5597,42 +5689,42 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 通用接口设置 - + Enable notifications in taskbar 开启任务栏提醒 - + Notify in the taskbar for game events while you are spectating 观看时在任务栏提示游戏信息 - + &Double-click cards to play them (instead of single-click) 双击卡牌开始 (而不是单击开始) - + &Play all nonlands onto the stack (not the battlefield) by default 默认将所有非地牌加入堆叠(不是战场) - + Annotate card text on tokens 用卡牌信息给衍生物标注 - + Animation settings 动画设置 - + &Tap/untap animation 横置/重置 动画 @@ -5702,97 +5794,127 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 将选择的系列移到顶端 - + Move selected set up 将选择的系列上移 - + Move selected set down 将选择的系列下移 - + Move selected set to the bottom 将选择的系列移到底部 - + + Search by set name, code, or type + 通过系列名、代码或类型搜索 + + + + Default order + 默认顺序 + + + + Restore original art priority order + 恢复初始优先顺序 + + + Enable all sets 启用所有系列 - + Disable all sets 禁用所有系列 - + Enable selected set(s) 启用所选系列 - + Disable selected set(s) 禁用所选系列 - + Deck Editor 套牌编辑器 - + Only cards in enabled sets will appear in the deck editor card list 套牌编辑器卡牌列表将只显示已启用卡组牌张 - + Card Art 牌张风格 - + Image priority is decided in the following order 图片使用的优先级由以下规则决定 - + The 这个 - + CUSTOM Folder 自定义文件夹 - + Enabled Sets (Top to Bottom) 启用的系列(从上到下) - + Disabled Sets (Top to Bottom) 禁用的系列(从上到下) - + + Warning: + 警告: + + + + While the set list is sorted by any of the columns, custom art priority setting is disabled. + 虽然所有列以系列列表排序,但是自定义优先设置被禁用。 + + + + To disable sorting click on the same column header again until this message disappears. + 若要禁用排序,请再次单击同一列标题,直到该消息消失为止。 + + + Manage sets 系列管理 - + Success 成功 - + The sets database has been saved successfully. 系列数据库已保存。 @@ -5823,7 +5945,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English 简体中文 (Chinese Simplified) @@ -5896,97 +6018,113 @@ Please refrain from engaging in this activity or further actions may be taken ag 分析套牌 - + Load deck (clipboard) 载入套牌(从剪贴板) - + Clear all filters 清除所有筛选项目 - + New deck 创建新套牌 - + Clear selected filter 清除所选的筛选项目 - + Open custom pic folder 打开自定义图片文件夹 - + Close 关闭 - + Print deck 打印套牌 - + Delete card 删除卡牌 - + Edit tokens 编辑衍生物... - + Reset layout 重置界面 - + Add card 添加卡牌 - + Save deck 保存套牌 - + Remove card 移除卡牌 - + Save deck as 另存为套牌... - + Load deck 读取套牌... - - + + Counters 指示物 - + Life 生命值 - + + + + + + + + + + + + + + Set + 系列 + + - + @@ -5994,16 +6132,15 @@ Please refrain from engaging in this activity or further actions may be taken ag - - Set - 系列 + + Add + 添加 - - + @@ -6011,455 +6148,440 @@ Please refrain from engaging in this activity or further actions may be taken ag - Add - 添加 - - - - - - - - - - - - - - + Remove 移除 - + Red - + Green 绿 - + Yellow - + Storm 风暴 - + W - + U - + B - + R - + G 绿 - + X 可变法术力 - + Main Window | Deck Editor 主窗口 | 套牌编辑器 - + Power / Toughness 力量/防御力 - + Power and Toughness 力量和防御力 - + Add (+1/+1) 添加 (+1/+1) - + Remove (-1/-1) 移除 (-1/-1) - + Toughness 防御力 - + Remove (-0/-1) 移除 (-1/-0) - + Add (+0/+1) 添加 (+0/+1) - + Power 力量 - + Remove (-1/-0) 移除 (-1/-0) - + Add (+1/+0) 添加 (+1/+0) - + Game Phases 游戏阶段 - + Untap 重置 - + Upkeep 维持 - - + + Draw - + Main 1 主要阶段1 - + Start combat 开始战斗 - + Attack 攻击 - + Block 阻挡 - + Damage 伤害 - + End combat 结束战斗 - + Main 2 主要阶段2 - + End 回合结束 - + Next phase 下个阶段 - + Next turn 下个回合 - + Playing Area 游戏区域 - + Manage sets 系列管理 - + Export deck 导出卡组 - + Save deck (clip) 保存卡组(剪切板) - + Save deck (clip; no annotations) 保存卡组(剪切板,没有注释) - + Tap / Untap Card 横置/重置卡牌 - + Untap all 重置所有 - + Toggle untap 锁定重置状态 - + Flip card 翻转卡牌 - + Peek card 查看卡牌 - + Play card 使用卡牌 - + Attach card 结附卡牌 - + Unattach card 取消结附 - + Clone card 复制卡牌 - + Create token 创造一个衍生物... - + Create all related tokens 创造所有相关的衍生物 - + Create another token 创造另一个衍生物 - + Set annotation 设置注释 - + Phases | P/T | Playing Area 阶段 | 力/防 | 游戏区域 - + Move card to 将卡移到 - + Bottom library 牌库底 - + Top library 牌库顶 - - + + Graveyard 坟墓场 - - + + Exile 放逐区 - + Hand 手牌 - + View 查看 - + Library 牌库 - + Tops card of library 牌库顶的卡牌 - + Sideboard 备牌 - + Close recent view 关闭最近的查看 - + Game Lobby 游戏大厅 - + Load remote deck 载入服务器上的套牌 - + Load local deck 从本地载入套牌 - + Gameplay 游戏 - + Draw arrow 划箭头... - + Leave game 离开游戏 - + Remove local arrows 移除本地箭头 - + Concede 放弃游戏 - + Roll dice 抛骰子 - + Rotate view CW 顺时针旋转视角 - + Shuffle library 切洗套牌 - + Rotate view CCW 逆时针旋转视角 - + Mulligan 起手牌 - + Draw card 抓牌 - + Draw cards 抓牌 - + Undo draw 撤销抓牌 - + Always reveal top card 一直展示牌库顶牌 - + Draw | Move | View | Gameplay 抓牌|移动|查看|游戏 - + How to set custom shortcuts 如何设置自定义快捷键 - + Restore all default shortcuts 恢复所有默认快捷键 - + Clear all shortcuts 清除所有快捷键 diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f32d59a26..cf04f1956 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -26,6 +26,7 @@ SET(common_SOURCES server_room.cpp serverinfo_user_container.cpp sfmt/SFMT.c + expression.cpp ) set(ORACLE_LIBS) diff --git a/common/decklist.cpp b/common/decklist.cpp index 0993d71f1..be96cfa2d 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -2,8 +2,17 @@ #include #include #include +#include #include +#if QT_VERSION < 0x050600 +// qHash on QRegularExpression was added in 5.6, FIX IT +uint qHash(const QRegularExpression &key, uint seed) noexcept +{ + return qHash(key.pattern(), seed); // call qHash on pattern QString instead +} +#endif + SideboardPlan::SideboardPlan(const QString &_name, const QList &_moveList) : name(_name), moveList(_moveList) { @@ -477,161 +486,131 @@ bool DeckList::saveToFile_Native(QIODevice *device) bool DeckList::loadFromStream_Plain(QTextStream &in) { + const QRegularExpression reCardLine("^\\s*[\\w\\[\\(\\{].*$", QRegularExpression::UseUnicodePropertiesOption); + const QRegularExpression reEmpty("^\\s*$"); + const QRegularExpression reComment("[\\w\\[\\(\\{].*$", QRegularExpression::UseUnicodePropertiesOption); + const QRegularExpression reSBMark("^\\s*sb:\\s*(.+)", QRegularExpression::CaseInsensitiveOption); + const QRegularExpression reSBComment("sideboard", QRegularExpression::CaseInsensitiveOption); + + // simplified matches + const QRegularExpression reMultiplier("^[xX\\(\\[]*(\\d+)[xX\\*\\)\\]]* ?(.+)"); + const QRegularExpression reBrace(" ?[\\[\\{][^\\]\\}]*[\\]\\}] ?"); // not nested + const QRegularExpression reRoundBrace("^\\([^\\)]*\\) ?"); // () are only matched at start of string + const QRegularExpression reDigitBrace(" ?\\(\\d*\\) ?"); // () are matched if containing digits + const QHash differences{{QRegularExpression("’"), QString("'")}, + {QRegularExpression("Æ"), QString("Ae")}, + {QRegularExpression("æ"), QString("ae")}, + {QRegularExpression(" ?[|/]+ ?"), QString(" // ")}, + {QRegularExpression("(? inputs; // QTextStream -> QVector - bool priorEntryIsBlank = true, isAtBeginning = true; - int blankLines = 0; - while (!in.atEnd()) { - QString line = in.readLine().simplified().toLower(); + QStringList inputs = in.readAll().trimmed().split('\n'); + int max_line = inputs.size(); - /* - * Removes all blank lines at start of inputs - * Ex: ("", "", "", "Card1", "Card2") => ("Card1", "Card2") - * - * This will also concise multiple blank lines in a row to just one blank - * Ex: ("Card1", "Card2", "", "", "", "Card3") => ("Card1", "Card2", "", "Card3") - */ - if (line.isEmpty()) { - if (priorEntryIsBlank || isAtBeginning) { - continue; - } - - priorEntryIsBlank = true; - blankLines++; - } else { - isAtBeginning = false; - priorEntryIsBlank = false; + // start at the first empty line before the first cardline + int deckStart = inputs.indexOf(reCardLine); + if (deckStart == -1) { // there are no cards? + if (inputs.indexOf(reComment) == -1) + return false; // input is empty + deckStart = max_line; + } else { + deckStart = inputs.lastIndexOf(reEmpty, deckStart); + if (deckStart == -1) { + deckStart = 0; } - - inputs.push_back(line); } - /* - * Removes blank line at end of inputs (if applicable) - * Ex: ("Card1", "Card2", "") => ("Card1", "Card2") - * NOTE: Any duplicates were taken care of above, so there can be - * at most one blank line at the very end - */ - if (!inputs.empty() && inputs.last().isEmpty()) { - blankLines--; - inputs.erase(inputs.end() - 1); - } - - // If "Sideboard" line appears in inputs, then blank lines mean nothing - if (inputs.contains("sideboard")) { - blankLines = 2; - } - - bool inSideboard = false, titleFound = false, isSideboard; - int okRows = 0; - - foreach (QString line, inputs) { - // This is a comment line, ignore it - if (line.startsWith("//")) { - if (!titleFound) // Set the title to the first comment - { - name = line.mid(2).trimmed(); - titleFound = true; - } else if (okRows == 0) // We haven't processed any cards yet - { - comments += line.mid(2).trimmed() + "\n"; + // find sideboard position, if marks are used this won't be needed + int sBStart = -1; + if (inputs.indexOf(reSBMark, deckStart) == -1) { + sBStart = inputs.indexOf(reSBComment, deckStart); + if (sBStart == -1) { + sBStart = inputs.indexOf(reEmpty, deckStart + 1); + if (sBStart == -1) { + sBStart = max_line; } + int nextCard = inputs.indexOf(reCardLine, sBStart + 1); + if (inputs.indexOf(reEmpty, nextCard + 1) != -1) { + sBStart = max_line; // if there is another empty line all cards are mainboard + } + } + } + int index = 0; + QRegularExpressionMatch match; + + // parse name and comments + while (index < deckStart) { + const QString current = inputs.at(index++); + if (!current.contains(reEmpty)) { + match = reComment.match(current); + name = match.captured(); + break; + } + } + while (index < deckStart) { + const QString current = inputs.at(index++); + if (!current.contains(reEmpty)) { + match = reComment.match(current); + comments += match.captured() + '\n'; + } + } + comments.chop(1); // remove last newline + + // parse decklist + for (; index < max_line; ++index) { + + // check if line is a card + match = reCardLine.match(inputs.at(index)); + if (!match.hasMatch()) continue; - } + QString cardName = match.captured().simplified(); - // If we have a blank line and it's the _ONLY_ blank line in the paste - // and it follows at least one valid card - // Then we assume it means to start the sideboard section of the paste. - // If we have the word "Sideboard" appear on any line, then that will - // also indicate the start of the sideboard. - if ((line.isEmpty() && blankLines == 1 && okRows > 0) || line.startsWith("sideboard")) { - inSideboard = true; - continue; // The line isn't actually a card - } - - isSideboard = inSideboard; - - if (line.startsWith("sb:")) { - line = line.mid(3).trimmed(); - isSideboard = true; - } - - if (line.trimmed().isEmpty()) { - continue; // The line was " " instead of "\n" - } - - // Filter out MWS edition symbols and basic land extras - QRegExp rx("\\[.*\\]\\s?"); - line.remove(rx); - rx.setPattern("\\s?\\(.*\\)"); - line.remove(rx); - - // Filter out post card name editions - rx.setPattern("\\|.*$"); - line.remove(rx); - - // If the user inputs "Quicksilver Elemental" then it will cut it off - // 1x Squishy Treaker - int i = line.indexOf(' '); - int cardNameStart = i + 1; - - if (i > 0) { - // If the count ends with an 'x', ignore it. For example, - // "4x Storm Crow" will count 4 correctly. - if (line.at(i - 1) == 'x') { - i--; - } else if (!line.at(i - 1).isDigit()) { - // If the user inputs "Quicksilver Elemental" then it will work as 1x of that card - cardNameStart = 0; + // check if card should be sideboard + bool sideboard = false; + if (sBStart < 0) { + match = reSBMark.match(cardName); + if (match.hasMatch()) { + sideboard = true; + cardName = match.captured(1); } + } else { + if (index == sBStart) // skip sideboard line itself + continue; + sideboard = index > sBStart; } - bool ok; - int number = line.left(i).toInt(&ok); - - if (!ok) { - number = 1; // If input is "cardName" assume it's "1x cardName" + // check if a specific amount is mentioned + int amount = 1; + match = reMultiplier.match(cardName); + if (match.hasMatch()) { + amount = match.capturedRef(1).toInt(); + cardName = match.captured(2); } - QString cardName = line.mid(cardNameStart); + // remove stuff inbetween braces + cardName.remove(reBrace); + cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards + cardName.remove(reDigitBrace); // all cards from un-sets that have a word in between round braces at the end - // Common differences between Cockatrice's card names - // and what's commonly used in decklists - rx.setPattern("’"); - cardName.replace(rx, "'"); - rx.setPattern("Æ"); - cardName.replace(rx, "Ae"); - rx.setPattern("\\s*[|/]{1,2}\\s*"); - cardName.replace(rx, " // "); - - // Replace only if the ampersand is preceded by a non-capital letter, - // as would happen with acronyms. So 'Fire & Ice' is replaced but not - // 'R&D' or 'R & D'. - // Qt regexes don't support lookbehind so we capture and replace instead. - rx.setPattern("([^A-Z])\\s*&\\s*"); - if (rx.indexIn(cardName) != -1) { - cardName.replace(rx, QString("%1 // ").arg(rx.cap(1))); + // replace common differences in cardnames + for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) { + cardName.replace(diff.key(), diff.value()); } - // We need to get the name of the card from the database, - // but we can't do that until we get the "real" name - // (name stored in database for the card) - // and establish a card info that is of the card, then it's - // a simple getting the _real_ name of the card - // (i.e. "STOrm, CrOW" => "Storm Crow") + // get cardname, this function does nothing if the name is not found cardName = getCompleteCardName(cardName); - // Look for the correct card zone of where to place the new card - QString zoneName = getCardZoneFromName(cardName, isSideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN); + // get zone name based on if it's in sideboard + QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN); - okRows++; - new DecklistCardNode(cardName, number, getZoneObjFromName(zoneName)); + // make new entry in decklist + new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName)); } updateDeckHash(); - return (okRows > 0); + return true; } InnerDecklistNode *DeckList::getZoneObjFromName(const QString zoneName) diff --git a/common/expression.cpp b/common/expression.cpp new file mode 100644 index 000000000..1671d04b1 --- /dev/null +++ b/common/expression.cpp @@ -0,0 +1,108 @@ +#include "expression.h" +#include "./lib/peglib.h" + +#include +#include +#include +#include + +peg::parser math(R"( + EXPRESSION <- P0 + P0 <- P1 (P1_OPERATOR P1)* + P1 <- P2 (P2_OPERATOR P2)* + P2 <- P3 (P3_OPERATOR P3)* + P3 <- NUMBER / FUNCTION / VARIABLE / '(' P0 ')' + + P1_OPERATOR <- < [-+] > + P2_OPERATOR <- < [/*] > + P3_OPERATOR <- < '^' > + + NUMBER <- < '-'? [0-9]+ > + NAME <- < [a-z][a-z0-9]* > + VARIABLE <- < [x] > + FUNCTION <- NAME '(' EXPRESSION ( [,\n] EXPRESSION )* ')' + + %whitespace <- [ \t\r]* + )"); + +QMap> *default_functions = nullptr; + +Expression::Expression(double initial) : value(initial) +{ + if (default_functions == nullptr) { + default_functions = new QMap>(); + default_functions->insert("sin", [](double a) { return sin(a); }); + default_functions->insert("cos", [](double a) { return cos(a); }); + default_functions->insert("tan", [](double a) { return tan(a); }); + default_functions->insert("sqrt", [](double a) { return sqrt(a); }); + default_functions->insert("log", [](double a) { return log(a); }); + default_functions->insert("log10", [](double a) { return log(a); }); + default_functions->insert("trunc", [](double a) { return trunc(a); }); + default_functions->insert("abs", [](double a) { return abs(a); }); + + default_functions->insert("floor", [](double a) { return floor(a); }); + default_functions->insert("ceil", [](double a) { return ceil(a); }); + default_functions->insert("round", [](double a) { return round(a); }); + default_functions->insert("trunc", [](double a) { return trunc(a); }); + } + fns = QMap>(*default_functions); +} + +double Expression::eval(const peg::Ast &ast) +{ + const auto &nodes = ast.nodes; + if (ast.name == "NUMBER") { + return stod(ast.token); + } else if (ast.name == "FUNCTION") { + QString name = QString::fromStdString(nodes[0]->token); + if (!fns.contains(name)) + return 0; + return fns[name](eval(*nodes[1])); + } else if (ast.name == "VARIABLE") { + return value; + } else if (ast.name[0] == 'P') { + double result = eval(*nodes[0]); + for (int i = 1; i < nodes.size(); i += 2) { + double arg = eval(*nodes[i + 1]); + char operation = nodes[i]->token[0]; + switch (operation) { + case '+': + result += arg; + break; + case '-': + result -= arg; + break; + case '*': + result *= arg; + break; + case '/': + result /= arg; + break; + case '^': + result = pow(result, arg); + break; + default: + result = 0; + break; + } + } + return result; + } else { + return -1; + } +} + +double Expression::parse(const QString &expr) +{ + QByteArray ba = expr.toLocal8Bit(); + + math.enable_ast(); + + std::shared_ptr ast; + if (math.parse(ba.data(), ast)) { + ast = peg::AstOptimizer(true).optimize(ast); + return eval(*ast); + } + + return 0; +} diff --git a/common/expression.h b/common/expression.h new file mode 100644 index 000000000..8c6a94932 --- /dev/null +++ b/common/expression.h @@ -0,0 +1,28 @@ +#ifndef EXPRESSION_H +#define EXPRESSION_H + +#include +#include +#include + +namespace peg +{ +template struct AstBase; +struct EmptyType; +typedef AstBase Ast; +} // namespace peg + +class Expression +{ +public: + double value; + + explicit Expression(double initial = 0); + double parse(const QString &expr); + +private: + double eval(const peg::Ast &ast); + QMap> fns; +}; + +#endif diff --git a/common/lib/peglib.h b/common/lib/peglib.h new file mode 100644 index 000000000..5c5e4c011 --- /dev/null +++ b/common/lib/peglib.h @@ -0,0 +1,3293 @@ +// +// peglib.h +// +// Copyright (c) 2015-18 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPPEGLIB_PEGLIB_H +#define CPPPEGLIB_PEGLIB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// guard for older versions of VC++ +#ifdef _MSC_VER +// VS2013 has no constexpr +#if (_MSC_VER == 1800) +#define PEGLIB_NO_CONSTEXPR_SUPPORT +#elif (_MSC_VER >= 1800) +// good to go +#else (_MSC_VER < 1800) +#error "Requires C+11 support" +#endif +#endif + +// define if the compiler doesn't support unicode characters reliably in the +// source code +//#define PEGLIB_NO_UNICODE_CHARS + +namespace peg { + +/*----------------------------------------------------------------------------- + * any + *---------------------------------------------------------------------------*/ + +class any +{ +public: + any() : content_(nullptr) {} + + any(const any& rhs) : content_(rhs.clone()) {} + + any(any&& rhs) : content_(rhs.content_) { + rhs.content_ = nullptr; + } + + template + any(const T& value) : content_(new holder(value)) {} + + any& operator=(const any& rhs) { + if (this != &rhs) { + if (content_) { + delete content_; + } + content_ = rhs.clone(); + } + return *this; + } + + any& operator=(any&& rhs) { + if (this != &rhs) { + if (content_) { + delete content_; + } + content_ = rhs.content_; + rhs.content_ = nullptr; + } + return *this; + } + + ~any() { + delete content_; + } + + bool is_undefined() const { + return content_ == nullptr; + } + + template < + typename T, + typename std::enable_if::value, std::nullptr_t>::type = nullptr + > + T& get() { + if (!content_) { + throw std::bad_cast(); + } + auto p = dynamic_cast*>(content_); + assert(p); + if (!p) { + throw std::bad_cast(); + } + return p->value_; + } + + template < + typename T, + typename std::enable_if::value, std::nullptr_t>::type = nullptr + > + T& get() { + return *this; + } + + template < + typename T, + typename std::enable_if::value, std::nullptr_t>::type = nullptr + > + const T& get() const { + assert(content_); + auto p = dynamic_cast*>(content_); + assert(p); + if (!p) { + throw std::bad_cast(); + } + return p->value_; + } + + template < + typename T, + typename std::enable_if::value, std::nullptr_t>::type = nullptr + > + const any& get() const { + return *this; + } + +private: + struct placeholder { + virtual ~placeholder() {} + virtual placeholder* clone() const = 0; + }; + + template + struct holder : placeholder { + holder(const T& value) : value_(value) {} + placeholder* clone() const override { + return new holder(value_); + } + T value_; + }; + + placeholder* clone() const { + return content_ ? content_->clone() : nullptr; + } + + placeholder* content_; +}; + +/*----------------------------------------------------------------------------- + * scope_exit + *---------------------------------------------------------------------------*/ + +// This is based on "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +template +struct scope_exit +{ + explicit scope_exit(EF&& f) + : exit_function(std::move(f)) + , execute_on_destruction{true} {} + + scope_exit(scope_exit&& rhs) + : exit_function(std::move(rhs.exit_function)) + , execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { + this->exit_function(); + } + } + + void release() { + this->execute_on_destruction = false; + } + +private: + scope_exit(const scope_exit&) = delete; + void operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + + EF exit_function; + bool execute_on_destruction; +}; + +template +auto make_scope_exit(EF&& exit_function) -> scope_exit { + return scope_exit::type>(std::forward(exit_function)); +} + +/*----------------------------------------------------------------------------- + * UTF8 functions + *---------------------------------------------------------------------------*/ + +inline size_t codepoint_length(const char *s8, size_t l) { + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + return 1; + } else if ((b & 0xE0) == 0xC0) { + return 2; + } else if ((b & 0xF0) == 0xE0) { + return 3; + } else if ((b & 0xF8) == 0xF0) { + return 4; + } + } + return 0; +} + +inline size_t encode_codepoint(char32_t cp, char *buff) { + if (cp < 0x0080) { + buff[0] = static_cast(cp & 0x7F); + return 1; + } else if (cp < 0x0800) { + buff[0] = static_cast(0xC0 | ((cp >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (cp & 0x3F)); + return 2; + } else if (cp < 0xD800) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0xE000) { + // D800 - DFFF is invalid... + return 0; + } else if (cp < 0x10000) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0x110000) { + buff[0] = static_cast(0xF0 | ((cp >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((cp >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (cp & 0x3F)); + return 4; + } + return 0; +} + +inline std::string encode_codepoint(char32_t cp) { + char buff[4]; + auto l = encode_codepoint(cp, buff); + return std::string(buff, l); +} + +inline bool decode_codepoint(const char *s8, size_t l, size_t &bytes, + char32_t &cp) { + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + bytes = 1; + cp = b; + return true; + } else if ((b & 0xE0) == 0xC0) { + if (l >= 2) { + bytes = 2; + cp = ((static_cast(s8[0] & 0x1F)) << 6) | + (static_cast(s8[1] & 0x3F)); + return true; + } + } else if ((b & 0xF0) == 0xE0) { + if (l >= 3) { + bytes = 3; + cp = ((static_cast(s8[0] & 0x0F)) << 12) | + ((static_cast(s8[1] & 0x3F)) << 6) | + (static_cast(s8[2] & 0x3F)); + return true; + } + } else if ((b & 0xF8) == 0xF0) { + if (l >= 4) { + bytes = 4; + cp = ((static_cast(s8[0] & 0x07)) << 18) | + ((static_cast(s8[1] & 0x3F)) << 12) | + ((static_cast(s8[2] & 0x3F)) << 6) | + (static_cast(s8[3] & 0x3F)); + return true; + } + } + } + return false; +} + +inline size_t decode_codepoint(const char *s8, size_t l, char32_t &out) { + size_t bytes; + if (decode_codepoint(s8, l, bytes, out)) { + return bytes; + } + return 0; +} + +inline char32_t decode_codepoint(const char *s8, size_t l) { + char32_t out = 0; + decode_codepoint(s8, l, out); + return out; +} + +inline std::u32string decode(const char *s8, size_t l) { + std::u32string out; + size_t i = 0; + while (i < l) { + auto beg = i++; + while (i < l && (s8[i] & 0xc0) == 0x80) { + i++; + } + out += decode_codepoint(&s8[beg], (i - beg)); + } + return out; +} + +/*----------------------------------------------------------------------------- + * resolve_escape_sequence + *---------------------------------------------------------------------------*/ + +inline bool is_hex(char c, int& v) { + if ('0' <= c && c <= '9') { + v = c - '0'; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } + return false; +} + +inline bool is_digit(char c, int& v) { + if ('0' <= c && c <= '9') { + v = c - '0'; + return true; + } + return false; +} + +inline std::pair parse_hex_number(const char* s, size_t n, size_t i) { + int ret = 0; + int val; + while (i < n && is_hex(s[i], val)) { + ret = static_cast(ret * 16 + val); + i++; + } + return std::make_pair(ret, i); +} + +inline std::pair parse_octal_number(const char* s, size_t n, size_t i) { + int ret = 0; + int val; + while (i < n && is_digit(s[i], val)) { + ret = static_cast(ret * 8 + val); + i++; + } + return std::make_pair(ret, i); +} + +inline std::string resolve_escape_sequence(const char* s, size_t n) { + std::string r; + r.reserve(n); + + size_t i = 0; + while (i < n) { + auto ch = s[i]; + if (ch == '\\') { + i++; + switch (s[i]) { + case 'n': r += '\n'; i++; break; + case 'r': r += '\r'; i++; break; + case 't': r += '\t'; i++; break; + case '\'': r += '\''; i++; break; + case '"': r += '"'; i++; break; + case '[': r += '['; i++; break; + case ']': r += ']'; i++; break; + case '\\': r += '\\'; i++; break; + case 'x': + case 'u': { + char32_t cp; + std::tie(cp, i) = parse_hex_number(s, n, i + 1); + r += encode_codepoint(cp); + break; + } + default: { + char32_t cp; + std::tie(cp, i) = parse_octal_number(s, n, i); + r += encode_codepoint(cp); + break; + } + } + } else { + r += ch; + i++; + } + } + return r; +} + +/*----------------------------------------------------------------------------- + * PEG + *---------------------------------------------------------------------------*/ + +/* +* Line information utility function +*/ +inline std::pair line_info(const char* start, const char* cur) { + auto p = start; + auto col_ptr = p; + auto no = 1; + + while (p < cur) { + if (*p == '\n') { + no++; + col_ptr = p + 1; + } + p++; + } + + auto col = p - col_ptr + 1; + + return std::make_pair(no, col); +} + +/* +* Semantic values +*/ +struct SemanticValues : protected std::vector +{ + // Input text + const char* path; + const char* ss; + + // Matched string + const char* c_str() const { return s_; } + size_t length() const { return n_; } + + std::string str() const { + return std::string(s_, n_); + } + + // Line number and column at which the matched string is + std::pair line_info() const { + return peg::line_info(ss, s_); + } + + // Choice count + size_t choice_count() const { return choice_count_; } + + // Choice number (0 based index) + size_t choice() const { return choice_; } + + // Tokens + std::vector> tokens; + + std::string token(size_t id = 0) const { + if (!tokens.empty()) { + assert(id < tokens.size()); + const auto& tok = tokens[id]; + return std::string(tok.first, tok.second); + } + return std::string(s_, n_); + } + + // Transform the semantic value vector to another vector + template + auto transform(size_t beg = 0, size_t end = static_cast(-1)) const -> vector { + return this->transform(beg, end, [](const any& v) { return v.get(); }); + } + + SemanticValues() : s_(nullptr), n_(0), choice_count_(0), choice_(0) {} + + using std::vector::iterator; + using std::vector::const_iterator; + using std::vector::size; + using std::vector::empty; + using std::vector::assign; + using std::vector::begin; + using std::vector::end; + using std::vector::rbegin; + using std::vector::rend; + using std::vector::operator[]; + using std::vector::at; + using std::vector::resize; + using std::vector::front; + using std::vector::back; + using std::vector::push_back; + using std::vector::pop_back; + using std::vector::insert; + using std::vector::erase; + using std::vector::clear; + using std::vector::swap; + using std::vector::emplace; + using std::vector::emplace_back; + +private: + friend class Context; + friend class Sequence; + friend class PrioritizedChoice; + friend class Holder; + + const char* s_; + size_t n_; + size_t choice_count_; + size_t choice_; + + template + auto transform(F f) const -> vector::type> { + vector::type> r; + for (const auto& v: *this) { + r.emplace_back(f(v)); + } + return r; + } + + template + auto transform(size_t beg, size_t end, F f) const -> vector::type> { + vector::type> r; + end = (std::min)(end, size()); + for (size_t i = beg; i < end; i++) { + r.emplace_back(f((*this)[i])); + } + return r; + } + + void reset() { + path = nullptr; + ss = nullptr; + tokens.clear(); + + s_ = nullptr; + n_ = 0; + choice_count_ = 0; + choice_ = 0; + } +}; + +/* + * Semantic action + */ +template < + typename R, typename F, + typename std::enable_if::value, std::nullptr_t>::type = nullptr, + typename... Args> +any call(F fn, Args&&... args) { + fn(std::forward(args)...); + return any(); +} + +template < + typename R, typename F, + typename std::enable_if::type, any>::value, std::nullptr_t>::type = nullptr, + typename... Args> +any call(F fn, Args&&... args) { + return fn(std::forward(args)...); +} + +template < + typename R, typename F, + typename std::enable_if< + !std::is_void::value && + !std::is_same::type, any>::value, std::nullptr_t>::type = nullptr, + typename... Args> +any call(F fn, Args&&... args) { + return any(fn(std::forward(args)...)); +} + +class Action +{ +public: + Action() = default; + + Action(const Action& rhs) : fn_(rhs.fn_) {} + + template ::value && !std::is_same::value, std::nullptr_t>::type = nullptr> + Action(F fn) : fn_(make_adaptor(fn, &F::operator())) {} + + template ::value, std::nullptr_t>::type = nullptr> + Action(F fn) : fn_(make_adaptor(fn, fn)) {} + + template ::value, std::nullptr_t>::type = nullptr> + Action(F /*fn*/) {} + + template ::value && !std::is_same::value, std::nullptr_t>::type = nullptr> + void operator=(F fn) { + fn_ = make_adaptor(fn, &F::operator()); + } + + template ::value, std::nullptr_t>::type = nullptr> + void operator=(F fn) { + fn_ = make_adaptor(fn, fn); + } + + template ::value, std::nullptr_t>::type = nullptr> + void operator=(F /*fn*/) {} + + Action& operator=(const Action& rhs) = default; + + operator bool() const { + return bool(fn_); + } + + any operator()(SemanticValues& sv, any& dt) const { + return fn_(sv, dt); + } + +private: + template + struct TypeAdaptor_sv { + TypeAdaptor_sv(std::function fn) + : fn_(fn) {} + any operator()(SemanticValues& sv, any& /*dt*/) { + return call(fn_, sv); + } + std::function fn_; + }; + + template + struct TypeAdaptor_csv { + TypeAdaptor_csv(std::function fn) + : fn_(fn) {} + any operator()(SemanticValues& sv, any& /*dt*/) { + return call(fn_, sv); + } + std::function fn_; + }; + + template + struct TypeAdaptor_sv_dt { + TypeAdaptor_sv_dt(std::function fn) + : fn_(fn) {} + any operator()(SemanticValues& sv, any& dt) { + return call(fn_, sv, dt); + } + std::function fn_; + }; + + template + struct TypeAdaptor_csv_dt { + TypeAdaptor_csv_dt(std::function fn) + : fn_(fn) {} + any operator()(SemanticValues& sv, any& dt) { + return call(fn_, sv, dt); + } + std::function fn_; + }; + + typedef std::function Fty; + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv) const) { + return TypeAdaptor_sv(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv) const) { + return TypeAdaptor_csv(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv)) { + return TypeAdaptor_sv(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv)) { + return TypeAdaptor_csv(fn); + } + + template + Fty make_adaptor(F fn, R (* /*mf*/)(SemanticValues& sv)) { + return TypeAdaptor_sv(fn); + } + + template + Fty make_adaptor(F fn, R (* /*mf*/)(const SemanticValues& sv)) { + return TypeAdaptor_csv(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv, any& dt) const) { + return TypeAdaptor_sv_dt(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv, any& dt) const) { + return TypeAdaptor_csv_dt(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv, any& dt)) { + return TypeAdaptor_sv_dt(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv, any& dt)) { + return TypeAdaptor_csv_dt(fn); + } + + template + Fty make_adaptor(F fn, R(* /*mf*/)(SemanticValues& sv, any& dt)) { + return TypeAdaptor_sv_dt(fn); + } + + template + Fty make_adaptor(F fn, R(* /*mf*/)(const SemanticValues& sv, any& dt)) { + return TypeAdaptor_csv_dt(fn); + } + + Fty fn_; +}; + +/* + * Semantic predicate + */ +// Note: 'parse_error' exception class should be be used in sematic action handlers to reject the rule. +struct parse_error { + parse_error() = default; + parse_error(const char* s) : s_(s) {} + const char* what() const { return s_.empty() ? nullptr : s_.c_str(); } +private: + std::string s_; +}; + +/* + * Result + */ +inline bool success(size_t len) { + return len != static_cast(-1); +} + +inline bool fail(size_t len) { + return len == static_cast(-1); +} + +/* + * Context + */ +class Context; +class Ope; +class Definition; + +typedef std::function Tracer; + +class Context +{ +public: + const char* path; + const char* s; + const size_t l; + + const char* error_pos; + const char* message_pos; + std::string message; // TODO: should be `int`. + + std::vector> value_stack; + size_t value_stack_size; + std::vector>> args_stack; + + size_t nest_level; + + bool in_token; + + std::shared_ptr whitespaceOpe; + bool in_whitespace; + + std::shared_ptr wordOpe; + + std::vector> capture_scope_stack; + + const size_t def_count; + const bool enablePackratParsing; + std::vector cache_registered; + std::vector cache_success; + + std::map, std::tuple> cache_values; + + std::function tracer; + + Context( + const char* a_path, + const char* a_s, + size_t a_l, + size_t a_def_count, + std::shared_ptr a_whitespaceOpe, + std::shared_ptr a_wordOpe, + bool a_enablePackratParsing, + Tracer a_tracer) + : path(a_path) + , s(a_s) + , l(a_l) + , error_pos(nullptr) + , message_pos(nullptr) + , value_stack_size(0) + , nest_level(0) + , in_token(false) + , whitespaceOpe(a_whitespaceOpe) + , in_whitespace(false) + , wordOpe(a_wordOpe) + , def_count(a_def_count) + , enablePackratParsing(a_enablePackratParsing) + , cache_registered(enablePackratParsing ? def_count * (l + 1) : 0) + , cache_success(enablePackratParsing ? def_count * (l + 1) : 0) + , tracer(a_tracer) + { + args_stack.resize(1); + capture_scope_stack.resize(1); + } + + template + void packrat(const char* a_s, size_t def_id, size_t& len, any& val, T fn) { + if (!enablePackratParsing) { + fn(val); + return; + } + + auto col = a_s - s; + auto idx = def_count * static_cast(col) + def_id; + + if (cache_registered[idx]) { + if (cache_success[idx]) { + auto key = std::make_pair(col, def_id); + std::tie(len, val) = cache_values[key]; + return; + } else { + len = static_cast(-1); + return; + } + } else { + fn(val); + cache_registered[idx] = true; + cache_success[idx] = success(len); + if (success(len)) { + auto key = std::make_pair(col, def_id); + cache_values[key] = std::make_pair(len, val); + } + return; + } + } + + SemanticValues& push() { + assert(value_stack_size <= value_stack.size()); + if (value_stack_size == value_stack.size()) { + value_stack.emplace_back(std::make_shared()); + } + auto& sv = *value_stack[value_stack_size++]; + if (!sv.empty()) { + sv.clear(); + } + sv.reset(); + sv.path = path; + sv.ss = s; + return sv; + } + + void pop() { + value_stack_size--; + } + + void push_args(const std::vector>& args) { + args_stack.push_back(args); + } + + void pop_args() { + args_stack.pop_back(); + } + + const std::vector>& top_args() const { + return args_stack[args_stack.size() - 1]; + } + + void push_capture_scope() { + capture_scope_stack.resize(capture_scope_stack.size() + 1); + } + + void pop_capture_scope() { + capture_scope_stack.pop_back(); + } + + void shift_capture_values() { + assert(capture_scope_stack.size() >= 2); + auto it = capture_scope_stack.rbegin(); + auto it_prev = it + 1; + for (const auto& kv: *it) { + (*it_prev)[kv.first] = kv.second; + } + } + + void set_error_pos(const char* a_s) { + if (error_pos < a_s) error_pos = a_s; + } + + void trace(const char* name, const char* a_s, size_t n, SemanticValues& sv, any& dt) const { + if (tracer) tracer(name, a_s, n, sv, *this, dt); + } +}; + +/* + * Parser operators + */ +class Ope +{ +public: + struct Visitor; + + virtual ~Ope() {} + virtual size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const = 0; + virtual void accept(Visitor& v) = 0; +}; + +class Sequence : public Ope +{ +public: + Sequence(const Sequence& rhs) : opes_(rhs.opes_) {} + +#if defined(_MSC_VER) && _MSC_VER < 1900 // Less than Visual Studio 2015 + // NOTE: Compiler Error C2797 on Visual Studio 2013 + // "The C++ compiler in Visual Studio does not implement list + // initialization inside either a member initializer list or a non-static + // data member initializer. Before Visual Studio 2013 Update 3, this was + // silently converted to a function call, which could lead to bad code + // generation. Visual Studio 2013 Update 3 reports this as an error." + template + Sequence(const Args& ...args) { + opes_ = std::vector>{ static_cast>(args)... }; + } +#else + template + Sequence(const Args& ...args) : opes_{ static_cast>(args)... } {} +#endif + + Sequence(const std::vector>& opes) : opes_(opes) {} + Sequence(std::vector>&& opes) : opes_(opes) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Sequence", s, n, sv, dt); + auto& chldsv = c.push(); + size_t i = 0; + for (const auto& ope : opes_) { + c.nest_level++; + auto se = make_scope_exit([&]() { c.nest_level--; }); + const auto& rule = *ope; + auto len = rule.parse(s + i, n - i, chldsv, c, dt); + if (fail(len)) { + return static_cast(-1); + } + i += len; + } + sv.insert(sv.end(), chldsv.begin(), chldsv.end()); + sv.s_ = chldsv.c_str(); + sv.n_ = chldsv.length(); + sv.tokens.insert(sv.tokens.end(), chldsv.tokens.begin(), chldsv.tokens.end()); + return i; + } + + void accept(Visitor& v) override; + + std::vector> opes_; +}; + +class PrioritizedChoice : public Ope +{ +public: +#if defined(_MSC_VER) && _MSC_VER < 1900 // Less than Visual Studio 2015 + // NOTE: Compiler Error C2797 on Visual Studio 2013 + // "The C++ compiler in Visual Studio does not implement list + // initialization inside either a member initializer list or a non-static + // data member initializer. Before Visual Studio 2013 Update 3, this was + // silently converted to a function call, which could lead to bad code + // generation. Visual Studio 2013 Update 3 reports this as an error." + template + PrioritizedChoice(const Args& ...args) { + opes_ = std::vector>{ static_cast>(args)... }; + } +#else + template + PrioritizedChoice(const Args& ...args) : opes_{ static_cast>(args)... } {} +#endif + + PrioritizedChoice(const std::vector>& opes) : opes_(opes) {} + PrioritizedChoice(std::vector>&& opes) : opes_(opes) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("PrioritizedChoice", s, n, sv, dt); + size_t id = 0; + for (const auto& ope : opes_) { + c.nest_level++; + auto& chldsv = c.push(); + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + c.pop_capture_scope(); + }); + const auto& rule = *ope; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + sv.insert(sv.end(), chldsv.begin(), chldsv.end()); + sv.s_ = chldsv.c_str(); + sv.n_ = chldsv.length(); + sv.choice_count_ = opes_.size(); + sv.choice_ = id; + sv.tokens.insert(sv.tokens.end(), chldsv.tokens.begin(), chldsv.tokens.end()); + + c.shift_capture_values(); + return len; + } + id++; + } + return static_cast(-1); + } + + void accept(Visitor& v) override; + + size_t size() const { return opes_.size(); } + + std::vector> opes_; +}; + +class ZeroOrMore : public Ope +{ +public: + ZeroOrMore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("ZeroOrMore", s, n, sv, dt); + auto save_error_pos = c.error_pos; + size_t i = 0; + while (n - i > 0) { + c.nest_level++; + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop_capture_scope(); + }); + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + const auto& rule = *ope_; + auto len = rule.parse(s + i, n - i, sv, c, dt); + if (success(len)) { + c.shift_capture_values(); + } else { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + break; + } + i += len; + } + return i; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class OneOrMore : public Ope +{ +public: + OneOrMore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("OneOrMore", s, n, sv, dt); + size_t len = 0; + { + c.nest_level++; + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + len = rule.parse(s, n, sv, c, dt); + if (success(len)) { + c.shift_capture_values(); + } else { + return static_cast(-1); + } + } + auto save_error_pos = c.error_pos; + auto i = len; + while (n - i > 0) { + c.nest_level++; + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop_capture_scope(); + }); + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + const auto& rule = *ope_; + len = rule.parse(s + i, n - i, sv, c, dt); + if (success(len)) { + c.shift_capture_values(); + } else { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + break; + } + i += len; + } + return i; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class Option : public Ope +{ +public: + Option(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Option", s, n, sv, dt); + auto save_error_pos = c.error_pos; + c.nest_level++; + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + if (success(len)) { + c.shift_capture_values(); + return len; + } else { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + return 0; + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class AndPredicate : public Ope +{ +public: + AndPredicate(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("AndPredicate", s, n, sv, dt); + c.nest_level++; + auto& chldsv = c.push(); + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + return 0; + } else { + return static_cast(-1); + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class NotPredicate : public Ope +{ +public: + NotPredicate(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("NotPredicate", s, n, sv, dt); + auto save_error_pos = c.error_pos; + c.nest_level++; + auto& chldsv = c.push(); + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + c.set_error_pos(s); + return static_cast(-1); + } else { + c.error_pos = save_error_pos; + return 0; + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class LiteralString : public Ope + , public std::enable_shared_from_this +{ +public: + LiteralString(const std::string& s) + : lit_(s) + , init_is_word_(false) + , is_word_(false) + {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::string lit_; + mutable bool init_is_word_; + mutable bool is_word_; +}; + +class CharacterClass : public Ope + , public std::enable_shared_from_this +{ +public: + CharacterClass(const std::string& s) { + auto chars = decode(s.c_str(), s.length()); + auto i = 0u; + while (i < chars.size()) { + if (i + 2 < chars.size() && chars[i + 1] == '-') { + auto cp1 = chars[i]; + auto cp2 = chars[i + 2]; + ranges_.emplace_back(std::make_pair(cp1, cp2)); + i += 3; + } else { + auto cp = chars[i]; + ranges_.emplace_back(std::make_pair(cp, cp)); + i += 1; + } + } + } + + CharacterClass(const std::vector>& ranges) : ranges_(ranges) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("CharacterClass", s, n, sv, dt); + + if (n < 1) { + c.set_error_pos(s); + return static_cast(-1); + } + + char32_t cp; + auto len = decode_codepoint(s, n, cp); + + if (!ranges_.empty()) { + for (const auto& range: ranges_) { + if (range.first <= cp && cp <= range.second) { + return len; + } + } + } + + c.set_error_pos(s); + return static_cast(-1); + } + + void accept(Visitor& v) override; + + std::vector> ranges_; +}; + +class Character : public Ope + , public std::enable_shared_from_this +{ +public: + Character(char ch) : ch_(ch) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Character", s, n, sv, dt); + if (n < 1 || s[0] != ch_) { + c.set_error_pos(s); + return static_cast(-1); + } + return 1; + } + + void accept(Visitor& v) override; + + char ch_; +}; + +class AnyCharacter : public Ope + , public std::enable_shared_from_this +{ +public: + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("AnyCharacter", s, n, sv, dt); + auto len = codepoint_length(s, n); + if (len < 1) { + c.set_error_pos(s); + return static_cast(-1); + } + return len; + } + + void accept(Visitor& v) override; +}; + +class CaptureScope : public Ope +{ +public: + CaptureScope(const std::shared_ptr& ope) + : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.push_capture_scope(); + auto se = make_scope_exit([&]() { + c.pop_capture_scope(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + return len; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class Capture : public Ope +{ +public: + typedef std::function MatchAction; + + Capture(const std::shared_ptr& ope, MatchAction ma) + : ope_(ope), match_action_(ma) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + if (success(len) && match_action_) { + match_action_(s, len, c); + } + return len; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; + MatchAction match_action_; +}; + +class TokenBoundary : public Ope +{ +public: + TokenBoundary(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class Ignore : public Ope +{ +public: + Ignore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& /*sv*/, Context& c, any& dt) const override { + const auto& rule = *ope_; + auto& chldsv = c.push(); + auto se = make_scope_exit([&]() { + c.pop(); + }); + return rule.parse(s, n, chldsv, c, dt); + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +typedef std::function Parser; + +class User : public Ope +{ +public: + User(Parser fn) : fn_(fn) {} + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("User", s, n, sv, dt); + assert(fn_); + return fn_(s, n, sv, dt); + } + void accept(Visitor& v) override; + std::function fn_; +}; + +class WeakHolder : public Ope +{ +public: + WeakHolder(const std::shared_ptr& ope) : weak_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + auto ope = weak_.lock(); + assert(ope); + const auto& rule = *ope; + return rule.parse(s, n, sv, c, dt); + } + + void accept(Visitor& v) override; + + std::weak_ptr weak_; +}; + +class Holder : public Ope +{ +public: + Holder(Definition* outer) + : outer_(outer) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + any reduce(SemanticValues& sv, any& dt) const; + + std::shared_ptr ope_; + Definition* outer_; + + friend class Definition; +}; + +typedef std::unordered_map Grammar; + +class Reference : public Ope + , public std::enable_shared_from_this +{ +public: + Reference( + const Grammar& grammar, + const std::string& name, + const char* s, + bool is_macro, + const std::vector>& args) + : grammar_(grammar) + , name_(name) + , s_(s) + , is_macro_(is_macro) + , args_(args) + , rule_(nullptr) + , iarg_(0) + {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::shared_ptr get_core_operator() const; + + const Grammar& grammar_; + const std::string name_; + const char* s_; + + const bool is_macro_; + const std::vector> args_; + + Definition* rule_; + size_t iarg_; +}; + +class Whitespace : public Ope +{ +public: + Whitespace(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + if (c.in_whitespace) { + return 0; + } + c.in_whitespace = true; + auto se = make_scope_exit([&]() { c.in_whitespace = false; }); + const auto& rule = *ope_; + return rule.parse(s, n, sv, c, dt); + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class BackReference : public Ope +{ +public: + BackReference(const std::string& name) : name_(name) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::string name_; +}; + +/* + * Factories + */ +template +std::shared_ptr seq(Args&& ...args) { + return std::make_shared(static_cast>(args)...); +} + +template +std::shared_ptr cho(Args&& ...args) { + return std::make_shared(static_cast>(args)...); +} + +inline std::shared_ptr zom(const std::shared_ptr& ope) { + return std::make_shared(ope); +} + +inline std::shared_ptr oom(const std::shared_ptr& ope) { + return std::make_shared(ope); +} + +inline std::shared_ptr opt(const std::shared_ptr& ope) { + return std::make_shared