Merge branch 'master' into 2474-server-status

This commit is contained in:
tooomm 2019-02-04 18:54:50 +01:00
commit 894828962b
146 changed files with 18357 additions and 12225 deletions

View file

@ -24,66 +24,25 @@ clone_depth: 50 #same as travis, see https://www.appveyor.com/blog/2014/06/04
image: Visual Studio 2017 image: Visual Studio 2017
cache: cache:
- c:\openssl-release - c:\Tools\vcpkg\installed
- 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
environment: environment:
openssl_ver: 1.0.2o
protobuf_ver: 3.6.0
zlib_ver: 1.2.11
matrix: matrix:
- target_arch: win64 - target_arch: win64
qt_ver: 5.9\msvc2017_64 qt_ver: 5.9\msvc2017_64
cmake_generator: Visual Studio 15 2017 Win64 cmake_generator: Visual Studio 15 2017 Win64
cmake_toolset: v141,host=x64 cmake_toolset: v141,host=x64
vc_arch: amd64 vcpkg_arch: x64
- target_arch: win32 - target_arch: win32
qt_ver: 5.9\msvc2015 # Qt doesn't provide a msvc2017_32 qt_ver: 5.9\msvc2015 # Qt doesn't provide a msvc2017_32
cmake_generator: Visual Studio 15 2017 cmake_generator: Visual Studio 15 2017
cmake_toolset: v141 cmake_toolset: v141
vc_arch: amd64_x86 vcpkg_arch: x86
install: install:
- ps: | - vcpkg remove --outdated --recurse
if (Test-Path c:\openssl-release) { - vcpkg install openssl protobuf liblzma zlib --triplet %vcpkg_arch%-windows
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
}
services: services:
- mysql - mysql
@ -92,13 +51,10 @@ build_script:
- ps: | - ps: |
New-Item -ItemType directory -Path $env:APPVEYOR_BUILD_FOLDER\build New-Item -ItemType directory -Path $env:APPVEYOR_BUILD_FOLDER\build
Set-Location -Path $env:APPVEYOR_BUILD_FOLDER\build Set-Location -Path $env:APPVEYOR_BUILD_FOLDER\build
$zlibdir = "c:\zlib-release" $vcpkgbindir = "C:\Tools\vcpkg\installed\$env:vcpkg_arch-windows\bin"
$openssldir = "C:\openssl-release"
$protodir = "c:\protobuf-release"
$protoc = "c:\protobuf-release\bin\protoc.exe"
$mysqldll = "c:\Program Files\MySQL\MySQL Server 5.7\lib\libmysql.dll" $mysqldll = "c:\Program Files\MySQL\MySQL Server 5.7\lib\libmysql.dll"
cmake --version 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 - msbuild PACKAGE.vcxproj /p:Configuration=Release
- ps: | - ps: |
$exe = dir -name *.exe $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 (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" Push-AppveyorArtifact "latest-$env:target_arch"
$version = $matches['content'] $version = $matches['content']
test: off test: off

19
.ci/Fedora29/Dockerfile Normal file
View file

@ -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

View file

@ -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/*

View file

@ -1,59 +1,149 @@
#!/bin/bash #!/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 <<EOM
***********************************************************
*** ***
*** Your code does not comply with our styleguide. ***
*** ***
*** Please correct it or run the "clangify.sh" script. ***
*** Then commit and push those changes to this branch. ***
*** Check our CONTRIBUTING.md file for more details. ***
*** ***
*** Thank you ♥ ***
*** ***
***********************************************************
Used clang-format version:
${diff%%
*}
The following changes should be made:
${diff#*
}
Exiting...
EOM
exit 2
;;
0)
echo "Thank you for complying with our code standards."
;;
*)
echo "Something went wrong in our formatting checks: clangify returned $err" >&2
;;
esac
fi
set -e set -e
# Setup
./servatrice/check_schema_version.sh ./servatrice/check_schema_version.sh
mkdir -p build mkdir -p build
cd build cd build
prefix=""
if [[ $TRAVIS_OS_NAME == "osx" ]]; then # Add cmake flags
export PATH="/usr/local/opt/ccache/bin:$PATH" if [[ $MAKE_SERVER ]]; then
prefix="-DCMAKE_PREFIX_PATH=$(echo /usr/local/opt/qt5/)" flags+=" -DWITH_SERVER=1"
fi fi
if [[ $TRAVIS_OS_NAME == "linux" ]]; then if [[ $MAKE_TEST ]]; then
prefix="-DCMAKE_PREFIX_PATH=$(echo /opt/qt5*/lib/cmake/)" 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 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 --version
cmake .. $flags
make -j2
if [[ $BUILDTYPE == "Debug" ]]; then if [[ $MAKE_TEST ]]; then
cmake .. -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=$BUILDTYPE $prefix -DTEST=1
make -j2
make test 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 fi
if [[ $BUILDTYPE == "Release" ]]; then if [[ $MAKE_INSTALL ]]; then
cmake .. -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=$BUILDTYPE $prefix make install
make package -j2 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 fi

View file

@ -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

View file

@ -21,6 +21,20 @@ If you have any questions on IDEs, feel free to chat with us on [Gitter](https:/
# Code Style Guide # # 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 ### ### Compatibility ###
Cockatrice is currently compiled on all platforms using <kbd>C++11</kbd>. You'll notice <kbd>C++03</kbd> code throughout the codebase. Please feel free to help convert it over! Cockatrice is currently compiled on all platforms using <kbd>C++11</kbd>. You'll notice <kbd>C++03</kbd> code throughout the codebase. Please feel free to help convert it over!
@ -28,7 +42,17 @@ Cockatrice is currently compiled on all platforms using <kbd>C++11</kbd>. You'll
For consistency, we use Qt data structures where possible. For example, `QString` over For consistency, we use Qt data structures where possible. For example, `QString` over
`std::string` and `QList` over `std::vector`. `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 <filename>` 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 Use header files with the extension `.h` and source files with the extension
`.cpp`. `.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, Simple functions, such as getters, may be written inline in the header file,
but other functions should be written in the source 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++ ```c++
// Good
#include <QList>
#include <QString>
#include "card.h"
#include "deck.h"
// Good // Good
#include "card.h" #include "card.h"
#include "deck.h" #include "deck.h"
#include <QList> #include <QList>
#include <QString> #include <QString>
// Bad: // Bad
#include <QList> #include <QList>
#include "card.h" #include "card.h"
#include <QString> #include <QString>
#include "deck.h" #include "deck.h"
// Bad
#include "card.h"
#include "deck.h"
#include <QString>
#include <QList>
``` ```
### Naming ### #### Naming ####
Use `UpperCamelCase` for classes, structs, enums, etc. and `lowerCamelCase` for Use `UpperCamelCase` for classes, structs, enums, etc. and `lowerCamelCase` for
function and variable names. function and variable names.
@ -89,16 +113,16 @@ Bar& bar2 = *bar1;
Use `nullptr` instead of `NULL` (or `0`) for null pointers. 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. 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. 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: See the following example:
```c++ ```c++
int main() int main()
{ // function or class: own line { // function or class: own line
if (someCondition) { // control statement: same line if (someCondition) { // control statement: same line
doSomething(); // single line statement, braces preferred doSomething(); // single line statement, braces preferred
} else if (someOtherCondition1) { // else goes after closing brace } else if (someOtherCondition1) { // else goes on the same line as a closing brace
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
doSomethingElse(); 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. 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). 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.
See [the clang-format documentation](https://clang.llvm.org/docs/ClangFormat.html) for more information.
### Memory Management ### ### Memory Management ###
@ -177,35 +200,32 @@ You can find more information on how we use Protobuf on [our wiki!](https://gith
# Translations # # Translations #
**Basic workflow for translations:** Basic workflow for translations:
1. Developer adds a `tr("foo")` string in the code; 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; 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; 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. 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.
All the user-interface strings inside Cockatrice's source code must be written in
english language.<br>
Translations to other languages are managed using [Transifex](https://www.transifex.com/projects/p/cockatrice/). 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 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.
in the code, you don't need to take care of adding the new strings to the For example setting the text of this label in a way that the string "My name is:" can be translated:
translation files. Every few days, or when a lot of new strings have been added, ```c++
someone from the development team will take care of extracing all the new strings, nameLabel.setText(tr("My name is:"));
adding them to the english translation files and making them available to ```
translators on Transifex.
### 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 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. the new strings and add them to the english translation files.
To update the english translation files, re-run cmake enabling the appropriate 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 cockatrice/translations/cockatrice_en.ts
oracle/translations/oracle_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 ```sh
cmake .. -DUPDATE_TRANSLATIONS=OFF cmake .. -DUPDATE_TRANSLATIONS=OFF
``` ```
Now you are ready to propose your change. 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 (checked every 24 hours)
Once your change gets merged, Transifex will pick up the modified files automatically (checks every 24 hours)
and update the interface where translators will be able to translate the new strings. 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. 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 This can be done manually from the Transifex web interface, but it's quite time
consuming. 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 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. from Transifex to the source code and vice versa.
### Translations (for translators) ### ### Adding Translations (for translators) ###
**Step 4: Editing translations at Transifex**
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). 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:** 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. **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 operations is: The preferred flow of operation is:
* just before a release, update the version number in CMakeLists.txt to "next release version"; * 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; * 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 * 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. * 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.

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ mysql.cnf
.idea/ .idea/
*.aps *.aps
cmake-build-debug/ cmake-build-debug/
preferences

View file

@ -1,54 +1,114 @@
language: cpp language: cpp
compiler: gcc compiler: gcc
cache: ccache
matrix: matrix:
include: include:
#Ubuntu #Ubuntu Xenial (Debug only)
- name: Ubuntu (Debug) - name: Ubuntu Xenial (Debug)
if: tag IS NOT present if: tag IS NOT present
os: linux os: linux
dist: xenial dist: xenial
group: stable group: stable
env: BUILDTYPE=Debug cache: ccache
- name: Ubuntu (Release) 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 if: (branch = master AND NOT type = pull_request) OR tag IS present
os: linux services: docker
dist: xenial env: NAME=UbuntuBionic
group: stable cache:
env: BUILDTYPE=Release 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 #macOS
- name: macOS (Debug) - name: macOS (Debug)
if: tag IS NOT present if: tag IS NOT present
os: osx os: osx
osx_image: xcode8 osx_image: xcode10.1
env: BUILDTYPE=Debug cache: ccache
addons:
homebrew:
packages:
- ccache
- protobuf
- qt
- xz
script: bash ./.ci/travis-compile.sh --server --install --debug
- name: macOS (Release) - name: macOS (Release)
if: (branch = master AND NOT type = pull_request) OR tag IS present if: (branch = master AND NOT type = pull_request) OR tag IS present
os: osx os: osx
osx_image: xcode8 osx_image: xcode9.2
env: BUILDTYPE=Release cache: ccache
addons:
#install dependencies for container-based "linux" builds homebrew:
addons: packages:
apt: - ccache
packages: - protobuf
- libprotobuf-dev - qt
- protobuf-compiler - xz
- qt5-default update: true
- qttools5-dev script: bash ./.ci/travis-compile.sh --server --package "$TRAVIS_OS_NAME" --release
- 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
# Builds for pull requests skip the deployment step altogether # Builds for pull requests skip the deployment step altogether
deploy: deploy:
@ -67,7 +127,7 @@ deploy:
on: on:
tags: true tags: true
repo: Cockatrice/Cockatrice 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 # Deploy configuration for "stable" releases
- provider: releases - provider: releases
@ -82,7 +142,7 @@ deploy:
on: on:
tags: true tags: true
repo: Cockatrice/Cockatrice 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: notifications:

View file

@ -34,7 +34,7 @@ endif()
# A project name is needed for CPack # A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake # 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 # Use c++11 for all targets
set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ ISO Standard") 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) IF(MSVC)
# Visual Studio: # Visual Studio:
# Maximum optimization # Maximum optimization
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD") # Disable warning C4251
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD /wd4251")
# Generate complete debugging information # Generate complete debugging information
#set(CMAKE_CXX_FLAGS_DEBUG "/Zi") #set(CMAKE_CXX_FLAGS_DEBUG "/Zi")
ELSEIF (CMAKE_COMPILER_IS_GNUCXX) ELSEIF (CMAKE_COMPILER_IS_GNUCXX)
@ -171,7 +172,7 @@ IF(MSVC)
ENDIF() ENDIF()
# Package builder # Package builder
set(CPACK_PACKAGE_CONTACT "Gavin Bisesi <Daenyth+github@gmail.com>") set(CPACK_PACKAGE_CONTACT "Zach Halpern <zahalpern+github@gmail.com>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_NAME}) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_NAME})
set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team") set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md") set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md")

View file

@ -1,30 +1,21 @@
FROM ubuntu:trusty FROM ubuntu:bionic
MAINTAINER Gavin Bisesi <Daenyth@gmail.com> MAINTAINER Zach Halpern <zahalpern+github@gmail.com>
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\ RUN apt-get update && apt-get install -y\
build-essential g++\ build-essential\
cmake\ cmake\
git\ git\
libprotobuf-dev\ libprotobuf-dev\
libqt5sql5-mysql\
libqt5websockets5-dev\
protobuf-compiler\ protobuf-compiler\
qt5-default\ qt5-default\
qtbase5-dev\ qtbase5-dev\
qttools5-dev-tools\ qttools5-dev-tools\
qttools5-dev\ qttools5-dev
libqt5sql5-mysql
ENV dir /home/servatrice/code COPY . /home/servatrice/code/
WORKDIR $dir WORKDIR /home/servatrice/code
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
WORKDIR build WORKDIR build
RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 &&\ RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 &&\
@ -35,4 +26,4 @@ WORKDIR /home/servatrice
EXPOSE 4747 EXPOSE 4747
ENTRYPOINT [ "servatrice" ] CMD [ "servatrice", "--log-to-console" ]

View file

@ -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)** **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/) - [Qt](https://www.qt.io/developers/)
- [protobuf](https://github.com/google/protobuf) - [protobuf](https://github.com/google/protobuf)
- [CMake](https://www.cmake.org/) - [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/) - [zlib](https://www.zlib.net/)
- [xz](https://tukaani.org/xz/)
To compile: To compile:

View file

@ -1,22 +1,209 @@
#!/bin/bash #!/bin/bash
# This script will run clang-format on all modified, non-3rd-party C++/Header files. # 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 # go to the project root directory, this file should be located in the project root directory
files_to_clean=($(git diff --name-only $(git merge-base origin/master HEAD))) cd "${BASH_SOURCE%/*}/" || exit 2 # could not find path, this could happen with special links etc.
printf "%s\n" ${files_to_clean[@]} | \ # defaults
xargs -I{} find '{}' \( -name "*.cpp" -o -name "*.h" \) \ include=("common" \
-not -path "./cockatrice/src/qt-json/*" \ "cockatrice/src" \
-not -path "./servatrice/src/smtp/*" \ "oracle/src" \
-not -path "./common/sfmt/*" \ "servatrice/src")
-not -path "./oracle/src/zip/*" \ exclude=("servatrice/src/smtp" \
-not -path "./build*/*" \ "common/sfmt" \
-exec clang-format -style=file -i {} \; "common/lib" \
echo "Successfully formatted following files:" "oracle/src/zip" \
printf "%s\n" ${files_to_clean[@]} "oracle/src/lzma" \
else "oracle/src/qt-json")
echo "Please install clang-format and git to use this program" 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 <<EOM
A bash script to automatically format your code using clang-format.
If no options are given, all dirty source files are edited in place.
If <dir>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. <dir> 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 <git branch or object>] [<dir> ...]
DEFAULTS:
Current includes are:
${include[@]/%/
}
Default excludes are:
${exclude[@]/%/
}
OPTIONS:
-b, --branch <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 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

View file

@ -1,36 +1,36 @@
# Find the MS Visual Studio VC redistributable package # Find the MS Visual Studio VC redistributable package
if (WIN32) if (WIN32)
set(VCREDISTRUNTIME_FOUND "NO") set(VCREDISTRUNTIME_FOUND "NO")
if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64-bit if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64-bit
set(REDIST_ARCH x64) set(REDIST_ARCH x64)
else() else()
set(REDIST_ARCH x86) set(REDIST_ARCH x86)
endif() endif()
set(REDIST_FILE vc_redist.${REDIST_ARCH}.exe) set(REDIST_FILE vcredist_${REDIST_ARCH}.exe)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries) include(InstallRequiredSystemLibraries)
# Check if the list contains minimum one element, to get the path from # Check if the list contains minimum one element, to get the path from
list(LENGTH CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS libsCount) list(LENGTH CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS libsCount)
if (libsCount GREATER 0) if (libsCount GREATER 0)
list(GET CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS 0 _path) list(GET CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS 0 _path)
get_filename_component(_path ${_path} DIRECTORY) get_filename_component(_path ${_path} DIRECTORY)
get_filename_component(_path ${_path}/../../ ABSOLUTE) get_filename_component(_path ${_path}/../../ ABSOLUTE)
if (EXISTS "${_path}/${REDIST_FILE}") # VS 2017 if (EXISTS "${_path}/${REDIST_FILE}") # VS 2017
set(VCREDISTRUNTIME_FOUND "YES") set(VCREDISTRUNTIME_FOUND "YES")
set(VCREDISTRUNTIME_FILE ${_path}/${REDIST_FILE}) set(VCREDISTRUNTIME_FILE ${_path}/${REDIST_FILE})
endif() endif()
endif() endif()
if(VCREDISTRUNTIME_FOUND) if(VCREDISTRUNTIME_FOUND)
message(STATUS "Found VCredist ${VCREDISTRUNTIME_FILE}") message(STATUS "Found VCredist ${VCREDISTRUNTIME_FILE}")
else() else()
message(WARNING "Could not find VCredist package. It's not required for compiling, but needs to be available at runtime.") message(WARNING "Could not find VCredist package. It's not required for compiling, but needs to be available at runtime.")
endif() endif()
endif() endif()

View file

@ -1,69 +1,71 @@
# Find the OpenSSL runtime libraries (.dll) for Windows that # Find the OpenSSL runtime libraries (.dll) for Windows that
# will be needed by Qt in order to access https urls. # will be needed by Qt in order to access https urls.
if (WIN32) if (WIN32)
# Get standard installation paths for OpenSSL under Windows # Get standard installation paths for OpenSSL under Windows
# http://www.slproweb.com/products/Win32OpenSSL.html # http://www.slproweb.com/products/Win32OpenSSL.html
if( CMAKE_SIZEOF_VOID_P EQUAL 8 ) if( CMAKE_SIZEOF_VOID_P EQUAL 8 )
# target win64 # target win64
set(_OPENSSL_ROOT_HINTS set(_OPENSSL_ROOT_HINTS
${OPENSSL_ROOT_DIR} ${OPENSSL_ROOT_DIR}
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]"
ENV OPENSSL_ROOT_DIR ENV OPENSSL_ROOT_DIR
) )
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
set(_OPENSSL_ROOT_PATHS set(_OPENSSL_ROOT_PATHS
"${_programfiles}/OpenSSL-Win64" "C:/Tools/vcpkg/installed/x64-windows/bin"
"C:/OpenSSL-Win64/" "${_programfiles}/OpenSSL-Win64"
) "C:/OpenSSL-Win64/"
unset(_programfiles) )
else( CMAKE_SIZEOF_VOID_P EQUAL 8 ) unset(_programfiles)
# target win32 else( CMAKE_SIZEOF_VOID_P EQUAL 8 )
set(_OPENSSL_ROOT_HINTS # target win32
${OPENSSL_ROOT_DIR} set(_OPENSSL_ROOT_HINTS
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" ${OPENSSL_ROOT_DIR}
ENV 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 file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
"${_programfiles}/OpenSSL" set(_OPENSSL_ROOT_PATHS
"${_programfiles}/OpenSSL-Win32" "C:/Tools/vcpkg/installed/x86-windows/bin"
"C:/OpenSSL/" "${_programfiles}/OpenSSL"
"C:/OpenSSL-Win32/" "${_programfiles}/OpenSSL-Win32"
) "C:/OpenSSL/"
unset(_programfiles) "C:/OpenSSL-Win32/"
endif( CMAKE_SIZEOF_VOID_P EQUAL 8 ) )
unset(_programfiles)
else () endif( CMAKE_SIZEOF_VOID_P EQUAL 8 )
set(_OPENSSL_ROOT_HINTS
${OPENSSL_ROOT_DIR} else ()
ENV OPENSSL_ROOT_DIR set(_OPENSSL_ROOT_HINTS
) ${OPENSSL_ROOT_DIR}
endif () ENV OPENSSL_ROOT_DIR
)
set(_OPENSSL_ROOT_HINTS_AND_PATHS endif ()
HINTS ${_OPENSSL_ROOT_HINTS}
PATHS ${_OPENSSL_ROOT_PATHS} 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}) # For OpenSSL < 1.1, they are named libeay32 and ssleay32 and even if the dll is 64bit, it's still suffixed as *32.dll
FIND_FILE(WIN32SSLRUNTIME_SSLEAY NAMES ssleay32.dll libssl.dll ${_OPENSSL_ROOT_HINTS_AND_PATHS}) # 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") IF(WIN32SSLRUNTIME_LIBEAY AND WIN32SSLRUNTIME_SSLEAY)
message(STATUS "Found OpenSSL ${WIN32SSLRUNTIME_LIBRARIES}") SET(WIN32SSLRUNTIME_LIBRARIES "${WIN32SSLRUNTIME_LIBEAY}" "${WIN32SSLRUNTIME_SSLEAY}")
ELSE() SET(WIN32SSLRUNTIME_FOUND "YES")
SET(WIN32SSLRUNTIME_FOUND "NO") message(STATUS "Found OpenSSL ${WIN32SSLRUNTIME_LIBRARIES}")
message(WARNING "Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime.") ELSE()
ENDIF() 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.")
MARK_AS_ADVANCED( ENDIF()
WIN32SSLRUNTIME_LIBEAY
WIN32SSLRUNTIME_SSLEAY MARK_AS_ADVANCED(
) WIN32SSLRUNTIME_LIBEAY
WIN32SSLRUNTIME_SSLEAY
)

View file

@ -34,5 +34,7 @@
<string>${MACOSX_BUNDLE_COPYRIGHT}</string> <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<true/> <true/>
<key>NSRequiresAquaSystemAppearance</key>
<true/>
</dict> </dict>
</plist> </plist>

View file

@ -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" "VersionMajor" "@CPACK_PACKAGE_VERSION_MAJOR@"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "VersionMinor" "@CPACK_PACKAGE_VERSION_MINOR@" 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: 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..." DetailPrint "Sleep to ensure unlock of vc_redist file after installation..."
Sleep 3000 Sleep 3000
Delete "$INSTDIR\vc_redist.x86.exe" Delete "$INSTDIR\vcredist_x86.exe"
PastVcRedist86Check: PastVcRedist86Check:
IfFileExists "$INSTDIR\vc_redist.x64.exe" VcRedist64Exists PastVcRedist64Check IfFileExists "$INSTDIR\vcredist_x64.exe" VcRedist64Exists PastVcRedist64Check
VcRedist64Exists: 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..." DetailPrint "Sleep to ensure unlock of vc_redist file after installation..."
Sleep 3000 Sleep 3000
Delete "$INSTDIR\vc_redist.x64.exe" Delete "$INSTDIR\vcredist_x64.exe"
PastVcRedist64Check: PastVcRedist64Check:
${Else} ${Else}

View file

@ -100,7 +100,6 @@ SET(cockatrice_SOURCES
src/localserver.cpp src/localserver.cpp
src/localserverinterface.cpp src/localserverinterface.cpp
src/localclient.cpp src/localclient.cpp
src/qt-json/json.cpp
src/soundengine.cpp src/soundengine.cpp
src/pending_command.cpp src/pending_command.cpp
src/pictureloader.cpp src/pictureloader.cpp
@ -114,15 +113,18 @@ SET(cockatrice_SOURCES
src/settings/messagesettings.cpp src/settings/messagesettings.cpp
src/settings/gamefilterssettings.cpp src/settings/gamefilterssettings.cpp
src/settings/layoutssettings.cpp src/settings/layoutssettings.cpp
src/settings/downloadsettings.cpp
src/update_downloader.cpp src/update_downloader.cpp
src/logger.cpp src/logger.cpp
src/releasechannel.cpp src/releasechannel.cpp
src/userconnection_information.cpp src/userconnection_information.cpp
src/spoilerbackgroundupdater.cpp src/spoilerbackgroundupdater.cpp
src/handle_public_servers.cpp src/handle_public_servers.cpp
src/carddbparser/carddatabaseparser.cpp
src/carddbparser/cockatricexml3.cpp src/carddbparser/cockatricexml3.cpp
src/carddbparser/cockatricexml4.cpp
${VERSION_STRING_CPP} ${VERSION_STRING_CPP}
) )
add_subdirectory(sounds) add_subdirectory(sounds)
add_subdirectory(themes) add_subdirectory(themes)
@ -149,8 +151,8 @@ if(APPLE)
ENDIF(APPLE) ENDIF(APPLE)
# Qt5 # Qt5
find_package(Qt5 COMPONENTS Concurrent Multimedia Network PrintSupport Svg Widgets REQUIRED) 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) set(COCKATRICE_QT_MODULES Qt5::Concurrent Qt5::Multimedia Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Widgets Qt5::WebSockets)
# Qt5LinguistTools # Qt5LinguistTools
find_package(Qt5LinguistTools) find_package(Qt5LinguistTools)
@ -254,6 +256,8 @@ if(WIN32)
set(plugin_dest_dir Plugins) set(plugin_dest_dir Plugins)
set(qtconf_dest_dir .) 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 # qt5 plugins: audio, iconengines, imageformats, platforms, printsupport
install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime
FILES_MATCHING REGEX "(audio|iconengines|imageformats|platforms|printsupport)/.*[^d]\\.dll") FILES_MATCHING REGEX "(audio|iconengines|imageformats|platforms|printsupport)/.*[^d]\\.dll")

View file

@ -351,6 +351,7 @@
<file>resources/tips/images/cockatrice_register.png</file> <file>resources/tips/images/cockatrice_register.png</file>
<file>resources/tips/images/cockatrice_wiki.png</file> <file>resources/tips/images/cockatrice_wiki.png</file>
<file>resources/tips/images/coin_flip.png</file> <file>resources/tips/images/coin_flip.png</file>
<file>resources/tips/images/counter_expression.png</file>
<file>resources/tips/images/face_down.png</file> <file>resources/tips/images/face_down.png</file>
<file>resources/tips/images/filter_games.png</file> <file>resources/tips/images/filter_games.png</file>
<file>resources/tips/images/github_logo.png</file> <file>resources/tips/images/github_logo.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -83,4 +83,10 @@
<image>face_down.png</image> <image>face_down.png</image>
<date>2018-03-01</date> <date>2018-03-01</date>
</tip> </tip>
<tip>
<title>Counter expressions</title>
<text>When setting a counter value, you can type a math expression in the box and the counter will be set to the result.&lt;br&gt;The "x" variable contains the current counter value.</text>
<image>counter_expression.png</image>
<date>2019-02-02</date>
</tip>
</tips> </tips>

View file

@ -1,9 +1,11 @@
#include "abstractcounter.h" #include "abstractcounter.h"
#include "expression.h"
#include "pb/command_inc_counter.pb.h" #include "pb/command_inc_counter.pb.h"
#include "pb/command_set_counter.pb.h" #include "pb/command_set_counter.pb.h"
#include "player.h" #include "player.h"
#include "settingscache.h" #include "settingscache.h"
#include <QAction> #include <QAction>
#include <QApplication>
#include <QGraphicsSceneHoverEvent> #include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QMenu> #include <QMenu>
@ -17,7 +19,7 @@ AbstractCounter::AbstractCounter(Player *_player,
bool _useNameForShortcut, bool _useNameForShortcut,
QGraphicsItem *parent) QGraphicsItem *parent)
: QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value), : 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) deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea)
{ {
setAcceptHoverEvents(true); setAcceptHoverEvents(true);
@ -46,7 +48,7 @@ AbstractCounter::AbstractCounter(Player *_player,
} else } else
menu = nullptr; menu = nullptr;
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts(); refreshShortcuts();
retranslateUi(); retranslateUi();
} }
@ -114,7 +116,11 @@ void AbstractCounter::setValue(int _value)
void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
{ {
if (isUnderMouse() && player->getLocal()) { 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; Command_IncCounter cmd;
cmd.set_counter_id(id); cmd.set_counter_id(id);
cmd.set_delta(1); cmd.set_delta(1);
@ -126,10 +132,6 @@ void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
cmd.set_delta(-1); cmd.set_delta(-1);
player->sendGameCommand(cmd); player->sendGameCommand(cmd);
event->accept(); event->accept();
} else if (event->button() == Qt::MidButton) {
if (menu)
menu->exec(event->screenPos());
event->accept();
} }
} else } else
event->ignore(); event->ignore();
@ -160,8 +162,12 @@ void AbstractCounter::setCounter()
{ {
bool ok; bool ok;
dialogSemaphore = true; dialogSemaphore = true;
int newValue = QInputDialog::getInt(0, tr("Set counter"), tr("New value for counter '%1':").arg(name), value, QString expression = QInputDialog::getText(nullptr, tr("Set counter"), tr("New value for counter '%1':").arg(name),
-2000000000, 2000000000, 1, &ok); QLineEdit::Normal, QString::number(value), &ok);
Expression exp(value);
int newValue = static_cast<int>(exp.parse(expression));
if (deleteAfterDialog) { if (deleteAfterDialog) {
deleteLater(); deleteLater();
return; return;

View file

@ -11,6 +11,7 @@ class AbstractCounter : public QObject, public QGraphicsItem
{ {
Q_OBJECT Q_OBJECT
Q_INTERFACES(QGraphicsItem) Q_INTERFACES(QGraphicsItem)
protected: protected:
Player *player; Player *player;
int id; int id;
@ -18,15 +19,17 @@ protected:
int value; int value;
bool useNameForShortcut, hovered; bool useNameForShortcut, hovered;
void mousePressEvent(QGraphicsSceneMouseEvent *event); void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event); void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private: private:
QAction *aSet, *aDec, *aInc; QAction *aSet, *aDec, *aInc;
QMenu *menu; QMenu *menu;
bool dialogSemaphore, deleteAfterDialog; bool dialogSemaphore, deleteAfterDialog;
bool shownInCounterArea; bool shownInCounterArea;
bool shortcutActive;
private slots: private slots:
void refreshShortcuts(); void refreshShortcuts();
void incrementCounter(); void incrementCounter();
@ -39,14 +42,19 @@ public:
bool _shownInCounterArea, bool _shownInCounterArea,
int _value, int _value,
bool _useNameForShortcut = false, bool _useNameForShortcut = false,
QGraphicsItem *parent = 0); QGraphicsItem *parent = nullptr);
~AbstractCounter(); ~AbstractCounter() override;
void retranslateUi();
void setValue(int _value);
void setShortcutsActive();
void setShortcutsInactive();
void delCounter();
QMenu *getMenu() const QMenu *getMenu() const
{ {
return menu; return menu;
} }
void retranslateUi();
int getId() const int getId() const
{ {
@ -64,12 +72,6 @@ public:
{ {
return value; return value;
} }
void setValue(int _value);
void delCounter();
void setShortcutsActive();
void setShortcutsInactive();
bool shortcutActive;
}; };
#endif #endif

View file

@ -1,5 +1,7 @@
#include "carddatabase.h" #include "carddatabase.h"
#include "carddbparser/cockatricexml3.h" #include "carddbparser/cockatricexml3.h"
#include "carddbparser/cockatricexml4.h"
#include "game_specific_terms.h"
#include "pictureloader.h" #include "pictureloader.h"
#include "settingscache.h" #include "settingscache.h"
#include "spoilerbackgroundupdater.h" #include "spoilerbackgroundupdater.h"
@ -207,30 +209,23 @@ void SetList::guessSortKeys()
} }
} }
CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set)
{
}
CardInfo::CardInfo(const QString &_name, CardInfo::CardInfo(const QString &_name,
bool _isToken,
const QString &_manacost,
const QString &_cmc,
const QString &_cardtype,
const QString &_powtough,
const QString &_text, const QString &_text,
const QStringList &_colors, bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards, const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards, const QList<CardRelation *> &_reverseRelatedCards,
bool _upsideDownArt, CardInfoPerSetMap _sets,
const QString &_loyalty,
bool _cipt, bool _cipt,
int _tableRow, int _tableRow,
const SetList &_sets, bool _upsideDownArt)
const QStringMap &_customPicURLs, : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
MuidMap _muIds, reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), tableRow(_tableRow),
QStringMap _collectorNumbers, upsideDownArt(_upsideDownArt)
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)
{ {
pixmapCacheKey = QLatin1String("card_") + name; pixmapCacheKey = QLatin1String("card_") + name;
simpleName = CardInfo::simplifyName(name); simpleName = CardInfo::simplifyName(name);
@ -244,76 +239,27 @@ CardInfo::~CardInfo()
} }
CardInfoPtr CardInfo::newInstance(const QString &_name, CardInfoPtr CardInfo::newInstance(const QString &_name,
bool _isToken,
const QString &_manacost,
const QString &_cmc,
const QString &_cardtype,
const QString &_powtough,
const QString &_text, const QString &_text,
const QStringList &_colors, bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards, const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards, const QList<CardRelation *> &_reverseRelatedCards,
bool _upsideDownArt, CardInfoPerSetMap _sets,
const QString &_loyalty,
bool _cipt, bool _cipt,
int _tableRow, int _tableRow,
const SetList &_sets, bool _upsideDownArt)
const QStringMap &_customPicURLs,
MuidMap _muIds,
QStringMap _collectorNumbers,
QStringMap _rarities)
{ {
CardInfoPtr ptr(new CardInfo(_name, _isToken, _manacost, _cmc, _cardtype, _powtough, _text, _colors, _relatedCards, CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
_reverseRelatedCards, _upsideDownArt, _loyalty, _cipt, _tableRow, _sets, _sets, _cipt, _tableRow, _upsideDownArt));
_customPicURLs, std::move(_muIds), std::move(_collectorNumbers),
std::move(_rarities)));
ptr->setSmartPointer(ptr); ptr->setSmartPointer(ptr);
for (int i = 0; i < _sets.size(); i++) { for (const CardInfoPerSet &set : _sets) {
_sets[i]->append(ptr); set.getPtr()->append(ptr);
} }
return 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 CardInfo::getCorrectedName() const
{ {
QString result = name; QString result = name;
@ -321,26 +267,21 @@ QString CardInfo::getCorrectedName() const
return result.remove(" // ").remove(':').remove('"').remove('?').replace('/', ' '); return result.remove(" // ").remove(':').remove('"').remove('?').replace('/', ' ');
} }
void CardInfo::addToSet(CardSetPtr set) void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info)
{ {
if (set.isNull()) { _set->append(smartThis);
qDebug() << "addToSet(nullptr)"; sets.insert(_set->getShortName(), _info);
return;
}
set->append(smartThis);
sets << set;
refreshCachedSetNames(); refreshCachedSetNames();
} }
void CardInfo::refreshCachedSetNames() void CardInfo::refreshCachedSetNames()
{ {
// update the cached list of set names
QStringList setList; QStringList setList;
for (int i = 0; i < sets.size(); i++) { // update the cached list of set names
if (sets[i]->getEnabled()) { for (const auto &set : sets) {
setList << sets[i]->getShortName(); if (set.getPtr()->getEnabled()) {
setList << set.getPtr()->getShortName();
} }
} }
setsNames = setList.join(", "); setsNames = setList.join(", ");
@ -369,11 +310,12 @@ QString CardInfo::simplifyName(const QString &name)
const QChar CardInfo::getColorChar() const const QChar CardInfo::getColorChar() const
{ {
QString colors = getColors();
switch (colors.size()) { switch (colors.size()) {
case 0: case 0:
return QChar(); return QChar();
case 1: case 1:
return colors.first().isEmpty() ? QChar() : colors.first().at(0); return colors.at(0);
default: default:
return QChar('m'); return QChar('m');
} }
@ -386,6 +328,7 @@ CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoa
// add new parsers here // add new parsers here
availableParsers << new CockatriceXml3Parser; availableParsers << new CockatriceXml3Parser;
availableParsers << new CockatriceXml4Parser;
for (auto &parser : availableParsers) { for (auto &parser : availableParsers) {
connect(parser, SIGNAL(addCard(CardInfoPtr)), this, SLOT(addCard(CardInfoPtr)), Qt::DirectConnection); connect(parser, SIGNAL(addCard(CardInfoPtr)), this, SLOT(addCard(CardInfoPtr)), Qt::DirectConnection);
@ -417,9 +360,7 @@ void CardDatabase::clear()
simpleNameCards.clear(); simpleNameCards.clear();
sets.clear(); sets.clear();
for (auto parser : availableParsers) { ICardDatabaseParser::clearSetlist();
parser->clearSetlist();
}
loadStatus = NotLoaded; loadStatus = NotLoaded;
@ -436,12 +377,8 @@ void CardDatabase::addCard(CardInfoPtr card)
// if card already exists just add the new set property // if card already exists just add the new set property
if (cards.contains(card->getName())) { if (cards.contains(card->getName())) {
CardInfoPtr sameCard = cards[card->getName()]; CardInfoPtr sameCard = cards[card->getName()];
for (auto set : card->getSets()) { for (const CardInfoPerSet &set : card->getSets()) {
QString setName = set->getCorrectedShortName(); sameCard->addToSet(set.getPtr(), set);
sameCard->setSet(set);
sameCard->setMuId(setName, card->getMuId(setName));
sameCard->setRarity(setName, card->getRarity(setName));
sameCard->setSetNumber(setName, card->getCollectorNumber(setName));
} }
return; return;
} }
@ -582,7 +519,7 @@ LoadStatus CardDatabase::loadCardDatabases()
// load custom card databases // load custom card databases
QDir dir(settingsCache->getCustomCardDatabasePath()); QDir dir(settingsCache->getCustomCardDatabasePath());
for (QString fileName : for (const QString &fileName :
dir.entryList(QStringList("*.xml"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase)) { dir.entryList(QStringList("*.xml"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase)) {
loadCardDatabase(dir.absoluteFilePath(fileName)); loadCardDatabase(dir.absoluteFilePath(fileName));
} }
@ -614,20 +551,13 @@ void CardDatabase::refreshCachedReverseRelatedCards()
continue; 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()) { foreach (CardRelation *cardRelation, card->getReverseRelatedCards()) {
const QString &targetCard = cardRelation->getName(); const QString &targetCard = cardRelation->getName();
if (!cards.contains(targetCard)) { if (!cards.contains(targetCard)) {
continue; continue;
} }
auto *newCardRelation = new CardRelation(relatedCardName, cardRelation->getDoesAttach(), auto *newCardRelation = new CardRelation(card->getName(), cardRelation->getDoesAttach(),
cardRelation->getIsCreateAllExclusion(), cardRelation->getIsCreateAllExclusion(),
cardRelation->getIsVariable(), cardRelation->getDefaultCount()); cardRelation->getIsVariable(), cardRelation->getDefaultCount());
cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation); cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation);
@ -635,23 +565,6 @@ void CardDatabase::refreshCachedReverseRelatedCards()
} }
} }
QStringList CardDatabase::getAllColors() const
{
QSet<QString> colors;
QHashIterator<QString, CardInfoPtr> 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 QStringList CardDatabase::getAllMainCardTypes() const
{ {
QSet<QString> types; QSet<QString> types;
@ -717,8 +630,8 @@ bool CardDatabase::saveCustomTokensToFile()
tmpSets.insert(CardDatabase::TOKENS_SETNAME, customTokensSet); tmpSets.insert(CardDatabase::TOKENS_SETNAME, customTokensSet);
CardNameMap tmpCards; CardNameMap tmpCards;
for (CardInfoPtr card : cards) { for (const CardInfoPtr &card : cards) {
if (card->getSets().contains(customTokensSet)) { if (card->getSets().contains(CardDatabase::TOKENS_SETNAME)) {
tmpCards.insert(card->getName(), card); tmpCards.insert(card->getName(), card);
} }
} }
@ -743,4 +656,46 @@ void CardInfo::resetReverseRelatedCards2Me()
cardRelation->deleteLater(); cardRelation->deleteLater();
} }
reverseRelatedCardsToMe = QList<CardRelation *>(); reverseRelatedCardsToMe = QList<CardRelation *>();
}
// 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);
} }

View file

@ -9,10 +9,13 @@
#include <QMetaType> #include <QMetaType>
#include <QSharedPointer> #include <QSharedPointer>
#include <QStringList> #include <QStringList>
#include <QVariant>
#include <QVector> #include <QVector>
#include <utility>
class CardDatabase; class CardDatabase;
class CardInfo; class CardInfo;
class CardInfoPerSet;
class CardSet; class CardSet;
class CardRelation; class CardRelation;
class ICardDatabaseParser; class ICardDatabaseParser;
@ -21,6 +24,7 @@ typedef QMap<QString, QString> QStringMap;
typedef QMap<QString, int> MuidMap; typedef QMap<QString, int> MuidMap;
typedef QSharedPointer<CardInfo> CardInfoPtr; typedef QSharedPointer<CardInfo> CardInfoPtr;
typedef QSharedPointer<CardSet> CardSetPtr; typedef QSharedPointer<CardSet> CardSetPtr;
typedef QMap<QString, CardInfoPerSet> CardInfoPerSetMap;
Q_DECLARE_METATYPE(CardInfoPtr) Q_DECLARE_METATYPE(CardInfoPtr)
@ -112,175 +116,162 @@ public:
QStringList getUnknownSetsNames(); QStringList getUnknownSetsNames();
}; };
class CardInfoPerSet
{
public:
explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer<CardSet>(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 class CardInfo : public QObject
{ {
Q_OBJECT Q_OBJECT
private: private:
CardInfoPtr smartThis; CardInfoPtr smartThis;
// The card name
QString name; QString name;
// The name without punctuation or capitalization, for better card name recognition.
/*
* The name without punctuation or capitalization, for better card tag name
* recognition.
*/
QString simpleName; QString simpleName;
// The key used to identify this card in the cache
bool isToken; QString pixmapCacheKey;
SetList sets; // card text
QString manacost;
QString cmc;
QString cardtype;
QString powtough;
QString 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 // the cards i'm related to
QList<CardRelation *> relatedCards; QList<CardRelation *> relatedCards;
// the card i'm reverse-related to // the card i'm reverse-related to
QList<CardRelation *> reverseRelatedCards; QList<CardRelation *> reverseRelatedCards;
// the cards thare are reverse-related to me // the cards thare are reverse-related to me
QList<CardRelation *> reverseRelatedCardsToMe; QList<CardRelation *> reverseRelatedCardsToMe;
// card sets
CardInfoPerSetMap sets;
// cached set names
QString setsNames; QString setsNames;
// positioning properties; used by UI
bool upsideDownArt;
QString loyalty;
QStringMap customPicURLs;
MuidMap muIds;
QStringMap collectorNumbers;
QStringMap rarities;
bool cipt; bool cipt;
int tableRow; int tableRow;
QString pixmapCacheKey; bool upsideDownArt;
public: public:
explicit CardInfo(const QString &_name = QString(), 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 QString &_text = QString(),
const QStringList &_colors = QStringList(), bool _isToken = false,
QVariantHash _properties = QVariantHash(),
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(), const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(), const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
bool _upsideDownArt = false, CardInfoPerSetMap _sets = CardInfoPerSetMap(),
const QString &_loyalty = QString(),
bool _cipt = false, bool _cipt = false,
int _tableRow = 0, int _tableRow = 0,
const SetList &_sets = SetList(), bool _upsideDownArt = false);
const QStringMap &_customPicURLs = QStringMap(),
MuidMap muids = MuidMap(),
QStringMap _collectorNumbers = QStringMap(),
QStringMap _rarities = QStringMap());
~CardInfo() override; ~CardInfo() override;
static CardInfoPtr newInstance(const QString &_name = QString(), 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 QString &_text = QString(),
const QStringList &_colors = QStringList(), bool _isToken = false,
QVariantHash _properties = QVariantHash(),
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(), const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(), const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
bool _upsideDownArt = false, CardInfoPerSetMap _sets = CardInfoPerSetMap(),
const QString &_loyalty = QString(),
bool _cipt = false, bool _cipt = false,
int _tableRow = 0, int _tableRow = 0,
const SetList &_sets = SetList(), bool _upsideDownArt = false);
const QStringMap &_customPicURLs = QStringMap(),
MuidMap muids = MuidMap(),
QStringMap _collectorNumbers = QStringMap(),
QStringMap _rarities = QStringMap());
void setSmartPointer(CardInfoPtr _ptr) void setSmartPointer(CardInfoPtr _ptr)
{ {
smartThis = _ptr; smartThis = std::move(_ptr);
} }
// basic properties
inline const QString &getName() const inline const QString &getName() const
{ {
return name; return name;
} }
inline const QString &getSetsNames() const
{
return setsNames;
}
const QString &getSimpleName() const const QString &getSimpleName() const
{ {
return simpleName; 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 const QString &getPixmapCacheKey() const
{ {
return pixmapCacheKey; return pixmapCacheKey;
} }
const QString &getLoyalty() const
const QString &getText() const
{ {
return loyalty; return text;
}
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);
} }
void setText(const QString &_text) void setText(const QString &_text)
{ {
text = _text; text = _text;
emit cardInfoChanged(smartThis); 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); emit cardInfoChanged(smartThis);
} }
const QChar getColorChar() const; const CardInfoPerSetMap &getSets() const
const QStringList &getColors() 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<CardRelation *> &getRelatedCards() const const QList<CardRelation *> &getRelatedCards() const
{ {
return relatedCards; return relatedCards;
@ -298,32 +289,12 @@ public:
{ {
reverseRelatedCardsToMe.append(cardRelation); 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 int getTableRow() const
{ {
return tableRow; return tableRow;
@ -332,27 +303,31 @@ public:
{ {
tableRow = _tableRow; tableRow = _tableRow;
} }
// void setLoyalty(int _loyalty) { loyalty = _loyalty; emit cardInfoChanged(smartThis); } bool getUpsideDownArt() const
// void setCustomPicURL(const QString &_set, const QString &_customPicURL) { customPicURLs.insert(_set,
// _customPicURL); }
void setSet(const CardSetPtr &_set)
{ {
sets.append(_set); return upsideDownArt;
refreshCachedSetNames();
} }
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) QString getCorrectedName() const;
{ void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet());
collectorNumbers.insert(_set, _setNumber);
}
void setRarity(const QString &_set, const QString &_setNumber)
{
rarities.insert(_set, _setNumber);
}
void addToSet(CardSetPtr set);
void emitPixmapUpdated() void emitPixmapUpdated()
{ {
emit pixmapUpdated(); emit pixmapUpdated();
@ -439,7 +414,6 @@ public:
SetList getSetList() const; SetList getSetList() const;
LoadStatus loadFromFile(const QString &fileName); LoadStatus loadFromFile(const QString &fileName);
bool saveCustomTokensToFile(); bool saveCustomTokensToFile();
QStringList getAllColors() const;
QStringList getAllMainCardTypes() const; QStringList getAllMainCardTypes() const;
LoadStatus getLoadStatus() const LoadStatus getLoadStatus() const
{ {
@ -506,4 +480,4 @@ public:
return defaultCount; return defaultCount;
} }
}; };
#endif #endif

View file

@ -14,9 +14,7 @@ CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromE
cardDatabaseEnabledSetsChanged(); cardDatabaseEnabledSetsChanged();
} }
CardDatabaseModel::~CardDatabaseModel() CardDatabaseModel::~CardDatabaseModel() = default;
{
}
QMap<wchar_t, wchar_t> CardDatabaseDisplayModel::characterTranslation = {{L'', L'\"'}, QMap<wchar_t, wchar_t> CardDatabaseDisplayModel::characterTranslation = {{L'', L'\"'},
{L'', L'\"'}, {L'', L'\"'},
@ -53,7 +51,7 @@ QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const
case PTColumn: case PTColumn:
return card->getPowTough(); return card->getPowTough();
case ColorColumn: case ColorColumn:
return card->getColors().join(""); return card->getColors();
default: default:
return QVariant(); return QVariant();
} }
@ -97,8 +95,8 @@ bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card)
if (!showOnlyCardsFromEnabledSets) if (!showOnlyCardsFromEnabledSets)
return true; return true;
for (CardSetPtr set : card->getSets()) { for (const auto &set : card->getSets()) {
if (set->getEnabled()) if (set.getPtr()->getEnabled())
return true; return true;
} }

View file

@ -26,12 +26,12 @@ public:
{ {
SortRole = Qt::UserRole SortRole = Qt::UserRole
}; };
CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = 0); CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr);
~CardDatabaseModel(); ~CardDatabaseModel() override;
int rowCount(const QModelIndex &parent = QModelIndex()) const; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const; QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
CardDatabase *getDatabase() const CardDatabase *getDatabase() const
{ {
return db; return db;
@ -77,7 +77,7 @@ private:
static QMap<wchar_t, wchar_t> characterTranslation; static QMap<wchar_t, wchar_t> characterTranslation;
public: public:
CardDatabaseDisplayModel(QObject *parent = 0); explicit CardDatabaseDisplayModel(QObject *parent = nullptr);
void setFilterTree(FilterTree *filterTree); void setFilterTree(FilterTree *filterTree);
void setIsToken(FilterBool _isToken) void setIsToken(FilterBool _isToken)
{ {
@ -119,15 +119,15 @@ public:
invalidate(); invalidate();
} }
void clearFilterAll(); void clearFilterAll();
int rowCount(const QModelIndex &parent = QModelIndex()) const; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
protected: 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); 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 rowMatchesCardName(CardInfoPtr info) const;
bool canFetchMore(const QModelIndex &parent) const; bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent); void fetchMore(const QModelIndex &parent) override;
private slots: private slots:
void filterTreeChanged(); void filterTreeChanged();
/** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */ /** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */
@ -138,11 +138,11 @@ class TokenDisplayModel : public CardDatabaseDisplayModel
{ {
Q_OBJECT Q_OBJECT
public: public:
TokenDisplayModel(QObject *parent = 0); explicit TokenDisplayModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
protected: protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
}; };
#endif #endif

View file

@ -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;
}

View file

@ -9,15 +9,27 @@
class ICardDatabaseParser : public QObject class ICardDatabaseParser : public QObject
{ {
public: public:
virtual ~ICardDatabaseParser() ~ICardDatabaseParser() override = default;
{
}
virtual bool getCanParseFile(const QString &name, QIODevice &device) = 0; virtual bool getCanParseFile(const QString &name, QIODevice &device) = 0;
virtual void parseFile(QIODevice &device) = 0; virtual void parseFile(QIODevice &device) = 0;
virtual bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName) = 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: signals:
virtual void addCard(CardInfoPtr card) = 0; virtual void addCard(CardInfoPtr card) = 0;
virtual void addSet(CardSetPtr set) = 0;
}; };
Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser") Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser")

View file

@ -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) void CockatriceXml3Parser::loadSetsFromXml(QXmlStreamReader &xml)
{ {
while (!xml.atEnd()) { 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) void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
{ {
while (!xml.atEnd()) { while (!xml.atEnd()) {
@ -128,55 +142,77 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
} }
if (xml.name() == "card") { if (xml.name() == "card") {
QString name, manacost, cmc, type, pt, text, loyalty; QString name = QString("");
QStringList colors; QString text = QString("");
QVariantHash properties = QVariantHash();
QString colors = QString("");
QList<CardRelation *> relatedCards, reverseRelatedCards; QList<CardRelation *> relatedCards, reverseRelatedCards;
QStringMap customPicURLs; CardInfoPerSetMap sets = CardInfoPerSetMap();
MuidMap muids;
QStringMap collectorNumbers, rarities;
SetList sets;
int tableRow = 0; int tableRow = 0;
bool cipt = false; bool cipt = false;
bool isToken = false; bool isToken = false;
bool upsideDown = false; bool upsideDown = false;
while (!xml.atEnd()) { while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) { if (xml.readNext() == QXmlStreamReader::EndElement) {
break; break;
} }
// variable - assigned properties
if (xml.name() == "name") { if (xml.name() == "name") {
name = xml.readElementText(); 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") { } else if (xml.name() == "text") {
text = xml.readElementText(); text = xml.readElementText();
} else if (xml.name() == "color") {
colors.append(xml.readElementText());
} else if (xml.name() == "token") {
isToken = static_cast<bool>(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") { } else if (xml.name() == "set") {
// NOTE: attributes must be read before readElementText()
QXmlStreamAttributes attrs = xml.attributes(); QXmlStreamAttributes attrs = xml.attributes();
QString setName = xml.readElementText(); QString setName = xml.readElementText();
sets.append(internalAddSet(setName)); CardInfoPerSet setInfo(internalAddSet(setName));
if (attrs.hasAttribute("muId")) { 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")) { if (attrs.hasAttribute("picURL")) {
customPicURLs[setName] = attrs.value("picURL").toString(); setInfo.setProperty("picurl", attrs.value("picURL").toString());
} }
if (attrs.hasAttribute("num")) { if (attrs.hasAttribute("num")) {
collectorNumbers[setName] = attrs.value("num").toString(); setInfo.setProperty("num", attrs.value("num").toString());
} }
if (attrs.hasAttribute("rarity")) { if (attrs.hasAttribute("rarity")) {
rarities[setName] = attrs.value("rarity").toString(); setInfo.setProperty("rarity", attrs.value("rarity").toString());
} }
} else if (xml.name() == "color") { sets.insert(setName, setInfo);
colors << xml.readElementText(); // relatd cards
} else if (xml.name() == "related" || xml.name() == "reverse-related") { } else if (xml.name() == "related" || xml.name() == "reverse-related") {
bool attach = false; bool attach = false;
bool exclude = false; bool exclude = false;
@ -213,16 +249,6 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
} else { } else {
relatedCards << relation; 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<bool>(xml.readElementText().toInt());
} else if (xml.name() != "") { } else if (xml.name() != "") {
qDebug() << "[CockatriceXml3Parser] Unknown card property" << xml.name() qDebug() << "[CockatriceXml3Parser] Unknown card property" << xml.name()
<< ", trying to continue anyway"; << ", trying to continue anyway";
@ -230,9 +256,9 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
} }
} }
CardInfoPtr newCard = CardInfo::newInstance( properties.insert("colors", colors);
name, isToken, manacost, cmc, type, pt, text, colors, relatedCards, reverseRelatedCards, upsideDown, CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
loyalty, cipt, tableRow, sets, customPicURLs, muids, collectorNumbers, rarities); reverseRelatedCards, sets, cipt, tableRow, upsideDown);
emit addCard(newCard); emit addCard(newCard);
} }
} }
@ -262,37 +288,60 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
return xml; return xml;
} }
xml.writeStartElement("card");
xml.writeTextElement("name", info->getName());
const SetList &sets = info->getSets();
QString tmpString; 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.writeStartElement("set");
xml.writeAttribute("rarity", set.getProperty("rarity"));
xml.writeAttribute("muId", set.getProperty("muid"));
xml.writeAttribute("uuId", set.getProperty("uuid"));
tmpSet = sets[i]->getShortName(); tmpString = set.getProperty("num");
xml.writeAttribute("rarity", info->getRarity(tmpSet));
xml.writeAttribute("muId", QString::number(info->getMuId(tmpSet)));
tmpString = info->getCollectorNumber(tmpSet);
if (!tmpString.isEmpty()) { if (!tmpString.isEmpty()) {
xml.writeAttribute("num", info->getCollectorNumber(tmpSet)); xml.writeAttribute("num", tmpString);
} }
tmpString = info->getCustomPicURL(tmpSet); tmpString = set.getProperty("picurl");
if (!tmpString.isEmpty()) { if (!tmpString.isEmpty()) {
xml.writeAttribute("picURL", tmpString); xml.writeAttribute("picURL", tmpString);
} }
xml.writeCharacters(tmpSet); xml.writeCharacters(set.getPtr()->getShortName());
xml.writeEndElement(); xml.writeEndElement();
} }
const QStringList &colors = info->getColors();
for (int i = 0; i < colors.size(); i++) {
xml.writeTextElement("color", colors[i]);
}
// related cards
const QList<CardRelation *> related = info->getRelatedCards(); const QList<CardRelation *> related = info->getRelatedCards();
for (auto i : related) { for (auto i : related) {
xml.writeStartElement("related"); xml.writeStartElement("related");
@ -338,23 +387,12 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
xml.writeCharacters(i->getName()); xml.writeCharacters(i->getName());
xml.writeEndElement(); xml.writeEndElement();
} }
xml.writeTextElement("manacost", info->getManaCost());
xml.writeTextElement("cmc", info->getCmc()); // positioning
xml.writeTextElement("type", info->getCardType());
if (!info->getPowTough().isEmpty()) {
xml.writeTextElement("pt", info->getPowTough());
}
xml.writeTextElement("tablerow", QString::number(info->getTableRow())); xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
xml.writeTextElement("text", info->getText());
if (info->getMainCardType() == "Planeswalker") {
xml.writeTextElement("loyalty", info->getLoyalty());
}
if (info->getCipt()) { if (info->getCipt()) {
xml.writeTextElement("cipt", "1"); xml.writeTextElement("cipt", "1");
} }
if (info->getIsToken()) {
xml.writeTextElement("token", "1");
}
if (info->getUpsideDownArt()) { if (info->getUpsideDownArt()) {
xml.writeTextElement("upsidedown", "1"); xml.writeTextElement("upsidedown", "1");
} }

View file

@ -11,27 +11,18 @@ class CockatriceXml3Parser : public ICardDatabaseParser
Q_INTERFACES(ICardDatabaseParser) Q_INTERFACES(ICardDatabaseParser)
public: public:
CockatriceXml3Parser() = default; CockatriceXml3Parser() = default;
~CockatriceXml3Parser() = default; ~CockatriceXml3Parser() override = default;
bool getCanParseFile(const QString &name, QIODevice &device); bool getCanParseFile(const QString &name, QIODevice &device) override;
void parseFile(QIODevice &device); void parseFile(QIODevice &device) override;
bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName); bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName) override;
void clearSetlist();
private: 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 loadCardsFromXml(QXmlStreamReader &xml);
void loadSetsFromXml(QXmlStreamReader &xml); void loadSetsFromXml(QXmlStreamReader &xml);
QString getMainCardType(QString &type);
signals: signals:
void addCard(CardInfoPtr card); void addCard(CardInfoPtr card) override;
void addSet(CardSetPtr set); void addSet(CardSetPtr set) override;
}; };
#endif #endif

View file

@ -0,0 +1,362 @@
#include "cockatricexml4.h"
#include <QDebug>
#include <QFile>
#include <QXmlStreamReader>
#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<CardRelation *> 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<bool>(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<CardRelation *> 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<CardRelation *> 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;
}

View file

@ -0,0 +1,28 @@
#ifndef COCKATRICE_XML4_H
#define COCKATRICE_XML4_H
#include <QXmlStreamReader>
#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

View file

@ -1,47 +1,47 @@
#include "cardfilter.h" #include "cardfilter.h"
const char *CardFilter::typeName(Type t) const QString CardFilter::typeName(Type t)
{ {
switch (t) { switch (t) {
case TypeAnd: case TypeAnd:
return "AND"; return tr("AND", "Logical conjunction operator used in card filter");
case TypeOr: case TypeOr:
return "OR"; return tr("OR", "Logical disjunction operator used in card filter");
case TypeAndNot: case TypeAndNot:
return "AND NOT"; return tr("AND NOT", "Negated logical conjunction operator used in card filter");
case TypeOrNot: case TypeOrNot:
return "OR NOT"; return tr("OR NOT", "Negated logical disjunction operator used in card filter");
default: default:
return ""; return QString("");
} }
} }
const char *CardFilter::attrName(Attr a) const QString CardFilter::attrName(Attr a)
{ {
switch (a) { switch (a) {
case AttrName: case AttrName:
return "Name"; return tr("Name");
case AttrType: case AttrType:
return "Type"; return tr("Type");
case AttrColor: case AttrColor:
return "Color"; return tr("Color");
case AttrText: case AttrText:
return "Text"; return tr("Text");
case AttrSet: case AttrSet:
return "Set"; return tr("Set");
case AttrManaCost: case AttrManaCost:
return "Mana Cost"; return tr("Mana Cost");
case AttrCmc: case AttrCmc:
return "CMC"; return tr("CMC");
case AttrRarity: case AttrRarity:
return "Rarity"; return tr("Rarity");
case AttrPow: case AttrPow:
return "Power"; return tr("Power");
case AttrTough: case AttrTough:
return "Toughness"; return tr("Toughness");
case AttrLoyalty: case AttrLoyalty:
return "Loyalty"; return tr("Loyalty");
default: default:
return ""; return QString("");
} }
} }

View file

@ -1,10 +1,13 @@
#ifndef CARDFILTER_H #ifndef CARDFILTER_H
#define CARDFILTER_H #define CARDFILTER_H
#include <QObject>
#include <QString> #include <QString>
class CardFilter class CardFilter : public QObject
{ {
Q_OBJECT
public: public:
enum Type enum Type
{ {
@ -54,8 +57,8 @@ public:
return a; return a;
} }
static const char *typeName(Type t); static const QString typeName(Type t);
static const char *attrName(Attr a); static const QString attrName(Attr a);
}; };
#endif #endif

View file

@ -1,3 +1,5 @@
#include <utility>
#include "cardframe.h" #include "cardframe.h"
#include "cardinfopicture.h" #include "cardinfopicture.h"
@ -16,6 +18,7 @@ CardFrame::CardFrame(const QString &cardName, QWidget *parent) : QTabWidget(pare
pic->setObjectName("pic"); pic->setObjectName("pic");
text = new CardInfoText(); text = new CardInfoText();
text->setObjectName("text"); text->setObjectName("text");
connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &)));
tab1 = new QWidget(this); tab1 = new QWidget(this);
tab2 = new QWidget(this); tab2 = new QWidget(this);
@ -93,10 +96,10 @@ void CardFrame::setCard(CardInfoPtr card)
disconnect(info.data(), nullptr, this, nullptr); disconnect(info.data(), nullptr, this, nullptr);
} }
info = card; info = std::move(card);
if (info) { if (info) {
connect(info.data(), SIGNAL(destroyed()), this, SLOT(clear())); connect(info.data(), SIGNAL(destroyed()), this, SLOT(clearCard()));
} }
text->setCard(info); text->setCard(info);
@ -115,7 +118,7 @@ void CardFrame::setCard(AbstractCardItem *card)
} }
} }
void CardFrame::clear() void CardFrame::clearCard()
{ {
setCard((CardInfoPtr) nullptr); setCard((CardInfoPtr) nullptr);
} }

View file

@ -37,7 +37,7 @@ public slots:
void setCard(CardInfoPtr card); void setCard(CardInfoPtr card);
void setCard(const QString &cardName); void setCard(const QString &cardName);
void setCard(AbstractCardItem *card); void setCard(AbstractCardItem *card);
void clear(); void clearCard();
void setViewMode(int mode); void setViewMode(int mode);
}; };

View file

@ -1,137 +1,83 @@
#include "cardinfotext.h" #include "cardinfotext.h"
#include "carditem.h" #include "carditem.h"
#include "game_specific_terms.h"
#include "main.h" #include "main.h"
#include <QGridLayout> #include <QGridLayout>
#include <QLabel> #include <QLabel>
#include <QTextEdit> #include <QTextEdit>
CardInfoText::CardInfoText(QWidget *parent) : QFrame(parent), info(nullptr) CardInfoText::CardInfoText(QWidget *parent) : QFrame(parent), info(nullptr)
{ {
nameLabel1 = new QLabel; nameLabel = new QLabel;
nameLabel2 = new QLabel; nameLabel->setOpenExternalLinks(false);
nameLabel2->setWordWrap(true); connect(nameLabel, SIGNAL(linkActivated(const QString &)), this, SIGNAL(linkActivated(const QString &)));
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);
textLabel = new QTextEdit(); textLabel = new QTextEdit();
textLabel->setReadOnly(true); textLabel->setReadOnly(true);
QGridLayout *grid = new QGridLayout(this); QGridLayout *grid = new QGridLayout(this);
int row = 0; grid->addWidget(nameLabel, 0, 0);
grid->addWidget(nameLabel1, row, 0); grid->addWidget(textLabel, 1, 0, -1, 2);
grid->addWidget(nameLabel2, row++, 1); grid->setRowStretch(1, 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->setColumnStretch(1, 1); grid->setColumnStretch(1, 1);
retranslateUi(); 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) void CardInfoText::setCard(CardInfoPtr card)
{ {
if (card) { if (card == nullptr) {
resetLabels(); nameLabel->setText("");
nameLabel2->setText(card->getName()); textLabel->setText("");
if (!card->getManaCost().isEmpty()) { return;
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();
} }
QString text = "<table width=\"100%\" border=0 cellspacing=0 cellpadding=0>";
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
.arg(tr("Name:"), card->getName().toHtmlEscaped());
QStringList cardProps = card->getProperties();
foreach (QString key, cardProps) {
QString keyText = Mtg::getNicePropertyName(key).toHtmlEscaped() + ":";
text +=
QString("<tr><td>%1</td><td></td><td>%2</td></tr>").arg(keyText, card->getProperty(key).toHtmlEscaped());
}
auto relatedCards = card->getRelatedCards();
auto reverserelatedCards2Me = card->getReverseRelatedCards2Me();
if (relatedCards.size() || reverserelatedCards2Me.size()) {
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>").arg(tr("Related cards:"));
for (int i = 0; i < relatedCards.size(); ++i) {
QString tmp = relatedCards.at(i)->getName().toHtmlEscaped();
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
}
for (int i = 0; i < reverserelatedCards2Me.size(); ++i) {
QString tmp = reverserelatedCards2Me.at(i)->getName().toHtmlEscaped();
text += "<a href=\"" + tmp + "\">" + tmp + "</a><br>";
}
text += "</td></tr>";
}
text += "</table>";
nameLabel->setText(text);
textLabel->setText(card->getText());
} }
void CardInfoText::setInvalidCardName(const QString &cardName) void CardInfoText::setInvalidCardName(const QString &cardName)
{ {
nameLabel1->setText(tr("Unknown card:")); nameLabel->setText(tr("Unknown card:") + " " + cardName);
nameLabel1->show(); textLabel->setText("");
nameLabel2->setText(cardName);
nameLabel2->show();
} }
void CardInfoText::retranslateUi() void CardInfoText::retranslateUi()
{ {
nameLabel1->setText(tr("Name:")); /*
manacostLabel1->setText(tr("Mana cost:")); * There's no way we can really translate the text currently being rendered.
colorLabel1->setText(tr("Color(s):")); * The best we can do is invalidate the current text.
cardtypeLabel1->setText(tr("Card type:")); */
powtoughLabel1->setText(tr("P / T:")); setInvalidCardName("");
loyaltyLabel1->setText(tr("Loyalty:"));
} }

View file

@ -12,23 +12,17 @@ class CardInfoText : public QFrame
Q_OBJECT Q_OBJECT
private: private:
QLabel *nameLabel1, *nameLabel2; QLabel *nameLabel;
QLabel *manacostLabel1, *manacostLabel2;
QLabel *colorLabel1, *colorLabel2;
QLabel *cardtypeLabel1, *cardtypeLabel2;
QLabel *powtoughLabel1, *powtoughLabel2;
QLabel *loyaltyLabel1, *loyaltyLabel2;
QTextEdit *textLabel; QTextEdit *textLabel;
CardInfoPtr info; CardInfoPtr info;
void resetLabels();
public: public:
CardInfoText(QWidget *parent = 0); CardInfoText(QWidget *parent = 0);
void retranslateUi(); void retranslateUi();
void setInvalidCardName(const QString &cardName); void setInvalidCardName(const QString &cardName);
signals:
void linkActivated(const QString &link);
public slots: public slots:
void setCard(CardInfoPtr card); void setCard(CardInfoPtr card);
}; };

View file

@ -1,6 +1,8 @@
#include "cardinfowidget.h" #include <utility>
#include "cardinfopicture.h" #include "cardinfopicture.h"
#include "cardinfotext.h" #include "cardinfotext.h"
#include "cardinfowidget.h"
#include "carditem.h" #include "carditem.h"
#include "main.h" #include "main.h"
#include <QDesktopWidget> #include <QDesktopWidget>
@ -14,8 +16,9 @@ CardInfoWidget::CardInfoWidget(const QString &cardName, QWidget *parent, Qt::Win
pic->setObjectName("pic"); pic->setObjectName("pic");
text = new CardInfoText(); text = new CardInfoText();
text->setObjectName("text"); 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->setObjectName("layout");
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0); layout->setSpacing(0);
@ -26,7 +29,7 @@ CardInfoWidget::CardInfoWidget(const QString &cardName, QWidget *parent, Qt::Win
setFrameStyle(QFrame::Panel | QFrame::Raised); setFrameStyle(QFrame::Panel | QFrame::Raised);
QDesktopWidget desktopWidget; QDesktopWidget desktopWidget;
int pixmapHeight = desktopWidget.screenGeometry().height() / 3; int pixmapHeight = desktopWidget.screenGeometry().height() / 3;
int pixmapWidth = pixmapHeight / aspectRatio; int pixmapWidth = static_cast<int>(pixmapHeight / aspectRatio);
pic->setFixedWidth(pixmapWidth); pic->setFixedWidth(pixmapWidth);
pic->setFixedHeight(pixmapHeight); pic->setFixedHeight(pixmapHeight);
setFixedWidth(pixmapWidth + 150); setFixedWidth(pixmapWidth + 150);
@ -41,7 +44,7 @@ void CardInfoWidget::setCard(CardInfoPtr card)
{ {
if (info) if (info)
disconnect(info.data(), nullptr, this, nullptr); disconnect(info.data(), nullptr, this, nullptr);
info = card; info = std::move(card);
if (info) if (info)
connect(info.data(), SIGNAL(destroyed()), this, SLOT(clear())); connect(info.data(), SIGNAL(destroyed()), this, SLOT(clear()));

View file

@ -22,7 +22,7 @@ private:
CardInfoText *text; CardInfoText *text;
public: 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: public slots:
void setCard(CardInfoPtr card); void setCard(CardInfoPtr card);

View file

@ -322,9 +322,7 @@ QModelIndex DeckListModel::addCard(const QString &cardName, const QString &zoneN
// This is usually called from tab_deck_editor // This is usually called from tab_deck_editor
// So we'll create a new CardInfo with the name // So we'll create a new CardInfo with the name
// and default values for all fields // and default values for all fields
info = CardInfo::newInstance(cardName, false, nullptr, nullptr, "unknown", nullptr, nullptr, QStringList(), info = CardInfo::newInstance(cardName);
QList<CardRelation *>(), QList<CardRelation *>(), false, 0, false, 0,
SetList(), QStringMap(), MuidMap(), QStringMap(), QStringMap());
} else { } else {
return {}; return {};
} }

View file

@ -7,7 +7,6 @@
class DeckLoader; class DeckLoader;
class CardDatabase; class CardDatabase;
class QProgressDialog;
class QPrinter; class QPrinter;
class QTextCursor; class QTextCursor;
@ -21,19 +20,19 @@ public:
: AbstractDecklistCardNode(_parent), dataNode(_dataNode) : AbstractDecklistCardNode(_parent), dataNode(_dataNode)
{ {
} }
int getNumber() const int getNumber() const override
{ {
return dataNode->getNumber(); return dataNode->getNumber();
} }
void setNumber(int _number) void setNumber(int _number) override
{ {
dataNode->setNumber(_number); dataNode->setNumber(_number);
} }
QString getName() const QString getName() const override
{ {
return dataNode->getName(); return dataNode->getName();
} }
void setName(const QString &_name) void setName(const QString &_name) override
{ {
dataNode->setName(_name); dataNode->setName(_name);
} }
@ -54,20 +53,20 @@ signals:
void deckHashChanged(); void deckHashChanged();
public: public:
DeckListModel(QObject *parent = 0); explicit DeckListModel(QObject *parent = nullptr);
~DeckListModel(); ~DeckListModel() override;
int rowCount(const QModelIndex &parent) const; int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const; int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const; QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &index) const; QModelIndex parent(const QModelIndex &index) const override;
Qt::ItemFlags flags(const QModelIndex &index) const; Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role); bool setData(const QModelIndex &index, const QVariant &value, int role) override;
bool removeRows(int row, int count, const QModelIndex &parent); bool removeRows(int row, int count, const QModelIndex &parent) override;
QModelIndex findCard(const QString &cardName, const QString &zoneName) const; QModelIndex findCard(const QString &cardName, const QString &zoneName) const;
QModelIndex addCard(const QString &cardName, const QString &zoneName, bool abAddAnyway = false); 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(); void cleanList();
DeckLoader *getDeckList() const DeckLoader *getDeckList() const
{ {

View file

@ -14,8 +14,6 @@
#include <QPushButton> #include <QPushButton>
#include <QRadioButton> #include <QRadioButton>
#define PUBLIC_SERVERS_URL "https://github.com/Cockatrice/Cockatrice/wiki/Public-Servers"
DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
{ {
previousHostButton = new QRadioButton(tr("Known Hosts"), this); previousHostButton = new QRadioButton(tr("Known Hosts"), this);
@ -273,15 +271,15 @@ void DlgConnect::newHostSelected(bool state)
previousHosts->setDisabled(true); previousHosts->setDisabled(true);
btnRefreshServers->setDisabled(true); btnRefreshServers->setDisabled(true);
hostEdit->clear(); hostEdit->clear();
hostEdit->setPlaceholderText("Server URL"); hostEdit->setPlaceholderText(tr("Server URL"));
hostEdit->setDisabled(false); hostEdit->setDisabled(false);
portEdit->clear(); portEdit->clear();
portEdit->setPlaceholderText("Communication Port"); portEdit->setPlaceholderText(tr("Communication Port"));
portEdit->setDisabled(false); portEdit->setDisabled(false);
playernameEdit->clear(); playernameEdit->clear();
passwordEdit->clear(); passwordEdit->clear();
saveEdit->clear(); saveEdit->clear();
saveEdit->setPlaceholderText("Unique Server Name"); saveEdit->setPlaceholderText(tr("Unique Server Name"));
saveEdit->setDisabled(false); saveEdit->setDisabled(false);
serverContactLabel->setText(""); serverContactLabel->setText("");
serverContactLink->setText(""); serverContactLink->setText("");

View file

@ -17,7 +17,7 @@
#include <QTreeView> #include <QTreeView>
#include <QVBoxLayout> #include <QVBoxLayout>
DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(0) DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr)
{ {
nameLabel = new QLabel(tr("&Name:")); nameLabel = new QLabel(tr("&Name:"));
nameEdit = new QLineEdit; nameEdit = new QLineEdit;
@ -46,7 +46,7 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(0)
annotationLabel->setBuddy(annotationEdit); annotationLabel->setBuddy(annotationEdit);
connect(annotationEdit, SIGNAL(textChanged(QString)), this, SLOT(annotationChanged(QString))); connect(annotationEdit, SIGNAL(textChanged(QString)), this, SLOT(annotationChanged(QString)));
QGridLayout *grid = new QGridLayout; auto *grid = new QGridLayout;
grid->addWidget(nameLabel, 0, 0); grid->addWidget(nameLabel, 0, 0);
grid->addWidget(nameEdit, 0, 1); grid->addWidget(nameEdit, 0, 1);
grid->addWidget(colorLabel, 1, 0); grid->addWidget(colorLabel, 1, 0);
@ -89,15 +89,15 @@ DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(0)
aRemoveToken->setIcon(QPixmap("theme:icons/decrement")); aRemoveToken->setIcon(QPixmap("theme:icons/decrement"));
connect(aRemoveToken, SIGNAL(triggered()), this, SLOT(actRemoveToken())); connect(aRemoveToken, SIGNAL(triggered()), this, SLOT(actRemoveToken()));
QToolBar *databaseToolBar = new QToolBar; auto *databaseToolBar = new QToolBar;
databaseToolBar->addAction(aAddToken); databaseToolBar->addAction(aAddToken);
databaseToolBar->addAction(aRemoveToken); databaseToolBar->addAction(aRemoveToken);
QVBoxLayout *leftVBox = new QVBoxLayout; auto *leftVBox = new QVBoxLayout;
leftVBox->addWidget(chooseTokenView); leftVBox->addWidget(chooseTokenView);
leftVBox->addWidget(databaseToolBar); leftVBox->addWidget(databaseToolBar);
QHBoxLayout *hbox = new QHBoxLayout; auto *hbox = new QHBoxLayout;
hbox->addLayout(leftVBox); hbox->addLayout(leftVBox);
hbox->addWidget(tokenDataGroupBox); 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(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
QVBoxLayout *mainLayout = new QVBoxLayout; auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(hbox); mainLayout->addLayout(hbox);
mainLayout->addWidget(buttonBox); mainLayout->addWidget(buttonBox);
@ -154,9 +154,10 @@ void DlgEditTokens::actAddToken()
} }
} while (askAgain); } while (askAgain);
CardInfoPtr card = CardInfo::newInstance(name, true); CardInfoPtr card = CardInfo::newInstance(name, "", true);
card->addToSet(databaseModel->getDatabase()->getSet(CardDatabase::TOKENS_SETNAME));
card->setCardType("Token"); card->setCardType("Token");
card->addToSet(databaseModel->getDatabase()->getSet(CardDatabase::TOKENS_SETNAME));
databaseModel->getDatabase()->addCard(card); databaseModel->getDatabase()->addCard(card);
} }
@ -172,7 +173,7 @@ void DlgEditTokens::actRemoveToken()
void DlgEditTokens::colorChanged(int colorIndex) void DlgEditTokens::colorChanged(int colorIndex)
{ {
if (currentCard) if (currentCard)
currentCard->setColors(QStringList() << QString(colorEdit->itemData(colorIndex).toChar())); currentCard->setColors(QString(colorEdit->itemData(colorIndex).toChar()));
} }
void DlgEditTokens::ptChanged(const QString &_pt) void DlgEditTokens::ptChanged(const QString &_pt)

View file

@ -35,7 +35,7 @@ private:
QTreeView *chooseTokenView; QTreeView *chooseTokenView;
public: public:
DlgEditTokens(QWidget *parent = nullptr); explicit DlgEditTokens(QWidget *parent = nullptr);
}; };
#endif #endif

View file

@ -32,7 +32,7 @@ DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : QDialog(pa
resize(500, 500); resize(500, 500);
actRefresh(); actRefresh();
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts(); refreshShortcuts();
} }

View file

@ -45,8 +45,6 @@ GeneralSettingsPage::GeneralSettingsPage()
languageBox.setCurrentIndex(i); languageBox.setCurrentIndex(i);
} }
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
// updates // updates
QList<ReleaseChannel *> channels = settingsCache->getUpdateReleaseChannels(); QList<ReleaseChannel *> channels = settingsCache->getUpdateReleaseChannels();
foreach (ReleaseChannel *chan, channels) { foreach (ReleaseChannel *chan, channels) {
@ -55,6 +53,7 @@ GeneralSettingsPage::GeneralSettingsPage()
updateReleaseChannelBox.setCurrentIndex(settingsCache->getUpdateReleaseChannel()->getIndex()); updateReleaseChannelBox.setCurrentIndex(settingsCache->getUpdateReleaseChannel()->getIndex());
updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates()); updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates());
newVersionOracleCheckBox.setChecked(settingsCache->getNotifyAboutNewVersion());
// pixmap cache // pixmap cache
pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
@ -64,27 +63,16 @@ GeneralSettingsPage::GeneralSettingsPage()
pixmapCacheEdit.setValue(settingsCache->getPixmapCacheSize()); pixmapCacheEdit.setValue(settingsCache->getPixmapCacheSize());
pixmapCacheEdit.setSuffix(" MB"); pixmapCacheEdit.setSuffix(" MB");
defaultUrlEdit = new QLineEdit(settingsCache->getPicUrl());
fallbackUrlEdit = new QLineEdit(settingsCache->getPicUrlFallback());
showTipsOnStartup.setChecked(settingsCache->getShowTipsOnStartup()); showTipsOnStartup.setChecked(settingsCache->getShowTipsOnStartup());
connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked()));
connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); 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(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int)));
connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), settingsCache, connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), settingsCache,
SLOT(setUpdateReleaseChannel(int))); SLOT(setUpdateReleaseChannel(int)));
connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int))); connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int)));
connect(&picDownloadCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool))); connect(&newVersionOracleCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutNewVersion(int)));
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(&showTipsOnStartup, SIGNAL(clicked(bool)), settingsCache, SLOT(setShowTipsOnStartup(bool))); connect(&showTipsOnStartup, SIGNAL(clicked(bool)), settingsCache, SLOT(setShowTipsOnStartup(bool)));
setEnabledStatus(settingsCache->getPicDownload());
auto *personalGrid = new QGridLayout; auto *personalGrid = new QGridLayout;
personalGrid->addWidget(&languageLabel, 0, 0); personalGrid->addWidget(&languageLabel, 0, 0);
personalGrid->addWidget(&languageBox, 0, 1); personalGrid->addWidget(&languageBox, 0, 1);
@ -93,19 +81,8 @@ GeneralSettingsPage::GeneralSettingsPage()
personalGrid->addWidget(&pixmapCacheLabel, 2, 0); personalGrid->addWidget(&pixmapCacheLabel, 2, 0);
personalGrid->addWidget(&pixmapCacheEdit, 2, 1); personalGrid->addWidget(&pixmapCacheEdit, 2, 1);
personalGrid->addWidget(&updateNotificationCheckBox, 3, 0); personalGrid->addWidget(&updateNotificationCheckBox, 3, 0);
personalGrid->addWidget(&showTipsOnStartup, 4, 0); personalGrid->addWidget(&newVersionOracleCheckBox, 4, 0);
personalGrid->addWidget(&picDownloadCheckBox, 5, 0); personalGrid->addWidget(&showTipsOnStartup, 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);
personalGroupBox = new QGroupBox; personalGroupBox = new QGroupBox;
personalGroupBox->setLayout(personalGrid); personalGroupBox->setLayout(personalGrid);
@ -194,20 +171,6 @@ QString GeneralSettingsPage::languageName(const QString &qmFile)
return translator.translate("i18n", DEFAULT_LANG_NAME); 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() void GeneralSettingsPage::deckPathButtonClicked()
{ {
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"));
@ -238,30 +201,6 @@ void GeneralSettingsPage::picsPathButtonClicked()
settingsCache->setPicsPath(path); 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() void GeneralSettingsPage::cardDatabasePathButtonClicked()
{ {
QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); QString path = QFileDialog::getOpenFileName(this, tr("Choose path"));
@ -291,7 +230,6 @@ void GeneralSettingsPage::retranslateUi()
{ {
personalGroupBox->setTitle(tr("Personal settings")); personalGroupBox->setTitle(tr("Personal settings"));
languageLabel.setText(tr("Language:")); languageLabel.setText(tr("Language:"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
if (settingsCache->getIsPortableBuild()) { if (settingsCache->getIsPortableBuild()) {
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)")); pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
@ -305,26 +243,12 @@ void GeneralSettingsPage::retranslateUi()
cardDatabasePathLabel.setText(tr("Card database:")); cardDatabasePathLabel.setText(tr("Card database:"));
tokenDatabasePathLabel.setText(tr("Token database:")); tokenDatabasePathLabel.setText(tr("Token database:"));
pixmapCacheLabel.setText(tr("Picture cache size:")); pixmapCacheLabel.setText(tr("Picture cache size:"));
defaultUrlLabel.setText(tr("Primary download URL:"));
fallbackUrlLabel.setText(tr("Fallback download URL:"));
urlLinkLabel.setText(
QString("<a href='%1'>%2</a>").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")); updateReleaseChannelLabel.setText(tr("Update channel"));
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client")); updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
defaultUrlRestoreButton.setText(tr("Reset")); newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
fallbackUrlRestoreButton.setText(tr("Reset"));
showTipsOnStartup.setText(tr("Show tips on startup")); 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() AppearanceSettingsPage::AppearanceSettingsPage()
{ {
QString themeName = settingsCache->getThemeName(); QString themeName = settingsCache->getThemeName();
@ -498,6 +422,15 @@ void UserInterfaceSettingsPage::retranslateUi()
DeckEditorSettingsPage::DeckEditorSettingsPage() 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 *lpGeneralGrid = new QGridLayout;
auto *lpSpoilerGrid = new QGridLayout; auto *lpSpoilerGrid = new QGridLayout;
@ -515,9 +448,46 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
// Update the GUI depending on if the box is ticked or not // Update the GUI depending on if the box is ticked or not
setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked()); setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked());
// Create the layout urlList = new QListWidget;
lpGeneralGrid->addWidget(&mcGeneralMessageLabel, 0, 0); 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(&mcDownloadSpoilersCheckBox, 0, 0);
lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0); lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0);
lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1); lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1);
@ -543,6 +513,94 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
setLayout(lpMainLayout); 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() void DeckEditorSettingsPage::updateSpoilers()
{ {
// Disable the button so the user can only press it once at a time // 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() void DeckEditorSettingsPage::retranslateUi()
{ {
mpGeneralGroupBox->setTitle(tr("URL Download Priority"));
mpSpoilerGroupBox->setTitle(tr("Spoilers")); mpSpoilerGroupBox->setTitle(tr("Spoilers"));
mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically")); mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically"));
mcSpoilerSaveLabel.setText(tr("Spoiler Location:")); mcSpoilerSaveLabel.setText(tr("Spoiler Location:"));
mcGeneralMessageLabel.setText(tr("Hey, something's here finally!"));
lastUpdatedLabel.setText(tr("Last Updated") + ": " + getLastUpdateTime()); lastUpdatedLabel.setText(tr("Last Updated") + ": " + getLastUpdateTime());
infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" + infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" +
tr("Press the button to manually update without relaunching") + "\n\n" + tr("Press the button to manually update without relaunching") + "\n\n" +
tr("Do not close settings until manual update complete")); tr("Do not close settings until manual update complete"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
urlLinkLabel.setText(QString("<a href='%1'>%2</a>").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() MessagesSettingsPage::MessagesSettingsPage()
@ -649,7 +711,7 @@ MessagesSettingsPage::MessagesSettingsPage()
connect(&roomHistory, SIGNAL(stateChanged(int)), settingsCache, SLOT(setRoomHistory(int))); connect(&roomHistory, SIGNAL(stateChanged(int)), settingsCache, SLOT(setRoomHistory(int)));
customAlertString = new QLineEdit(); customAlertString = new QLineEdit();
customAlertString->setPlaceholderText("Word1 Word2 Word3"); customAlertString->setPlaceholderText(tr("Word1 Word2 Word3"));
customAlertString->setText(settingsCache->getHighlightWords()); customAlertString->setText(settingsCache->getHighlightWords());
connect(customAlertString, SIGNAL(textChanged(QString)), settingsCache, SLOT(setHighlightWords(QString))); connect(customAlertString, SIGNAL(textChanged(QString)), settingsCache, SLOT(setHighlightWords(QString)));
@ -689,12 +751,16 @@ MessagesSettingsPage::MessagesSettingsPage()
aAdd = new QAction(this); aAdd = new QAction(this);
aAdd->setIcon(QPixmap("theme:icons/increment")); aAdd->setIcon(QPixmap("theme:icons/increment"));
aAdd->setStatusTip(tr("Add New URL"));
connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd())); connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd()));
aEdit = new QAction(this); aEdit = new QAction(this);
aEdit->setIcon(QPixmap("theme:icons/pencil")); aEdit->setIcon(QPixmap("theme:icons/pencil"));
aEdit->setStatusTip(tr("Edit URL"));
connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit())); connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit()));
aRemove = new QAction(this); aRemove = new QAction(this);
aRemove->setIcon(QPixmap("theme:icons/decrement")); aRemove->setIcon(QPixmap("theme:icons/decrement"));
aRemove->setStatusTip(tr("Remove URL"));
connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove())); connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove()));
auto *messageToolBar = new QToolBar; auto *messageToolBar = new QToolBar;
@ -798,7 +864,7 @@ void MessagesSettingsPage::actEdit()
void MessagesSettingsPage::actRemove() void MessagesSettingsPage::actRemove()
{ {
if (messageList->currentItem()) { if (messageList->currentItem() != nullptr) {
delete messageList->takeItem(messageList->currentRow()); delete messageList->takeItem(messageList->currentRow());
storeSettings(); storeSettings();
} }
@ -1000,7 +1066,7 @@ void DlgSettings::setTab(int index)
void DlgSettings::updateLanguage() void DlgSettings::updateLanguage()
{ {
qApp->removeTranslator(translator); qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
installNewTranslator(); installNewTranslator();
} }
@ -1094,7 +1160,7 @@ void DlgSettings::retranslateUi()
generalButton->setText(tr("General")); generalButton->setText(tr("General"));
appearanceButton->setText(tr("Appearance")); appearanceButton->setText(tr("Appearance"));
userInterfaceButton->setText(tr("User Interface")); userInterfaceButton->setText(tr("User Interface"));
deckEditorButton->setText(tr("Deck Editor")); deckEditorButton->setText(tr("Card Sources"));
messagesButton->setText(tr("Chat")); messagesButton->setText(tr("Chat"));
soundButton->setText(tr("Sound")); soundButton->setText(tr("Sound"));
shortcutsButton->setText(tr("Shortcuts")); shortcutsButton->setText(tr("Shortcuts"));

View file

@ -43,13 +43,9 @@ private slots:
void deckPathButtonClicked(); void deckPathButtonClicked();
void replaysPathButtonClicked(); void replaysPathButtonClicked();
void picsPathButtonClicked(); void picsPathButtonClicked();
void clearDownloadedPicsButtonClicked();
void cardDatabasePathButtonClicked(); void cardDatabasePathButtonClicked();
void tokenDatabasePathButtonClicked(); void tokenDatabasePathButtonClicked();
void languageBoxChanged(int index); void languageBoxChanged(int index);
void setEnabledStatus(bool);
void defaultUrlRestoreButtonClicked();
void fallbackUrlRestoreButtonClicked();
private: private:
QStringList findQmFiles(); QStringList findQmFiles();
@ -59,14 +55,12 @@ private:
QLineEdit *picsPathEdit; QLineEdit *picsPathEdit;
QLineEdit *cardDatabasePathEdit; QLineEdit *cardDatabasePathEdit;
QLineEdit *tokenDatabasePathEdit; QLineEdit *tokenDatabasePathEdit;
QLineEdit *defaultUrlEdit;
QLineEdit *fallbackUrlEdit;
QSpinBox pixmapCacheEdit; QSpinBox pixmapCacheEdit;
QGroupBox *personalGroupBox; QGroupBox *personalGroupBox;
QGroupBox *pathsGroupBox; QGroupBox *pathsGroupBox;
QComboBox languageBox; QComboBox languageBox;
QCheckBox picDownloadCheckBox;
QCheckBox updateNotificationCheckBox; QCheckBox updateNotificationCheckBox;
QCheckBox newVersionOracleCheckBox;
QComboBox updateReleaseChannelBox; QComboBox updateReleaseChannelBox;
QLabel languageLabel; QLabel languageLabel;
QLabel pixmapCacheLabel; QLabel pixmapCacheLabel;
@ -75,13 +69,7 @@ private:
QLabel picsPathLabel; QLabel picsPathLabel;
QLabel cardDatabasePathLabel; QLabel cardDatabasePathLabel;
QLabel tokenDatabasePathLabel; QLabel tokenDatabasePathLabel;
QLabel defaultUrlLabel;
QLabel fallbackUrlLabel;
QLabel urlLinkLabel;
QLabel updateReleaseChannelLabel; QLabel updateReleaseChannelLabel;
QPushButton clearDownloadedPicsButton;
QPushButton defaultUrlRestoreButton;
QPushButton fallbackUrlRestoreButton;
QCheckBox showTipsOnStartup; QCheckBox showTipsOnStartup;
}; };
@ -143,19 +131,30 @@ public:
QString getLastUpdateTime(); QString getLastUpdateTime();
private slots: private slots:
void storeSettings();
void urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int);
void setSpoilersEnabled(bool); void setSpoilersEnabled(bool);
void spoilerPathButtonClicked(); void spoilerPathButtonClicked();
void updateSpoilers(); void updateSpoilers();
void unlockSettings(); void unlockSettings();
void actAddURL();
void actRemoveURL();
void actEditURL();
void clearDownloadedPicsButtonClicked();
void resetDownloadedURLsButtonClicked();
private: private:
QPushButton clearDownloadedPicsButton;
QPushButton resetDownloadURLs;
QLabel urlLinkLabel;
QCheckBox picDownloadCheckBox;
QListWidget *urlList;
QCheckBox mcDownloadSpoilersCheckBox; QCheckBox mcDownloadSpoilersCheckBox;
QLabel msDownloadSpoilersLabel; QLabel msDownloadSpoilersLabel;
QGroupBox *mpGeneralGroupBox; QGroupBox *mpGeneralGroupBox;
QGroupBox *mpSpoilerGroupBox; QGroupBox *mpSpoilerGroupBox;
QLineEdit *mpSpoilerSavePathLineEdit; QLineEdit *mpSpoilerSavePathLineEdit;
QLabel mcSpoilerSaveLabel; QLabel mcSpoilerSaveLabel;
QLabel mcGeneralMessageLabel;
QLabel lastUpdatedLabel; QLabel lastUpdatedLabel;
QLabel infoOnSpoilersLabel; QLabel infoOnSpoilersLabel;
QPushButton *mpSpoilerPathButton; QPushButton *mpSpoilerPathButton;

View file

@ -13,7 +13,7 @@
#define MIN_TIP_IMAGE_HEIGHT 200 #define MIN_TIP_IMAGE_HEIGHT 200
#define MIN_TIP_IMAGE_WIDTH 200 #define MIN_TIP_IMAGE_WIDTH 200
#define MAX_TIP_IMAGE_HEIGHT 300 #define MAX_TIP_IMAGE_HEIGHT 300
#define MAX_TIP_IMAGE_WIDTH 300 #define MAX_TIP_IMAGE_WIDTH 500
DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent) DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent)
{ {
@ -149,9 +149,9 @@ void DlgTipOfTheDay::updateTip(int tipId)
qDebug() << "Image failed to load from" << imagePath; qDebug() << "Image failed to load from" << imagePath;
imageLabel->clear(); imageLabel->clear();
} else { } 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); 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("<i>Tip added on: " + tip.getDate().toString("yyyy.MM.dd") + "</i>"); date->setText("<i>Tip added on: " + tip.getDate().toString("yyyy.MM.dd") + "</i>");
@ -163,9 +163,7 @@ void DlgTipOfTheDay::updateTip(int tipId)
void DlgTipOfTheDay::resizeEvent(QResizeEvent *event) void DlgTipOfTheDay::resizeEvent(QResizeEvent *event)
{ {
int h = imageLabel->height(); imageLabel->setPixmap(image->scaled(imageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
int w = imageLabel->width();
imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
QWidget::resizeEvent(event); QWidget::resizeEvent(event);
} }

View file

@ -12,7 +12,7 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
filterCombo = new QComboBox; filterCombo = new QComboBox;
filterCombo->setObjectName("filterCombo"); filterCombo->setObjectName("filterCombo");
for (int i = 0; i < CardFilter::AttrEnd; i++) for (int i = 0; i < CardFilter::AttrEnd; i++)
filterCombo->addItem(tr(CardFilter::attrName(static_cast<CardFilter::Attr>(i))), QVariant(i)); filterCombo->addItem(CardFilter::attrName(static_cast<CardFilter::Attr>(i)), QVariant(i));
typeCombo = new QComboBox; typeCombo = new QComboBox;
typeCombo->setObjectName("typeCombo"); typeCombo->setObjectName("typeCombo");

View file

@ -187,11 +187,8 @@ bool FilterItem::acceptColor(const CardInfoPtr info) const
*/ */
int match_count = 0; int match_count = 0;
for (auto &it : converted_term) { for (auto &it : converted_term) {
for (auto i = info->getColors().constBegin(); i != info->getColors().constEnd(); i++) { if (info->getColors().contains(it, Qt::CaseInsensitive))
if ((*i).contains(it, Qt::CaseInsensitive)) { match_count++;
match_count++;
}
}
} }
return match_count == converted_term.length(); 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 FilterItem::acceptSet(const CardInfoPtr info) const
{ {
bool status = false; bool status = false;
for (auto i = info->getSets().constBegin(); i != info->getSets().constEnd(); i++) { for (const auto &set : info->getSets()) {
if ((*i)->getShortName().compare(term, Qt::CaseInsensitive) == 0 || if (set.getPtr()->getShortName().compare(term, Qt::CaseInsensitive) == 0 ||
(*i)->getLongName().compare(term, Qt::CaseInsensitive) == 0) { set.getPtr()->getLongName().compare(term, Qt::CaseInsensitive) == 0) {
status = true; status = true;
break; 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 * 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) * each other, we will get awkward results (i.e. comythic rare mythic rareon)
* Conditional statement will exit once a case is successful in * Conditional statement will exit once a case is successful in
* replacement OR we go through all possible cases. * 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()) { for (const auto &set : info->getSets()) {
if (rareLevel.compare(converted_term, Qt::CaseInsensitive) == 0) { if (set.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) {
return true; return true;
} }
} }

View file

@ -1,12 +1,14 @@
#ifndef FILTERTREE_H #ifndef FILTERTREE_H
#define FILTERTREE_H #define FILTERTREE_H
#include "carddatabase.h"
#include "cardfilter.h"
#include <QList> #include <QList>
#include <QMap> #include <QMap>
#include <QObject> #include <QObject>
#include <utility>
#include "carddatabase.h"
#include "cardfilter.h"
class FilterTreeNode class FilterTreeNode
{ {
@ -33,11 +35,11 @@ public:
} }
virtual FilterTreeNode *parent() const virtual FilterTreeNode *parent() const
{ {
return NULL; return nullptr;
} }
virtual FilterTreeNode *nodeAt(int /* i */) const virtual FilterTreeNode *nodeAt(int /* i */) const
{ {
return NULL; return nullptr;
} }
virtual void deleteAt(int /* i */) virtual void deleteAt(int /* i */)
{ {
@ -52,43 +54,39 @@ public:
} }
virtual int index() const 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 virtual bool isLeaf() const
{ {
return false; return false;
} }
virtual const char *textCStr() const
{
return "";
}
virtual void nodeChanged() const virtual void nodeChanged() const
{ {
if (parent() != NULL) if (parent() != nullptr)
parent()->nodeChanged(); parent()->nodeChanged();
} }
virtual void preInsertChild(const FilterTreeNode *p, int i) const virtual void preInsertChild(const FilterTreeNode *p, int i) const
{ {
if (parent() != NULL) if (parent() != nullptr)
parent()->preInsertChild(p, i); parent()->preInsertChild(p, i);
} }
virtual void postInsertChild(const FilterTreeNode *p, int i) const virtual void postInsertChild(const FilterTreeNode *p, int i) const
{ {
if (parent() != NULL) if (parent() != nullptr)
parent()->postInsertChild(p, i); parent()->postInsertChild(p, i);
} }
virtual void preRemoveChild(const FilterTreeNode *p, int i) const virtual void preRemoveChild(const FilterTreeNode *p, int i) const
{ {
if (parent() != NULL) if (parent() != nullptr)
parent()->preRemoveChild(p, i); parent()->preRemoveChild(p, i);
} }
virtual void postRemoveChild(const FilterTreeNode *p, int i) const virtual void postRemoveChild(const FilterTreeNode *p, int i) const
{ {
if (parent() != NULL) if (parent() != nullptr)
parent()->postRemoveChild(p, i); parent()->postRemoveChild(p, i);
} }
}; };
@ -100,13 +98,13 @@ protected:
public: public:
virtual ~FilterTreeBranch(); virtual ~FilterTreeBranch();
FilterTreeNode *nodeAt(int i) const; FilterTreeNode *nodeAt(int i) const override;
void deleteAt(int i); void deleteAt(int i) override;
int childCount() const int childCount() const override
{ {
return childNodes.size(); return childNodes.size();
} }
int childIndex(const FilterTreeNode *node) const; int childIndex(const FilterTreeNode *node) const override;
}; };
class FilterItemList; class FilterItemList;
@ -125,8 +123,8 @@ public:
} }
const FilterItemList *findTypeList(CardFilter::Type type) const; const FilterItemList *findTypeList(CardFilter::Type type) const;
FilterItemList *typeList(CardFilter::Type type); FilterItemList *typeList(CardFilter::Type type);
FilterTreeNode *parent() const; FilterTreeNode *parent() const override;
const char *textCStr() const const QString text() const override
{ {
return CardFilter::attrName(attr); return CardFilter::attrName(attr);
} }
@ -148,21 +146,21 @@ public:
{ {
return p->attr; return p->attr;
} }
FilterTreeNode *parent() const FilterTreeNode *parent() const override
{ {
return p; return p;
} }
int termIndex(const QString &term) const; int termIndex(const QString &term) const;
FilterTreeNode *termNode(const QString &term); FilterTreeNode *termNode(const QString &term);
const char *textCStr() const const QString text() const override
{ {
return CardFilter::typeName(type); return CardFilter::typeName(type);
} }
bool testTypeAnd(const CardInfoPtr info, CardFilter::Attr attr) const; bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const;
bool testTypeAndNot(const CardInfoPtr info, CardFilter::Attr attr) const; bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const;
bool testTypeOr(const CardInfoPtr info, CardFilter::Attr attr) const; bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const;
bool testTypeOrNot(const CardInfoPtr info, CardFilter::Attr attr) const; bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const;
}; };
class FilterItem : public FilterTreeNode class FilterItem : public FilterTreeNode
@ -173,10 +171,10 @@ private:
public: public:
const QString term; 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 CardFilter::Attr attr() const
{ {
@ -186,34 +184,30 @@ public:
{ {
return p->type; return p->type;
} }
FilterTreeNode *parent() const FilterTreeNode *parent() const override
{ {
return p; return p;
} }
QString text() const const QString text() const override
{ {
return term; return term;
} }
const char *textCStr() const bool isLeaf() const override
{
return term.toStdString().c_str();
}
bool isLeaf() const
{ {
return true; return true;
} }
bool acceptName(const CardInfoPtr info) const; bool acceptName(CardInfoPtr info) const;
bool acceptType(const CardInfoPtr info) const; bool acceptType(CardInfoPtr info) const;
bool acceptColor(const CardInfoPtr info) const; bool acceptColor(CardInfoPtr info) const;
bool acceptText(const CardInfoPtr info) const; bool acceptText(CardInfoPtr info) const;
bool acceptSet(const CardInfoPtr info) const; bool acceptSet(CardInfoPtr info) const;
bool acceptManaCost(const CardInfoPtr info) const; bool acceptManaCost(CardInfoPtr info) const;
bool acceptCmc(const CardInfoPtr info) const; bool acceptCmc(CardInfoPtr info) const;
bool acceptPowerToughness(const CardInfoPtr info, CardFilter::Attr attr) const; bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const;
bool acceptLoyalty(const CardInfoPtr info) const; bool acceptLoyalty(CardInfoPtr info) const;
bool acceptRarity(const CardInfoPtr info) const; bool acceptRarity(CardInfoPtr info) const;
bool acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) const; bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const;
bool relationCheck(int cardInfo) const; bool relationCheck(int cardInfo) const;
}; };
@ -232,47 +226,47 @@ private:
LogicMap *attrLogicMap(CardFilter::Attr attr); LogicMap *attrLogicMap(CardFilter::Attr attr);
FilterItemList *attrTypeList(CardFilter::Attr attr, CardFilter::Type type); 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(); emit changed();
} }
void preInsertChild(const FilterTreeNode *p, int i) const void preInsertChild(const FilterTreeNode *p, int i) const override
{ {
emit preInsertRow(p, i); 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); 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); 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); emit postRemoveRow(p, i);
} }
public: public:
FilterTree(); FilterTree();
~FilterTree(); ~FilterTree() override;
int findTermIndex(CardFilter::Attr attr, CardFilter::Type type, const QString &term); int findTermIndex(CardFilter::Attr attr, CardFilter::Type type, const QString &term);
int findTermIndex(const CardFilter *f); int findTermIndex(const CardFilter *f);
FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term); FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term);
FilterTreeNode *termNode(const CardFilter *f); FilterTreeNode *termNode(const CardFilter *f);
FilterTreeNode *attrTypeNode(CardFilter::Attr attr, CardFilter::Type type); 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; return 0;
} }
bool acceptsCard(const CardInfoPtr info) const; bool acceptsCard(CardInfoPtr info) const;
void clear(); void clear();
}; };

View file

@ -128,10 +128,7 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
case Qt::ToolTipRole: case Qt::ToolTipRole:
case Qt::StatusTipRole: case Qt::StatusTipRole:
case Qt::WhatsThisRole: case Qt::WhatsThisRole:
if (!node->isLeaf()) return node->text();
return tr(node->textCStr());
else
return node->text();
case Qt::CheckStateRole: case Qt::CheckStateRole:
if (node->isEnabled()) if (node->isEnabled())
return Qt::Checked; return Qt::Checked;

View file

@ -0,0 +1,49 @@
#ifndef GAME_SPECIFIC_TERMS_H
#define GAME_SPECIFIC_TERMS_H
#include <QCoreApplication>
#include <QString>
/*
* 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

View file

@ -40,7 +40,7 @@ void GameScene::addPlayer(Player *player)
players << player; players << player;
addItem(player); addItem(player);
connect(player, SIGNAL(sizeChanged()), this, SLOT(rearrange())); 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) void GameScene::removePlayer(Player *player)

View file

@ -22,7 +22,7 @@ GameView::GameView(QGraphicsScene *scene, QWidget *parent) : QGraphicsView(scene
connect(aCloseMostRecentZoneView, SIGNAL(triggered()), scene, SLOT(closeMostRecentZoneView())); connect(aCloseMostRecentZoneView, SIGNAL(triggered()), scene, SLOT(closeMostRecentZoneView()));
addAction(aCloseMostRecentZoneView); addAction(aCloseMostRecentZoneView);
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts(); refreshShortcuts();
rubberBand = new QRubberBand(QRubberBand::Rectangle, this); rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
} }

View file

@ -1,6 +1,6 @@
#include "handle_public_servers.h" #include "handle_public_servers.h"
#include "qt-json/json.h"
#include "settingscache.h" #include "settingscache.h"
#include <QJsonDocument>
#include <QMessageBox> #include <QMessageBox>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
@ -31,19 +31,16 @@ void HandlePublicServers::actFinishParsingDownloadedData()
savedHostList = uci.getServerInfo(); savedHostList = uci.getServerInfo();
// Downloaded data from GitHub // Downloaded data from GitHub
bool jsonSuccessful; QJsonParseError parseError{};
QString jsonData = QString(reply->readAll()); QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
if (parseError.error == QJsonParseError::NoError) {
auto jsonMap = QtJson::Json::parse(jsonData, jsonSuccessful).toMap(); QVariantMap jsonMap = jsonResponse.toVariant().toMap();
if (jsonSuccessful) {
updateServerINISettings(jsonMap); updateServerINISettings(jsonMap);
} else { } else {
qDebug() << "[PUBLIC SERVER HANDLER]" qDebug() << "[PUBLIC SERVER HANDLER]"
<< "JSON Parsing Error"; << "JSON Parsing Error:" << parseError.errorString();
emit sigPublicServersDownloadedUnsuccessfully(errorCode); emit sigPublicServersDownloadedUnsuccessfully(errorCode);
} }
} else { } else {
qDebug() << "[PUBLIC SERVER HANDLER]" qDebug() << "[PUBLIC SERVER HANDLER]"
<< "Error Downloading Public Servers" << errorCode; << "Error Downloading Public Servers" << errorCode;
@ -74,6 +71,10 @@ void HandlePublicServers::updateServerINISettings(QMap<QString, QVariant> jsonMa
QString serverPort = serverMap["port"].toString(); QString serverPort = serverMap["port"].toString();
QString serverSite = serverMap["site"].toString(); QString serverSite = serverMap["site"].toString();
if (serverMap.contains("websocketPort")) {
serverPort = serverMap["websocketPort"].toString();
}
bool serverFound = false; bool serverFound = false;
for (const auto &iter : savedHostList) { for (const auto &iter : savedHostList) {
// If the URL/IP matches // If the URL/IP matches

View file

@ -156,7 +156,7 @@ void MessageLogWidget::logAlwaysRevealTopCard(Player *player, CardZone *zone, bo
void MessageLogWidget::logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName) 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(sanitizeHtml(player->getName()))
.arg(cardLink(cardName)) .arg(cardLink(cardName))
.arg(sanitizeHtml(targetPlayer->getName())) .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); 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) void MessageLogWidget::logConnectionStateChanged(Player *player, bool connectionState)
{ {
if (connectionState) { if (connectionState) {
@ -341,7 +347,7 @@ void MessageLogWidget::logDrawCards(Player *player, int number)
mulliganPlayer = player; mulliganPlayer = player;
else { else {
soundEngine->playSound("draw_card"); soundEngine->playSound("draw_card");
appendHtmlServerMessage(tr("%1 draws %2 card(s).") appendHtmlServerMessage(tr("%1 draws %2 card(s).", "", number)
.arg(sanitizeHtml(player->getName())) .arg(sanitizeHtml(player->getName()))
.arg("<font color=\"blue\">" + QString::number(number) + "</font>")); .arg("<font color=\"blue\">" + QString::number(number) + "</font>"));
} }
@ -354,10 +360,11 @@ void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCar
.arg(sanitizeHtml(player->getName())) .arg(sanitizeHtml(player->getName()))
.arg(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone))); .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone)));
else else
appendHtmlServerMessage(tr("%1 is looking at the top %2 card(s) %3.") appendHtmlServerMessage(
.arg(sanitizeHtml(player->getName())) tr("%1 is looking at the top %3 card(s) %2.", "top card for singular, top %3 cards for plural", numberCards)
.arg("<font color=\"blue\">" + QString::number(numberCards) + "</font>") .arg(sanitizeHtml(player->getName()))
.arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone))); .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone))
.arg("<font color=\"blue\">" + QString::number(numberCards) + "</font>"));
} }
void MessageLogWidget::logFlipCard(Player *player, QString cardName, bool faceDown) void MessageLogWidget::logFlipCard(Player *player, QString cardName, bool faceDown)
@ -456,9 +463,11 @@ void MessageLogWidget::logRevealCards(Player *player,
int cardId, int cardId,
QString cardName, QString cardName,
Player *otherPlayer, Player *otherPlayer,
bool faceDown) bool faceDown,
int amount)
{ {
QPair<QString, QString> 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<QString, QString> temp = getFromStr(zone, amount == 1 ? cardName : QString::number(amount), cardId, false);
bool cardNameContainsStartZone = false; bool cardNameContainsStartZone = false;
if (!temp.first.isEmpty()) { if (!temp.first.isEmpty()) {
cardNameContainsStartZone = true; cardNameContainsStartZone = true;
@ -467,52 +476,61 @@ void MessageLogWidget::logRevealCards(Player *player,
QString fromStr = temp.second; QString fromStr = temp.second;
QString cardStr; QString cardStr;
if (cardNameContainsStartZone) if (cardNameContainsStartZone) {
cardStr = cardName; cardStr = cardName;
else if (cardName.isEmpty()) } else if (cardName.isEmpty()) {
cardStr = tr("a card"); if (amount == 0) {
else cardStr = tr("cards", "an unknown amount of cards");
} else {
cardStr = tr("%1 card(s)", "a card for singular, %1 cards for plural", amount)
.arg("<font color=\"blue\">" + QString::number(amount) + "</font>");
}
} else {
cardStr = cardLink(cardName); cardStr = cardLink(cardName);
}
if (cardId == -1) { if (cardId == -1) {
if (otherPlayer) if (otherPlayer) {
appendHtmlServerMessage(tr("%1 reveals %2 to %3.") appendHtmlServerMessage(tr("%1 reveals %2 to %3.")
.arg(sanitizeHtml(player->getName())) .arg(sanitizeHtml(player->getName()))
.arg(zone->getTranslatedName(true, CaseRevealZone)) .arg(zone->getTranslatedName(true, CaseRevealZone))
.arg(sanitizeHtml(otherPlayer->getName()))); .arg(sanitizeHtml(otherPlayer->getName())));
else } else {
appendHtmlServerMessage(tr("%1 reveals %2.") appendHtmlServerMessage(tr("%1 reveals %2.")
.arg(sanitizeHtml(player->getName())) .arg(sanitizeHtml(player->getName()))
.arg(zone->getTranslatedName(true, CaseRevealZone))); .arg(zone->getTranslatedName(true, CaseRevealZone)));
}
} else if (cardId == -2) { } else if (cardId == -2) {
if (otherPlayer) if (otherPlayer) {
appendHtmlServerMessage(tr("%1 randomly reveals %2%3 to %4.") appendHtmlServerMessage(tr("%1 randomly reveals %2%3 to %4.")
.arg(sanitizeHtml(player->getName())) .arg(sanitizeHtml(player->getName()))
.arg(cardStr) .arg(cardStr)
.arg(fromStr) .arg(fromStr)
.arg(sanitizeHtml(otherPlayer->getName()))); .arg(sanitizeHtml(otherPlayer->getName())));
else } else {
appendHtmlServerMessage( appendHtmlServerMessage(
tr("%1 randomly reveals %2%3.").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); tr("%1 randomly reveals %2%3.").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr));
}
} else { } else {
if (faceDown && player == otherPlayer) { if (faceDown && player == otherPlayer) {
if (cardName.isEmpty()) if (cardName.isEmpty()) {
appendHtmlServerMessage( appendHtmlServerMessage(
tr("%1 peeks at face down card #%2.").arg(sanitizeHtml(player->getName())).arg(cardId)); 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.") appendHtmlServerMessage(tr("%1 peeks at face down card #%2: %3.")
.arg(sanitizeHtml(player->getName())) .arg(sanitizeHtml(player->getName()))
.arg(cardId) .arg(cardId)
.arg(cardStr)); .arg(cardStr));
} else if (otherPlayer) }
} else if (otherPlayer) {
appendHtmlServerMessage(tr("%1 reveals %2%3 to %4.") appendHtmlServerMessage(tr("%1 reveals %2%3 to %4.")
.arg(sanitizeHtml(player->getName())) .arg(sanitizeHtml(player->getName()))
.arg(cardStr) .arg(cardStr)
.arg(fromStr) .arg(fromStr)
.arg(sanitizeHtml(otherPlayer->getName()))); .arg(sanitizeHtml(otherPlayer->getName())));
else } else {
appendHtmlServerMessage( appendHtmlServerMessage(
tr("%1 reveals %2%3.").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); 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; QString finalStr;
int delta = abs(oldValue - value); int delta = abs(oldValue - value);
if (value > oldValue) 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 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; QString colorStr;
switch (counterId) { switch (counterId) {
case 0: case 0:
colorStr = tr("red", "", delta); colorStr = tr("red counter(s)", "", delta);
break; break;
case 1: case 1:
colorStr = tr("yellow", "", delta); colorStr = tr("yellow counter(s)", "", delta);
break; break;
case 2: case 2:
colorStr = tr("green", "", delta); colorStr = tr("green counter(s)", "", delta);
break; break;
default:; default:;
} }
@ -676,13 +694,22 @@ void MessageLogWidget::logSetDoesntUntap(Player *player, CardItem *card, bool do
void MessageLogWidget::logSetPT(Player *player, CardItem *card, QString newPT) void MessageLogWidget::logSetPT(Player *player, CardItem *card, QString newPT)
{ {
if (currentContext == MessageContext_MoveCard) if (currentContext == MessageContext_MoveCard) {
moveCardPT.insert(card, newPT); moveCardPT.insert(card, newPT);
else } else {
appendHtmlServerMessage(tr("%1 sets PT of %2 to %3.") QString name = card->getName();
.arg(sanitizeHtml(player->getName())) if (name.isEmpty()) {
.arg(cardLink(card->getName())) name = QString("<font color=\"blue\">card #%1</font>").arg(sanitizeHtml(QString::number(card->getId())));
.arg(QString("<font color=\"blue\">%1</font>").arg(sanitizeHtml(newPT)))); } 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) 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(logStopDumpZone(Player *, CardZone *)), this, SLOT(logStopDumpZone(Player *, CardZone *)));
connect(player, SIGNAL(logDrawCards(Player *, int)), this, SLOT(logDrawCards(Player *, int))); 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(logUndoDraw(Player *, QString)), this, SLOT(logUndoDraw(Player *, QString)));
connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool)), this, connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int)), this,
SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *, bool))); SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int)));
connect(player, SIGNAL(logAlwaysRevealTopCard(Player *, CardZone *, bool)), this, connect(player, SIGNAL(logAlwaysRevealTopCard(Player *, CardZone *, bool)), this,
SLOT(logAlwaysRevealTopCard(Player *, CardZone *, bool))); SLOT(logAlwaysRevealTopCard(Player *, CardZone *, bool)));
} }

View file

@ -48,7 +48,7 @@ private:
const QString stackConstant() const; const QString stackConstant() const;
QString sanitizeHtml(QString dirty) const; QString sanitizeHtml(QString dirty) const;
QString cardLink(const QString cardName) const; QString cardLink(QString cardName) const;
QPair<QString, QString> getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange) const; QPair<QString, QString> getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange) const;
public slots: public slots:
@ -57,6 +57,7 @@ public slots:
void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal); void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal);
void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName); void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName);
void logConcede(Player *player); void logConcede(Player *player);
void logUnconcede(Player *player);
void logConnectionStateChanged(Player *player, bool connectionState); void logConnectionStateChanged(Player *player, bool connectionState);
void logCreateArrow(Player *player, void logCreateArrow(Player *player,
Player *startPlayer, Player *startPlayer,
@ -83,8 +84,13 @@ public slots:
void logMulligan(Player *player, int number); void logMulligan(Player *player, int number);
void logReplayStarted(int gameId); void logReplayStarted(int gameId);
void logReadyStart(Player *player); void logReadyStart(Player *player);
void void logRevealCards(Player *player,
logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer, bool faceDown); CardZone *zone,
int cardId,
QString cardName,
Player *otherPlayer,
bool faceDown,
int amount);
void logRollDie(Player *player, int sides, int roll); void logRollDie(Player *player, int sides, int roll);
void logSay(Player *player, QString message); void logSay(Player *player, QString message);
void logSetActivePhase(int phase); void logSetActivePhase(int phase);
@ -108,7 +114,7 @@ public:
MessageLogWidget(const TabSupervisor *_tabSupervisor, MessageLogWidget(const TabSupervisor *_tabSupervisor,
const UserlistProxy *_userlistProxy, const UserlistProxy *_userlistProxy,
TabGame *_game, TabGame *_game,
QWidget *parent = 0); QWidget *parent = nullptr);
}; };
#endif #endif

View file

@ -24,14 +24,14 @@ PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_
connect(activeAnimationTimer, SIGNAL(timeout()), this, SLOT(updateAnimation())); connect(activeAnimationTimer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
activeAnimationTimer->setSingleShot(false); activeAnimationTimer->setSingleShot(false);
} else } else
activeAnimationCounter = 9.0; activeAnimationCounter = 9;
setCacheMode(DeviceCoordinateCache); setCacheMode(DeviceCoordinateCache);
} }
QRectF PhaseButton::boundingRect() const 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*/) 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 iconRect = boundingRect().adjusted(3, 3, -3, -3);
QRectF translatedIconRect = painter->combinedTransform().mapRect(iconRect); QRectF translatedIconRect = painter->combinedTransform().mapRect(iconRect);
qreal scaleFactor = translatedIconRect.width() / iconRect.width(); qreal scaleFactor = translatedIconRect.width() / iconRect.width();
QPixmap iconPixmap = PhasePixmapGenerator::generatePixmap(round(translatedIconRect.height()), name); QPixmap iconPixmap =
PhasePixmapGenerator::generatePixmap(static_cast<int>(round(translatedIconRect.height())), name);
painter->setBrush(QColor(220 * (activeAnimationCounter / 10.0), 220 * (activeAnimationCounter / 10.0), painter->setBrush(QColor(static_cast<int>(220 * (activeAnimationCounter / 10.0)),
220 * (activeAnimationCounter / 10.0))); static_cast<int>(220 * (activeAnimationCounter / 10.0)),
static_cast<int>(220 * (activeAnimationCounter / 10.0))));
painter->setPen(Qt::gray); painter->setPen(Qt::gray);
painter->drawRect(0, 0, width - 1, width - 1); painter->drawRect(0, 0, static_cast<int>(width - 1), static_cast<int>(width - 1));
painter->save(); painter->save();
painter->resetTransform(); painter->resetTransform();
painter->drawPixmap(iconPixmap.rect().translated(round(3 * scaleFactor), round(3 * scaleFactor)), iconPixmap, painter->drawPixmap(iconPixmap.rect().translated(static_cast<int>(round(3 * scaleFactor)),
iconPixmap.rect()); static_cast<int>(round(3 * scaleFactor))),
iconPixmap, iconPixmap.rect());
painter->restore(); painter->restore();
painter->setBrush(QColor(0, 0, 0, 255 * ((10 - activeAnimationCounter) / 15.0))); painter->setBrush(QColor(0, 0, 0, static_cast<int>(255 * ((10 - activeAnimationCounter) / 15.0))));
painter->setPen(Qt::gray); painter->setPen(Qt::gray);
painter->drawRect(0, 0, width - 1, width - 1); painter->drawRect(0, 0, static_cast<int>(width - 1), static_cast<int>(width - 1));
} }
void PhaseButton::setWidth(double _width) void PhaseButton::setWidth(double _width)
@ -105,9 +108,9 @@ void PhaseButton::triggerDoubleClickAction()
PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) PhasesToolbar::PhasesToolbar(QGraphicsItem *parent)
: QGraphicsItem(parent), width(100), height(100), ySpacing(1), symbolSize(8) : 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())); connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll()));
QAction *aDrawCard = new QAction(this); auto *aDrawCard = new QAction(this);
connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard())); connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard()));
PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll); PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll);
@ -125,10 +128,10 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent)
buttonList << untapButton << upkeepButton << drawButton << main1Button << combatStartButton << combatAttackersButton buttonList << untapButton << upkeepButton << drawButton << main1Button << combatStartButton << combatAttackersButton
<< combatBlockersButton << combatDamageButton << combatEndButton << main2Button << cleanupButton; << combatBlockersButton << combatDamageButton << combatEndButton << main2Button << cleanupButton;
for (int i = 0; i < buttonList.size(); ++i) for (auto &i : buttonList)
connect(buttonList[i], SIGNAL(clicked()), this, SLOT(phaseButtonClicked())); 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())); connect(nextTurnButton, SIGNAL(clicked()), this, SLOT(actNextTurn()));
rearrangeButtons(); rearrangeButtons();
@ -138,7 +141,7 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent)
QRectF PhasesToolbar::boundingRect() const QRectF PhasesToolbar::boundingRect() const
{ {
return QRectF(0, 0, width, height); return {0, 0, width, height};
} }
void PhasesToolbar::retranslateUi() void PhasesToolbar::retranslateUi()
@ -186,8 +189,8 @@ const double PhasesToolbar::marginSize = 3;
void PhasesToolbar::rearrangeButtons() void PhasesToolbar::rearrangeButtons()
{ {
for (int i = 0; i < buttonList.size(); ++i) for (auto &i : buttonList)
buttonList[i]->setWidth(symbolSize); i->setWidth(symbolSize);
nextTurnButton->setWidth(symbolSize); nextTurnButton->setWidth(symbolSize);
double y = marginSize; double y = marginSize;
@ -208,7 +211,7 @@ void PhasesToolbar::rearrangeButtons()
buttonList[10]->setPos(marginSize, y += symbolSize); buttonList[10]->setPos(marginSize, y += symbolSize);
y += ySpacing; y += ySpacing;
y += ySpacing; y += ySpacing;
nextTurnButton->setPos(marginSize, y += symbolSize); nextTurnButton->setPos(marginSize, y + symbolSize);
} }
void PhasesToolbar::setHeight(double _height) void PhasesToolbar::setHeight(double _height)
@ -232,14 +235,21 @@ void PhasesToolbar::setActivePhase(int phase)
buttonList[i]->setActive(i == phase); buttonList[i]->setActive(i == phase);
} }
void PhasesToolbar::triggerPhaseAction(int phase)
{
if (0 <= phase && phase < buttonList.size()) {
buttonList[phase]->triggerDoubleClickAction();
}
}
void PhasesToolbar::phaseButtonClicked() void PhasesToolbar::phaseButtonClicked()
{ {
PhaseButton *button = qobject_cast<PhaseButton *>(sender()); auto *button = qobject_cast<PhaseButton *>(sender());
if (button->getActive()) if (button->getActive())
button->triggerDoubleClickAction(); button->triggerDoubleClickAction();
Command_SetActivePhase cmd; Command_SetActivePhase cmd;
cmd.set_phase(buttonList.indexOf(button)); cmd.set_phase(static_cast<google::protobuf::uint32>(buttonList.indexOf(button)));
emit sendGameCommand(cmd, -1); emit sendGameCommand(cmd, -1);
} }

View file

@ -27,16 +27,16 @@ private:
QAction *doubleClickAction; QAction *doubleClickAction;
double width; double width;
void updatePixmap(QPixmap &pixmap); // void updatePixmap(QPixmap &pixmap);
private slots: private slots:
void updateAnimation(); void updateAnimation();
public: public:
PhaseButton(const QString &_name, explicit PhaseButton(const QString &_name,
QGraphicsItem *parent = 0, QGraphicsItem *parent = nullptr,
QAction *_doubleClickAction = 0, QAction *_doubleClickAction = nullptr,
bool _highlightable = true); bool _highlightable = true);
QRectF boundingRect() const; QRectF boundingRect() const override;
void setWidth(double _width); void setWidth(double _width);
void setActive(bool _active); void setActive(bool _active);
bool getActive() const bool getActive() const
@ -48,9 +48,9 @@ signals:
void clicked(); void clicked();
protected: protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event); void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
}; };
class PhasesToolbar : public QObject, public QGraphicsItem class PhasesToolbar : public QObject, public QGraphicsItem
@ -67,8 +67,8 @@ private:
void rearrangeButtons(); void rearrangeButtons();
public: public:
PhasesToolbar(QGraphicsItem *parent = 0); explicit PhasesToolbar(QGraphicsItem *parent = nullptr);
QRectF boundingRect() const; QRectF boundingRect() const override;
void retranslateUi(); void retranslateUi();
void setHeight(double _height); void setHeight(double _height);
double getWidth() const double getWidth() const
@ -82,6 +82,7 @@ public:
QString getLongPhaseName(int phase) const; QString getLongPhaseName(int phase) const;
public slots: public slots:
void setActivePhase(int phase); void setActivePhase(int phase);
void triggerPhaseAction(int phase);
private slots: private slots:
void phaseButtonClicked(); void phaseButtonClicked();
void actNextTurn(); void actNextTurn();
@ -91,7 +92,7 @@ signals:
void sendGameCommand(const ::google::protobuf::Message &command, int playerId); void sendGameCommand(const ::google::protobuf::Message &command, int playerId);
protected: protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override;
}; };
#endif #endif

View file

@ -25,32 +25,14 @@
// never cache more than 300 cards at once for a single deck // never cache more than 300 cards at once for a single deck
#define CACHED_CARD_PER_DECK_MAX 300 #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)) PictureToLoad::PictureToLoad(CardInfoPtr _card) : card(std::move(_card))
{ {
/* #2479 will expand this into a list of Urls */ urlTemplates = settingsCache->downloads().getAllURLs();
urlTemplates.append(settingsCache->getPicUrl());
urlTemplates.append(settingsCache->getPicUrlFallback());
if (card) { if (card) {
sortedSets = card->getSets(); for (const auto &set : card->getSets()) {
sortedSets << set.getPtr();
}
qSort(sortedSets.begin(), sortedSets.end(), SetDownloadPriorityComparator()); qSort(sortedSets.begin(), sortedSets.end(), SetDownloadPriorityComparator());
// The first time called, nextSet will also populate the Urls for the first set. // The first time called, nextSet will also populate the Urls for the first set.
nextSet(); nextSet();
@ -72,7 +54,7 @@ void PictureToLoad::populateSetUrls()
} }
} }
foreach (QString urlTemplate, urlTemplates) { for (const QString &urlTemplate : urlTemplates) {
QString transformedUrl = transformUrl(urlTemplate); QString transformedUrl = transformUrl(urlTemplate);
if (!transformedUrl.isEmpty()) { if (!transformedUrl.isEmpty()) {
@ -115,10 +97,8 @@ QString PictureToLoad::getSetName() const
} }
} }
QStringList PictureLoaderWorker::md5Blacklist = QStringList() // Card back returned by gatherer when card is not found
<< "db0c48db407a907c16ade38de048a441"; // card back returned QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441";
// by gatherer when
// card is not found
PictureLoaderWorker::PictureLoaderWorker() : QObject(nullptr), downloadRunning(false), loadQueueRunning(false) PictureLoaderWorker::PictureLoaderWorker() : QObject(nullptr), downloadRunning(false), loadQueueRunning(false)
{ {
@ -145,12 +125,12 @@ PictureLoaderWorker::~PictureLoaderWorker()
void PictureLoaderWorker::processLoadQueue() void PictureLoaderWorker::processLoadQueue()
{ {
if (loadQueueRunning) if (loadQueueRunning) {
return; return;
}
loadQueueRunning = true; loadQueueRunning = true;
forever while (true) {
{
mutex.lock(); mutex.lock();
if (loadQueue.isEmpty()) { if (loadQueue.isEmpty()) {
mutex.unlock(); mutex.unlock();
@ -163,23 +143,26 @@ void PictureLoaderWorker::processLoadQueue()
QString setName = cardBeingLoaded.getSetName(); QString setName = cardBeingLoaded.getSetName();
QString cardName = cardBeingLoaded.getCard()->getName(); QString cardName = cardBeingLoaded.getCard()->getName();
QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName(); QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName();
qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Trying to load picture"; qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Trying to load picture";
if (cardImageExistsOnDisk(setName, correctedCardName)) if (cardImageExistsOnDisk(setName, correctedCardName)) {
continue; continue;
}
if (picDownload) { if (picDownload) {
qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName
<< "]: Picture not found on disk, trying to download"; << "]: Picture not found on disk, trying to download";
cardsToDownload.append(cardBeingLoaded); cardsToDownload.append(cardBeingLoaded);
cardBeingLoaded.clear(); cardBeingLoaded.clear();
if (!downloadRunning) if (!downloadRunning) {
startNextPicDownload(); startNextPicDownload();
}
} else { } else {
if (cardBeingLoaded.nextSet()) { if (cardBeingLoaded.nextSet()) {
qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName
<< "]: Picture NOT found and download disabled, moving to next " << "]: Picture NOT found and download disabled, moving to next "
"set (newset: " "set (new set: "
<< setName << " card: " << cardName << ")"; << setName << " card: " << cardName << ")";
mutex.lock(); mutex.lock();
loadQueue.prepend(cardBeingLoaded); loadQueue.prepend(cardBeingLoaded);
@ -188,7 +171,7 @@ void PictureLoaderWorker::processLoadQueue()
} else { } else {
qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName
<< "]: Picture NOT found, download disabled, no more sets to " << "]: Picture NOT found, download disabled, no more sets to "
"try: BAILING OUT (oldset: " "try: BAILING OUT (old set: "
<< setName << " card: " << cardName << ")"; << setName << " card: " << cardName << ")";
imageLoaded(cardBeingLoaded.getCard(), QImage()); 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 // Iterates through the list of paths, searching for images with the desired
// name with any QImageReader-supported // name with any QImageReader-supported
// extension // extension
for (int i = 0; i < picsPaths.length(); i++) { for (const auto &picsPath : picsPaths) {
imgReader.setFileName(picsPaths.at(i)); imgReader.setFileName(picsPath);
if (imgReader.read(&image)) { if (imgReader.read(&image)) {
qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
<< "]: Picture found on disk."; << "]: Picture found on disk.";
imageLoaded(cardBeingLoaded.getCard(), image); imageLoaded(cardBeingLoaded.getCard(), image);
return true; return true;
} }
imgReader.setFileName(picsPaths.at(i) + ".full"); imgReader.setFileName(picsPath + ".full");
if (imgReader.read(&image)) { if (imgReader.read(&image)) {
qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
<< "]: Picture.full found on disk."; << "]: Picture.full found on disk.";
imageLoaded(cardBeingLoaded.getCard(), image); imageLoaded(cardBeingLoaded.getCard(), image);
return true; return true;
} }
imgReader.setFileName(picsPaths.at(i) + ".xlhq"); imgReader.setFileName(picsPath + ".xlhq");
if (imgReader.read(&image)) { if (imgReader.read(&image)) {
qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
<< "]: Picture.xlhq found on disk."; << "]: Picture.xlhq found on disk.";
@ -248,7 +231,7 @@ bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &corre
return false; 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 /* This function takes Url templates and substitutes actual card details
into the url. This is used for making Urls with follow a predictable format 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(); CardSetPtr set = getCurrentSet();
QMap<QString, QString> transformMap = QMap<QString, QString>(); QMap<QString, QString> transformMap = QMap<QString, QString>();
// name
transformMap["!name!"] = card->getName(); transformMap["!name!"] = card->getName();
transformMap["!name_lower!"] = card->getName().toLower(); transformMap["!name_lower!"] = card->getName().toLower();
transformMap["!corrected_name!"] = card->getCorrectedName(); transformMap["!corrected_name!"] = card->getCorrectedName();
transformMap["!corrected_name_lower!"] = card->getCorrectedName().toLower(); 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) { if (set) {
transformMap["!cardid!"] = QString::number(card->getMuId(set->getShortName()));
transformMap["!collectornumber!"] = card->getCollectorNumber(set->getShortName());
transformMap["!setcode!"] = set->getShortName(); transformMap["!setcode!"] = set->getShortName();
transformMap["!setcode_lower!"] = set->getShortName().toLower(); transformMap["!setcode_lower!"] = set->getShortName().toLower();
transformMap["!setname!"] = set->getLongName(); transformMap["!setname!"] = set->getLongName();
transformMap["!setname_lower!"] = set->getLongName().toLower(); transformMap["!setname_lower!"] = set->getLongName().toLower();
} else {
transformMap["!cardid!"] = QString(); QRegExp rxSetProp("!set:([^!]+)!");
transformMap["!collectornumber!"] = QString(); pos = 0; // Defined above
transformMap["!setcode!"] = QString(); while ((pos = rxSetProp.indexIn(transformedUrl, pos)) != -1) {
transformMap["!setcode_lower!"] = QString(); QString propertyName = rxSetProp.cap(1);
transformMap["!setname!"] = QString(); pos += rxSetProp.matchedLength();
transformMap["!setname_lower!"] = QString(); 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 (transformedUrl.contains(prop)) {
if (!transformMap[prop].isEmpty()) { if (!transformMap[prop].isEmpty()) {
transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop])); transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop]));
@ -372,8 +379,8 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
return; return;
} }
const QByteArray &picData = reply->peek(reply->size()); // peek is used to keep the data in the buffer // peek is used to keep the data in the buffer for use by QImageReader
// for use by QImageReader const QByteArray &picData = reply->peek(reply->size());
if (imageIsBlackListed(picData)) { if (imageIsBlackListed(picData)) {
qDebug() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() qDebug() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName()
@ -395,8 +402,9 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
// prior to reading the // prior to reading the
// QImageReader data // QImageReader data
// into a QImage object, as that wipes the QImageReader buffer // into a QImage object, as that wipes the QImageReader buffer
if (extension == ".jpeg") if (extension == ".jpeg") {
extension = ".jpg"; extension = ".jpg";
}
if (imgReader.read(&testImage)) { if (imgReader.read(&testImage)) {
QString setName = cardBeingDownloaded.getSetName(); QString setName = cardBeingDownloaded.getSetName();
@ -410,8 +418,9 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
QFile newPic(picsPath + "/downloadedPics/" + setName + "/" + QFile newPic(picsPath + "/downloadedPics/" + setName + "/" +
cardBeingDownloaded.getCard()->getCorrectedName() + extension); cardBeingDownloaded.getCard()->getCorrectedName() + extension);
if (!newPic.open(QIODevice::WriteOnly)) if (!newPic.open(QIODevice::WriteOnly)) {
return; return;
}
newPic.write(picData); newPic.write(picData);
newPic.close(); newPic.close();
} }
@ -436,15 +445,16 @@ void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card)
QMutexLocker locker(&mutex); QMutexLocker locker(&mutex);
// avoid queueing the same card more than once // 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; return;
}
foreach (PictureToLoad pic, loadQueue) { for (const PictureToLoad &pic : loadQueue) {
if (pic.getCard() == card) if (pic.getCard() == card)
return; return;
} }
foreach (PictureToLoad pic, cardsToDownload) { for (const PictureToLoad &pic : cardsToDownload) {
if (pic.getCard() == card) if (pic.getCard() == card)
return; return;
} }
@ -466,7 +476,7 @@ void PictureLoaderWorker::picsPathChanged()
customPicsPath = settingsCache->getCustomPicsPath(); customPicsPath = settingsCache->getCustomPicsPath();
} }
PictureLoader::PictureLoader() : QObject(0) PictureLoader::PictureLoader() : QObject(nullptr)
{ {
worker = new PictureLoaderWorker; worker = new PictureLoaderWorker;
connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); 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) void PictureLoader::getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size)
{ {
if (card == nullptr) if (card == nullptr) {
return; 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 key = card->getPixmapCacheKey();
QString sizekey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height()); QString sizeKey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height());
if (QPixmapCache::find(sizekey, &pixmap)) if (QPixmapCache::find(sizeKey, &pixmap))
return; return;
// load the image and create a copy of the correct size // load the image and create a copy of the correct size
QPixmap bigPixmap; QPixmap bigPixmap;
if (QPixmapCache::find(key, &bigPixmap)) { if (QPixmapCache::find(key, &bigPixmap)) {
pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPixmapCache::insert(sizekey, pixmap); QPixmapCache::insert(sizeKey, pixmap);
return; return;
} }
@ -532,8 +543,9 @@ void PictureLoader::imageLoaded(CardInfoPtr card, const QImage &image)
void PictureLoader::clearPixmapCache(CardInfoPtr card) void PictureLoader::clearPixmapCache(CardInfoPtr card)
{ {
if (card) if (card) {
QPixmapCache::remove(card->getPixmapCacheKey()); QPixmapCache::remove(card->getPixmapCacheKey());
}
} }
void PictureLoader::clearPixmapCache() void PictureLoader::clearPixmapCache()
@ -546,13 +558,15 @@ void PictureLoader::cacheCardPixmaps(QList<CardInfoPtr> cards)
QPixmap tmp; QPixmap tmp;
int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX); int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX);
for (int i = 0; i < max; ++i) { for (int i = 0; i < max; ++i) {
CardInfoPtr card = cards.at(i); const CardInfoPtr &card = cards.at(i);
if (!card) if (!card) {
continue; continue;
}
QString key = card->getPixmapCacheKey(); QString key = card->getPixmapCacheKey();
if (QPixmapCache::find(key, &tmp)) if (QPixmapCache::find(key, &tmp)) {
continue; continue;
}
getInstance().worker->enqueueImageLoad(card); getInstance().worker->enqueueImageLoad(card);
} }

View file

@ -14,7 +14,23 @@ class QThread;
class PictureToLoad class PictureToLoad
{ {
private: 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; CardInfoPtr card;
QList<CardSetPtr> sortedSets; QList<CardSetPtr> sortedSets;
@ -24,7 +40,8 @@ private:
CardSetPtr currentSet; CardSetPtr currentSet;
public: public:
PictureToLoad(CardInfoPtr _card = CardInfoPtr()); explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr());
CardInfoPtr getCard() const CardInfoPtr getCard() const
{ {
return card; return card;
@ -42,7 +59,7 @@ public:
return currentSet; return currentSet;
} }
QString getSetName() const; QString getSetName() const;
QString transformUrl(QString urlTemplate) const; QString transformUrl(const QString &urlTemplate) const;
bool nextSet(); bool nextSet();
bool nextUrl(); bool nextUrl();
void populateSetUrls(); void populateSetUrls();
@ -52,8 +69,8 @@ class PictureLoaderWorker : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
PictureLoaderWorker(); explicit PictureLoaderWorker();
~PictureLoaderWorker(); ~PictureLoaderWorker() override;
void enqueueImageLoad(CardInfoPtr card); void enqueueImageLoad(CardInfoPtr card);
@ -70,8 +87,8 @@ private:
PictureToLoad cardBeingDownloaded; PictureToLoad cardBeingDownloaded;
bool picDownload, downloadRunning, loadQueueRunning; bool picDownload, downloadRunning, loadQueueRunning;
void startNextPicDownload(); void startNextPicDownload();
bool cardImageExistsOnDisk(QString &setName, QString &correctedCardname); bool cardImageExistsOnDisk(QString &, QString &);
bool imageIsBlackListed(const QByteArray &picData); bool imageIsBlackListed(const QByteArray &);
private slots: private slots:
void picDownloadFinished(QNetworkReply *reply); void picDownloadFinished(QNetworkReply *reply);
void picDownloadFailed(); void picDownloadFailed();
@ -96,8 +113,8 @@ public:
} }
private: private:
PictureLoader(); explicit PictureLoader();
~PictureLoader(); ~PictureLoader() override;
// Singleton - Don't implement copy constructor and assign operator // Singleton - Don't implement copy constructor and assign operator
PictureLoader(PictureLoader const &); PictureLoader(PictureLoader const &);
void operator=(PictureLoader const &); void operator=(PictureLoader const &);

File diff suppressed because it is too large Load diff

View file

@ -40,7 +40,7 @@ class CommandContainer;
class GameCommand; class GameCommand;
class GameEvent; class GameEvent;
class GameEventContext; class GameEventContext;
class Event_ConnectionStateChanged; // class Event_ConnectionStateChanged;
class Event_GameSay; class Event_GameSay;
class Event_Shuffle; class Event_Shuffle;
class Event_RollDie; class Event_RollDie;
@ -79,17 +79,17 @@ public:
{ {
Type = typeOther Type = typeOther
}; };
int type() const int type() const override
{ {
return Type; return Type;
} }
PlayerArea(QGraphicsItem *parent = 0); explicit PlayerArea(QGraphicsItem *parent = nullptr);
QRectF boundingRect() const QRectF boundingRect() const override
{ {
return bRect; 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); void setSize(qreal width, qreal height);
}; };
@ -127,12 +127,17 @@ signals:
void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation);
void logDumpZone(Player *player, CardZone *zone, int numberCards); void logDumpZone(Player *player, CardZone *zone, int numberCards);
void logStopDumpZone(Player *player, CardZone *zone); void logStopDumpZone(Player *player, CardZone *zone);
void void logRevealCards(Player *player,
logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer, bool faceDown); CardZone *zone,
int cardId,
QString cardName,
Player *otherPlayer,
bool faceDown,
int amount);
void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal); void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal);
void sizeChanged(); void sizeChanged();
void gameConceded(); void playerCountChanged();
public slots: public slots:
void actUntapAll(); void actUntapAll();
void actRollDie(); void actRollDie();
@ -144,6 +149,8 @@ public slots:
void actUndoDraw(); void actUndoDraw();
void actMulligan(); void actMulligan();
void actMoveTopCardToPlayFaceDown(); void actMoveTopCardToPlayFaceDown();
void actMoveTopCardToGrave();
void actMoveTopCardToExile();
void actMoveTopCardsToGrave(); void actMoveTopCardsToGrave();
void actMoveTopCardsToExile(); void actMoveTopCardsToExile();
void actMoveTopCardToBottom(); void actMoveTopCardToBottom();
@ -201,10 +208,10 @@ private:
QAction *aMoveHandToTopLibrary, *aMoveHandToBottomLibrary, *aMoveHandToGrave, *aMoveHandToRfg, QAction *aMoveHandToTopLibrary, *aMoveHandToBottomLibrary, *aMoveHandToGrave, *aMoveHandToRfg,
*aMoveGraveToTopLibrary, *aMoveGraveToBottomLibrary, *aMoveGraveToHand, *aMoveGraveToRfg, *aMoveRfgToTopLibrary, *aMoveGraveToTopLibrary, *aMoveGraveToBottomLibrary, *aMoveGraveToHand, *aMoveGraveToRfg, *aMoveRfgToTopLibrary,
*aMoveRfgToBottomLibrary, *aMoveRfgToHand, *aMoveRfgToGrave, *aViewLibrary, *aViewTopCards, *aMoveRfgToBottomLibrary, *aMoveRfgToHand, *aMoveRfgToGrave, *aViewLibrary, *aViewTopCards,
*aAlwaysRevealTopCard, *aOpenDeckInDeckEditor, *aMoveTopCardsToGrave, *aMoveTopCardsToExile, *aAlwaysRevealTopCard, *aOpenDeckInDeckEditor, *aMoveTopCardToGraveyard, *aMoveTopCardToExile,
*aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg, *aViewSideboard, *aDrawCard, *aDrawCards, *aUndoDraw, *aMoveTopCardsToGraveyard, *aMoveTopCardsToExile, *aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg,
*aMulligan, *aShuffle, *aMoveTopToPlayFaceDown, *aUntapAll, *aRollDie, *aCreateToken, *aCreateAnotherToken, *aViewSideboard, *aDrawCard, *aDrawCards, *aUndoDraw, *aMulligan, *aShuffle, *aMoveTopToPlayFaceDown,
*aCardMenu, *aMoveBottomCardToGrave; *aUntapAll, *aRollDie, *aCreateToken, *aCreateAnotherToken, *aCardMenu, *aMoveBottomCardToGrave;
QList<QAction *> aAddCounter, aSetCounter, aRemoveCounter; QList<QAction *> aAddCounter, aSetCounter, aRemoveCounter;
QAction *aPlay, *aPlayFacedown, *aHide, *aTap, *aDoesntUntap, *aAttach, *aUnattach, *aDrawArrow, *aSetPT, *aResetPT, 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 createCard(const CardItem *sourceCard, const QString &dbCardName, bool attach = false);
void createAttachedCard(const CardItem *sourceCard, const QString &dbCardName); void createAttachedCard(const CardItem *sourceCard, const QString &dbCardName);
bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation); bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation);
QString dbNameFromTokenDisplayName(const QString &tokenName);
QRectF bRect; QRectF bRect;
@ -259,7 +265,7 @@ private:
void initSayMenu(); void initSayMenu();
void eventConnectionStateChanged(const Event_ConnectionStateChanged &event); // void eventConnectionStateChanged(const Event_ConnectionStateChanged &event);
void eventGameSay(const Event_GameSay &event); void eventGameSay(const Event_GameSay &event);
void eventShuffle(const Event_Shuffle &event); void eventShuffle(const Event_Shuffle &event);
void eventRollDie(const Event_RollDie &event); void eventRollDie(const Event_RollDie &event);
@ -306,12 +312,12 @@ public:
{ {
Type = typeOther Type = typeOther
}; };
int type() const int type() const override
{ {
return Type; return Type;
} }
QRectF boundingRect() const; QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void playCard(CardItem *c, bool faceDown, bool tapped); void playCard(CardItem *c, bool faceDown, bool tapped);
void addCard(CardItem *c); void addCard(CardItem *c);
@ -334,7 +340,7 @@ public:
} }
Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_parent); Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_parent);
~Player(); ~Player() override;
void retranslateUi(); void retranslateUi();
void clear(); void clear();
TabGame *getGame() const TabGame *getGame() const

View file

@ -1,5 +1,4 @@
#include "releasechannel.h" #include "releasechannel.h"
#include "qt-json/json.h"
#include "version_string.h" #include "version_string.h"
#include <QJsonArray> #include <QJsonArray>
@ -93,21 +92,20 @@ QString StableReleaseChannel::getReleaseChannelUrl() const
void StableReleaseChannel::releaseListFinished() void StableReleaseChannel::releaseListFinished()
{ {
QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); auto *reply = static_cast<QNetworkReply *>(sender());
bool ok; QJsonParseError parseError{};
QString tmp = QString(reply->readAll()); QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
reply->deleteLater(); reply->deleteLater();
if (parseError.error != QJsonParseError::NoError) {
QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap(); qWarning() << "No reply received from the release update server.";
if (!ok) {
qWarning() << "No reply received from the release update server:" << tmp;
emit error(tr("No reply received from the release update server.")); emit error(tr("No reply received from the release update server."));
return; return;
} }
QVariantMap resultMap = jsonResponse.toVariant().toMap();
if (!(resultMap.contains("name") && resultMap.contains("html_url") && resultMap.contains("tag_name") && if (!(resultMap.contains("name") && resultMap.contains("html_url") && resultMap.contains("tag_name") &&
resultMap.contains("published_at"))) { 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.")); emit error(tr("Invalid reply received from the release update server."));
return; return;
} }
@ -145,7 +143,7 @@ void StableReleaseChannel::releaseListFinished()
QString myHash = QString(VERSION_COMMIT); QString myHash = QString(VERSION_COMMIT);
qDebug() << "Current hash=" << myHash << "update hash=" << shortHash; 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() << "desc=" << lastRelease->getDescriptionUrl() << "date=" << lastRelease->getPublishDate()
<< "url=" << lastRelease->getDownloadUrl(); << "url=" << lastRelease->getDownloadUrl();
@ -158,26 +156,25 @@ void StableReleaseChannel::releaseListFinished()
void StableReleaseChannel::tagListFinished() void StableReleaseChannel::tagListFinished()
{ {
QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); auto *reply = static_cast<QNetworkReply *>(sender());
bool ok; QJsonParseError parseError{};
QString tmp = QString(reply->readAll()); QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
reply->deleteLater(); reply->deleteLater();
if (parseError.error != QJsonParseError::NoError) {
QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap(); qWarning() << "No reply received from the tag update server.";
if (!ok) {
qWarning() << "No reply received from the tag update server:" << tmp;
emit error(tr("No reply received from the tag update server.")); emit error(tr("No reply received from the tag update server."));
return; return;
} }
QVariantMap resultMap = jsonResponse.toVariant().toMap();
if (!(resultMap.contains("object") && resultMap["object"].toMap().contains("sha"))) { 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.")); emit error(tr("Invalid reply received from the tag update server."));
return; return;
} }
lastRelease->setCommitHash(resultMap["object"].toMap()["sha"].toString()); 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 shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
QString myHash = QString(VERSION_COMMIT); QString myHash = QString(VERSION_COMMIT);
@ -190,7 +187,6 @@ void StableReleaseChannel::tagListFinished()
void StableReleaseChannel::fileListFinished() void StableReleaseChannel::fileListFinished()
{ {
// Only implemented to satisfy interface // Only implemented to satisfy interface
return;
} }
QString BetaReleaseChannel::getManualDownloadUrl() const QString BetaReleaseChannel::getManualDownloadUrl() const
@ -210,7 +206,7 @@ QString BetaReleaseChannel::getReleaseChannelUrl() const
void BetaReleaseChannel::releaseListFinished() void BetaReleaseChannel::releaseListFinished()
{ {
QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); auto *reply = static_cast<QNetworkReply *>(sender());
QByteArray jsonData = reply->readAll(); QByteArray jsonData = reply->readAll();
reply->deleteLater(); reply->deleteLater();
@ -224,7 +220,7 @@ void BetaReleaseChannel::releaseListFinished()
*/ */
QVariantMap resultMap = array.at(0).toObject().toVariantMap(); 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); qWarning() << "No reply received from the release update server:" << QString(jsonData);
emit error(tr("No reply received from the release update server.")); emit error(tr("No reply received from the release update server."));
return; return;
@ -262,18 +258,17 @@ void BetaReleaseChannel::releaseListFinished()
void BetaReleaseChannel::fileListFinished() void BetaReleaseChannel::fileListFinished()
{ {
QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); auto *reply = static_cast<QNetworkReply *>(sender());
QByteArray jsonData = reply->readAll(); QJsonParseError parseError{};
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
reply->deleteLater(); reply->deleteLater();
bool ok; if (parseError.error != QJsonParseError::NoError) {
qWarning() << "No reply received from the file update server.";
QVariantList resultList = QtJson::Json::parse(jsonData, ok).toList();
if (!ok) {
qWarning() << "No reply received from the file update server:" << QString(jsonData);
emit error(tr("No reply received from the file update server.")); emit error(tr("No reply received from the file update server."));
return; return;
} }
QVariantList resultList = jsonResponse.toVariant().toList();
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN); QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
QString myHash = QString(VERSION_COMMIT); QString myHash = QString(VERSION_COMMIT);
qDebug() << "Current hash=" << myHash << "update hash=" << shortHash; qDebug() << "Current hash=" << myHash << "update hash=" << shortHash;

View file

@ -5,6 +5,7 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QVariantMap> #include <QVariantMap>
#include <utility>
class QNetworkReply; class QNetworkReply;
class QNetworkAccessManager; class QNetworkAccessManager;
@ -15,8 +16,8 @@ class Release
friend class BetaReleaseChannel; friend class BetaReleaseChannel;
public: public:
Release(){}; Release() = default;
~Release(){}; ~Release() = default;
private: private:
QString name, descriptionUrl, downloadUrl, commitHash; QString name, descriptionUrl, downloadUrl, commitHash;
@ -26,20 +27,20 @@ private:
protected: protected:
void setName(QString _name) void setName(QString _name)
{ {
name = _name; name = std::move(_name);
} }
void setDescriptionUrl(QString _descriptionUrl) void setDescriptionUrl(QString _descriptionUrl)
{ {
descriptionUrl = _descriptionUrl; descriptionUrl = std::move(_descriptionUrl);
} }
void setDownloadUrl(QString _downloadUrl) void setDownloadUrl(QString _downloadUrl)
{ {
downloadUrl = _downloadUrl; downloadUrl = std::move(_downloadUrl);
compatibleVersionFound = true; compatibleVersionFound = true;
} }
void setCommitHash(QString _commitHash) void setCommitHash(QString _commitHash)
{ {
commitHash = _commitHash; commitHash = std::move(_commitHash);
} }
void setPublishDate(QDate _publishDate) void setPublishDate(QDate _publishDate)
{ {
@ -78,7 +79,7 @@ class ReleaseChannel : public QObject
Q_OBJECT Q_OBJECT
public: public:
ReleaseChannel(); ReleaseChannel();
~ReleaseChannel(); ~ReleaseChannel() override;
protected: protected:
// shared by all instances // shared by all instances
@ -116,33 +117,41 @@ class StableReleaseChannel : public ReleaseChannel
{ {
Q_OBJECT Q_OBJECT
public: public:
StableReleaseChannel(){}; StableReleaseChannel() = default;
~StableReleaseChannel(){}; ~StableReleaseChannel() override = default;
virtual QString getManualDownloadUrl() const;
virtual QString getName() const; QString getManualDownloadUrl() const override;
QString getName() const override;
protected: protected:
virtual QString getReleaseChannelUrl() const; QString getReleaseChannelUrl() const override;
protected slots: protected slots:
virtual void releaseListFinished();
void releaseListFinished() override;
void tagListFinished(); void tagListFinished();
virtual void fileListFinished();
void fileListFinished() override;
}; };
class BetaReleaseChannel : public ReleaseChannel class BetaReleaseChannel : public ReleaseChannel
{ {
Q_OBJECT Q_OBJECT
public: public:
BetaReleaseChannel(){}; BetaReleaseChannel() = default;
~BetaReleaseChannel(){}; ~BetaReleaseChannel() override = default;
virtual QString getManualDownloadUrl() const;
virtual QString getName() const; QString getManualDownloadUrl() const override;
QString getName() const override;
protected: protected:
virtual QString getReleaseChannelUrl() const; QString getReleaseChannelUrl() const override;
protected slots: protected slots:
virtual void releaseListFinished();
virtual void fileListFinished(); void releaseListFinished() override;
void fileListFinished() override;
}; };
#endif #endif

View file

@ -18,12 +18,13 @@
#include <QList> #include <QList>
#include <QThread> #include <QThread>
#include <QTimer> #include <QTimer>
#include <QWebSocket>
static const unsigned int protocolVersion = 14; static const unsigned int protocolVersion = 14;
RemoteClient::RemoteClient(QObject *parent) RemoteClient::RemoteClient(QObject *parent)
: AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false), : AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false),
messageLength(0) usingWebSocket(false), messageLength(0)
{ {
clearNewClientFeatures(); clearNewClientFeatures();
@ -38,6 +39,13 @@ RemoteClient::RemoteClient(QObject *parent)
connect(socket, SIGNAL(readyRead()), this, SLOT(readData())); connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(slotSocketError(QAbstractSocket::SocketError))); 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, connect(this, SIGNAL(serverIdentificationEventReceived(const Event_ServerIdentification &)), this,
SLOT(processServerIdentificationEvent(const Event_ServerIdentification &))); SLOT(processServerIdentificationEvent(const Event_ServerIdentification &)));
connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this, connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this,
@ -69,15 +77,25 @@ void RemoteClient::slotSocketError(QAbstractSocket::SocketError /*error*/)
emit socketError(errorString); emit socketError(errorString);
} }
void RemoteClient::slotWebSocketError(QAbstractSocket::SocketError /*error*/)
{
QString errorString = websocket->errorString();
doDisconnectFromServer();
emit socketError(errorString);
}
void RemoteClient::slotConnected() void RemoteClient::slotConnected()
{ {
timeRunning = lastDataReceived = 0; timeRunning = lastDataReceived = 0;
timer->start(); timer->start();
// dirty hack to be compatible with v14 server if (!usingWebSocket) {
sendCommandContainer(CommandContainer()); // dirty hack to be compatible with v14 server
getNewCmdId(); sendCommandContainer(CommandContainer());
// end of hack getNewCmdId();
// end of hack
}
} }
void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event) void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event)
@ -217,7 +235,7 @@ void RemoteClient::loginResponse(const Response &response)
missingFeatures << QString::fromStdString(resp.missing_features(i)); missingFeatures << QString::fromStdString(resp.missing_features(i));
} }
emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()),
resp.denied_end_time(), missingFeatures); static_cast<quint32>(resp.denied_end_time()), missingFeatures);
setStatus(StatusDisconnecting); setStatus(StatusDisconnecting);
} }
} }
@ -236,7 +254,7 @@ void RemoteClient::registerResponse(const Response &response)
break; break;
default: default:
emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()),
resp.denied_end_time()); static_cast<quint32>(resp.denied_end_time()));
setStatus(StatusDisconnecting); setStatus(StatusDisconnecting);
doDisconnectFromServer(); doDisconnectFromServer();
break; break;
@ -301,21 +319,51 @@ void RemoteClient::readData()
} while (!inputBuffer.isEmpty()); } 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) void RemoteClient::sendCommandContainer(const CommandContainer &cont)
{ {
QByteArray buf;
unsigned int size = cont.ByteSize(); auto size = static_cast<unsigned int>(cont.ByteSize());
#ifdef QT_DEBUG #ifdef QT_DEBUG
qDebug() << "OUT" << size << QString::fromStdString(cont.ShortDebugString()); qDebug() << "OUT" << size << QString::fromStdString(cont.ShortDebugString());
#endif #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<quint16>(port));
}
} }
void RemoteClient::doConnectToServer(const QString &hostname, void RemoteClient::doConnectToServer(const QString &hostname,
@ -330,7 +378,7 @@ void RemoteClient::doConnectToServer(const QString &hostname,
lastHostname = hostname; lastHostname = hostname;
lastPort = port; lastPort = port;
socket->connectToHost(hostname, port); connectToHost(hostname, port);
setStatus(StatusConnecting); setStatus(StatusConnecting);
} }
@ -354,7 +402,7 @@ void RemoteClient::doRegisterToServer(const QString &hostname,
lastHostname = hostname; lastHostname = hostname;
lastPort = port; lastPort = port;
socket->connectToHost(hostname, port); connectToHost(hostname, port);
setStatus(StatusRegistering); setStatus(StatusRegistering);
} }
@ -364,7 +412,7 @@ void RemoteClient::doActivateToServer(const QString &_token)
token = _token; token = _token;
socket->connectToHost(lastHostname, lastPort); connectToHost(lastHostname, static_cast<unsigned int>(lastPort));
setStatus(StatusActivating); setStatus(StatusActivating);
} }
@ -377,17 +425,19 @@ void RemoteClient::doDisconnectFromServer()
messageLength = 0; messageLength = 0;
QList<PendingCommand *> pc = pendingCommands.values(); QList<PendingCommand *> pc = pendingCommands.values();
for (int i = 0; i < pc.size(); i++) { for (const auto &i : pc) {
Response response; Response response;
response.set_response_code(Response::RespNotConnected); response.set_response_code(Response::RespNotConnected);
response.set_cmd_id(pc[i]->getCommandContainer().cmd_id()); response.set_cmd_id(i->getCommandContainer().cmd_id());
pc[i]->processResponse(response); i->processResponse(response);
delete pc[i]; delete i;
} }
pendingCommands.clear(); pendingCommands.clear();
setStatus(StatusDisconnected); setStatus(StatusDisconnected);
if (websocket->isValid())
websocket->close();
socket->close(); socket->close();
} }
@ -508,7 +558,7 @@ void RemoteClient::doRequestForgotPasswordToServer(const QString &hostname, unsi
lastHostname = hostname; lastHostname = hostname;
lastPort = port; lastPort = port;
socket->connectToHost(lastHostname, lastPort); connectToHost(lastHostname, static_cast<unsigned int>(lastPort));
setStatus(StatusRequestingForgotPassword); setStatus(StatusRequestingForgotPassword);
} }
@ -540,7 +590,7 @@ void RemoteClient::doSubmitForgotPasswordResetToServer(const QString &hostname,
token = _token; token = _token;
password = _newpassword; password = _newpassword;
socket->connectToHost(lastHostname, lastPort); connectToHost(lastHostname, static_cast<unsigned int>(lastPort));
setStatus(StatusSubmitForgotPasswordReset); setStatus(StatusSubmitForgotPasswordReset);
} }
@ -574,7 +624,7 @@ void RemoteClient::doSubmitForgotPasswordChallengeToServer(const QString &hostna
lastPort = port; lastPort = port;
email = _email; email = _email;
socket->connectToHost(lastHostname, lastPort); connectToHost(lastHostname, static_cast<unsigned int>(lastPort));
setStatus(StatusSubmitForgotPasswordChallenge); setStatus(StatusSubmitForgotPasswordChallenge);
} }

View file

@ -3,6 +3,7 @@
#include "abstractclient.h" #include "abstractclient.h"
#include <QTcpSocket> #include <QTcpSocket>
#include <QWebSocket>
class QTimer; class QTimer;
@ -17,7 +18,6 @@ signals:
void activateError(); void activateError();
void socketError(const QString &errorString); void socketError(const QString &errorString);
void protocolVersionMismatch(int clientVersion, int serverVersion); void protocolVersionMismatch(int clientVersion, int serverVersion);
void protocolError();
void void
sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
void sigRegisterToServer(const QString &hostname, void sigRegisterToServer(const QString &hostname,
@ -25,7 +25,7 @@ signals:
const QString &_userName, const QString &_userName,
const QString &_password, const QString &_password,
const QString &_email, const QString &_email,
const int _gender, int _gender,
const QString &_country, const QString &_country,
const QString &_realname); const QString &_realname);
void sigActivateToServer(const QString &_token); void sigActivateToServer(const QString &_token);
@ -48,7 +48,9 @@ signals:
private slots: private slots:
void slotConnected(); void slotConnected();
void readData(); void readData();
void websocketMessageReceived(const QByteArray &message);
void slotSocketError(QAbstractSocket::SocketError error); void slotSocketError(QAbstractSocket::SocketError error);
void slotWebSocketError(QAbstractSocket::SocketError error);
void ping(); void ping();
void processServerIdentificationEvent(const Event_ServerIdentification &event); void processServerIdentificationEvent(const Event_ServerIdentification &event);
void processConnectionClosedEvent(const Event_ConnectionClosed &event); void processConnectionClosedEvent(const Event_ConnectionClosed &event);
@ -62,7 +64,7 @@ private slots:
const QString &_userName, const QString &_userName,
const QString &_password, const QString &_password,
const QString &_email, const QString &_email,
const int _gender, int _gender,
const QString &_country, const QString &_country,
const QString &_realname); const QString &_realname);
void doLogin(); void doLogin();
@ -85,28 +87,35 @@ private slots:
private: private:
static const int maxTimeout = 10; static const int maxTimeout = 10;
int timeRunning, lastDataReceived; int timeRunning, lastDataReceived;
QByteArray inputBuffer; QByteArray inputBuffer;
bool messageInProgress; bool messageInProgress;
bool handshakeStarted; bool handshakeStarted;
bool newMissingFeatureFound(QString _serversMissingFeatures); bool usingWebSocket;
void clearNewClientFeatures();
int messageLength; int messageLength;
QTimer *timer; QTimer *timer;
QTcpSocket *socket; QTcpSocket *socket;
QWebSocket *websocket;
QString lastHostname; QString lastHostname;
int lastPort; 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: protected slots:
void sendCommandContainer(const CommandContainer &cont); void sendCommandContainer(const CommandContainer &cont) override;
public: public:
RemoteClient(QObject *parent = 0); explicit RemoteClient(QObject *parent = nullptr);
~RemoteClient(); ~RemoteClient() override;
QString peerName() const QString peerName() const
{ {
return socket->peerName(); if (usingWebSocket) {
return websocket->peerName();
} else {
return socket->peerName();
}
} }
void void
connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
@ -115,7 +124,7 @@ public:
const QString &_userName, const QString &_userName,
const QString &_password, const QString &_password,
const QString &_email, const QString &_email,
const int _gender, int _gender,
const QString &_country, const QString &_country,
const QString &_realname); const QString &_realname);
void activateToServer(const QString &_token); void activateToServer(const QString &_token);

View file

@ -1,20 +1,12 @@
#include "sequenceedit.h" #include "sequenceedit.h"
#include "../settingscache.h" #include "../settingscache.h"
#include <QEvent>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QKeyEvent> #include <QKeyEvent>
#include <QLineEdit>
#include <QPushButton>
#include <QToolTip> #include <QToolTip>
#include <utility> #include <utility>
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); lineEdit = new QLineEdit(this);
clearButton = new QPushButton("", this); clearButton = new QPushButton("", this);
defaultButton = 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())); connect(defaultButton, SIGNAL(clicked()), this, SLOT(restoreDefault()));
lineEdit->installEventFilter(this); lineEdit->installEventFilter(this);
lineEdit->setText(settingsCache->shortcuts().getShortcutString(shorcutName)); lineEdit->setText(settingsCache->shortcuts().getShortcutString(shortcutName));
} }
QString SequenceEdit::getSecuence() QString SequenceEdit::getSequence()
{ {
return lineEdit->text(); return lineEdit->text();
} }
void SequenceEdit::removeLastShortcut() void SequenceEdit::removeLastShortcut()
{ {
QString secuences = lineEdit->text(); QString sequences = lineEdit->text();
if (!secuences.isEmpty()) { if (!sequences.isEmpty()) {
if (secuences.lastIndexOf(";") > 0) { if (sequences.lastIndexOf(";") > 0) {
QString valid = secuences.left(secuences.lastIndexOf(";")); QString valid = sequences.left(sequences.lastIndexOf(";"));
lineEdit->setText(valid); lineEdit->setText(valid);
} else { } else {
lineEdit->clear(); lineEdit->clear();
@ -67,18 +59,18 @@ void SequenceEdit::removeLastShortcut()
void SequenceEdit::restoreDefault() void SequenceEdit::restoreDefault()
{ {
lineEdit->setText(settingsCache->shortcuts().getDefaultShortcutString(shorcutName)); lineEdit->setText(settingsCache->shortcuts().getDefaultShortcutString(shortcutName));
updateSettings(); updateSettings();
} }
void SequenceEdit::refreshShortcut() void SequenceEdit::refreshShortcut()
{ {
lineEdit->setText(settingsCache->shortcuts().getShortcutString(shorcutName)); lineEdit->setText(settingsCache->shortcuts().getShortcutString(shortcutName));
} }
void SequenceEdit::clear() void SequenceEdit::clear()
{ {
this->lineEdit->setText(""); lineEdit->setText("");
} }
bool SequenceEdit::eventFilter(QObject *, QEvent *event) bool SequenceEdit::eventFilter(QObject *, QEvent *event)
@ -142,8 +134,8 @@ void SequenceEdit::finishShortcut()
QKeySequence sequence(keys); QKeySequence sequence(keys);
if (!sequence.isEmpty() && valid) { if (!sequence.isEmpty() && valid) {
QString sequenceString = sequence.toString(); QString sequenceString = sequence.toString();
if (settingsCache->shortcuts().isKeyAllowed(shorcutName, sequenceString)) { if (settingsCache->shortcuts().isKeyAllowed(shortcutName, sequenceString)) {
if (settingsCache->shortcuts().isValid(shorcutName, sequenceString)) { if (settingsCache->shortcuts().isValid(shortcutName, sequenceString)) {
if (!lineEdit->text().isEmpty()) { if (!lineEdit->text().isEmpty()) {
if (lineEdit->text().contains(sequenceString)) { if (lineEdit->text().contains(sequenceString)) {
return; return;
@ -167,5 +159,5 @@ void SequenceEdit::finishShortcut()
void SequenceEdit::updateSettings() void SequenceEdit::updateSettings()
{ {
settingsCache->shortcuts().setShortcuts(shorcutName, lineEdit->text()); settingsCache->shortcuts().setShortcuts(shortcutName, lineEdit->text());
} }

View file

@ -1,19 +1,18 @@
#ifndef SECUENCEEDIT_H #ifndef SEQUENCEEDIT_H
#define SECUENCEEDIT_H #define SEQUENCEEDIT_H
#include <QEvent>
#include <QKeySequence> #include <QKeySequence>
#include <QLineEdit>
#include <QPushButton>
#include <QWidget> #include <QWidget>
class QLineEdit;
class QPushButton;
class QEvent;
class SequenceEdit : public QWidget class SequenceEdit : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
SequenceEdit(QString _shorcutName, QWidget *parent = nullptr); SequenceEdit(const QString &_shortcutName, QWidget *parent = nullptr);
QString getSecuence(); QString getSequence();
void refreshShortcut(); void refreshShortcut();
void clear(); void clear();
@ -25,13 +24,13 @@ protected:
bool eventFilter(QObject *, QEvent *event); bool eventFilter(QObject *, QEvent *event);
private: private:
QString shorcutName; QString shortcutName;
QLineEdit *lineEdit; QLineEdit *lineEdit;
QPushButton *clearButton; QPushButton *clearButton;
QPushButton *defaultButton; QPushButton *defaultButton;
int keys; int keys = 0;
int currentKey; int currentKey = 0;
bool valid; bool valid = false;
void processKey(QKeyEvent *e); void processKey(QKeyEvent *e);
int translateModifiers(Qt::KeyboardModifiers state, const QString &text); int translateModifiers(Qt::KeyboardModifiers state, const QString &text);
@ -39,4 +38,4 @@ private:
void updateSettings(); void updateSettings();
}; };
#endif // SECUENCEEDIT_H #endif // SEQUENCEEDIT_H

File diff suppressed because it is too large Load diff

View file

@ -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<QStringList>();
}
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();
}

View file

@ -0,0 +1,28 @@
#ifndef COCKATRICE_DOWNLOADSETTINGS_H
#define COCKATRICE_DOWNLOADSETTINGS_H
#include "settingsmanager.h"
#include <QObject>
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

View file

@ -164,6 +164,7 @@ SettingsCache::SettingsCache()
messageSettings = new MessageSettings(settingsPath, this); messageSettings = new MessageSettings(settingsPath, this);
gameFiltersSettings = new GameFiltersSettings(settingsPath, this); gameFiltersSettings = new GameFiltersSettings(settingsPath, this);
layoutsSettings = new LayoutsSettings(settingsPath, this); layoutsSettings = new LayoutsSettings(settingsPath, this);
downloadSettings = new DownloadSettings(settingsPath, this);
if (!QFile(settingsPath + "global.ini").exists()) if (!QFile(settingsPath + "global.ini").exists())
translateLegacySettings(); translateLegacySettings();
@ -176,6 +177,7 @@ SettingsCache::SettingsCache()
mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool(); mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool();
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt(); updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt();
lang = settings->value("personal/lang").toString(); lang = settings->value("personal/lang").toString();
@ -220,9 +222,6 @@ SettingsCache::SettingsCache()
picDownload = settings->value("personal/picturedownload", true).toBool(); 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(); mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray();
tokenDialogGeometry = settings->value("interface/token_dialog_geometry").toByteArray(); tokenDialogGeometry = settings->value("interface/token_dialog_geometry").toByteArray();
notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool(); notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool();
@ -234,7 +233,7 @@ SettingsCache::SettingsCache()
displayCardNames = settings->value("cards/displaycardnames", true).toBool(); displayCardNames = settings->value("cards/displaycardnames", true).toBool();
horizontalHand = settings->value("hand/horizontal", true).toBool(); horizontalHand = settings->value("hand/horizontal", true).toBool();
invertVerticalCoordinate = settings->value("table/invert_vertical", false).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(); tapAnimation = settings->value("cards/tapanimation", true).toBool();
chatMention = settings->value("chat/mention", true).toBool(); chatMention = settings->value("chat/mention", true).toBool();
chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool(); chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool();
@ -277,6 +276,7 @@ SettingsCache::SettingsCache()
spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool(); spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool();
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool(); rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
clientID = settings->value("personal/clientid", "notset").toString(); clientID = settings->value("personal/clientid", "notset").toString();
clientVersion = settings->value("personal/clientversion", "notset").toString();
knownMissingFeatures = settings->value("interface/knownmissingfeatures", "").toString(); knownMissingFeatures = settings->value("interface/knownmissingfeatures", "").toString();
} }
@ -415,18 +415,6 @@ void SettingsCache::setPicDownload(int _picDownload)
emit picDownloadChanged(); 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) void SettingsCache::setNotificationsEnabled(int _notificationsEnabled)
{ {
notificationsEnabled = static_cast<bool>(_notificationsEnabled); notificationsEnabled = static_cast<bool>(_notificationsEnabled);
@ -603,6 +591,12 @@ void SettingsCache::setClientID(QString _clientID)
settings->setValue("personal/clientid", clientID); settings->setValue("personal/clientid", clientID);
} }
void SettingsCache::setClientVersion(QString _clientVersion)
{
clientVersion = std::move(_clientVersion);
settings->setValue("personal/clientversion", clientVersion);
}
QStringList SettingsCache::getCountries() const QStringList SettingsCache::getCountries() const
{ {
static QStringList countries = QStringList() << "ad" static QStringList countries = QStringList() << "ad"
@ -924,6 +918,12 @@ void SettingsCache::setNotifyAboutUpdate(int _notifyaboutupdate)
settings->setValue("personal/updatenotification", notifyAboutUpdates); settings->setValue("personal/updatenotification", notifyAboutUpdates);
} }
void SettingsCache::setNotifyAboutNewVersion(int _notifyaboutnewversion)
{
notifyAboutNewVersion = static_cast<bool>(_notifyaboutnewversion);
settings->setValue("personal/newversionnotification", notifyAboutNewVersion);
}
void SettingsCache::setDownloadSpoilerStatus(bool _spoilerStatus) void SettingsCache::setDownloadSpoilerStatus(bool _spoilerStatus)
{ {
mbDownloadSpoilers = _spoilerStatus; mbDownloadSpoilers = _spoilerStatus;
@ -941,4 +941,4 @@ void SettingsCache::setMaxFontSize(int _max)
{ {
maxFontSize = _max; maxFontSize = _max;
settings->setValue("game/maxfontsize", maxFontSize); settings->setValue("game/maxfontsize", maxFontSize);
} }

View file

@ -2,6 +2,7 @@
#define SETTINGSCACHE_H #define SETTINGSCACHE_H
#include "settings/carddatabasesettings.h" #include "settings/carddatabasesettings.h"
#include "settings/downloadsettings.h"
#include "settings/gamefilterssettings.h" #include "settings/gamefilterssettings.h"
#include "settings/layoutssettings.h" #include "settings/layoutssettings.h"
#include "settings/messagesettings.h" #include "settings/messagesettings.h"
@ -13,9 +14,6 @@
class ReleaseChannel; 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 // size should be a multiple of 64
#define PIXMAPCACHE_SIZE_DEFAULT 2047 #define PIXMAPCACHE_SIZE_DEFAULT 2047
#define PIXMAPCACHE_SIZE_MIN 64 #define PIXMAPCACHE_SIZE_MIN 64
@ -60,6 +58,7 @@ private:
MessageSettings *messageSettings; MessageSettings *messageSettings;
GameFiltersSettings *gameFiltersSettings; GameFiltersSettings *gameFiltersSettings;
LayoutsSettings *layoutsSettings; LayoutsSettings *layoutsSettings;
DownloadSettings *downloadSettings;
QByteArray mainWindowGeometry; QByteArray mainWindowGeometry;
QByteArray tokenDialogGeometry; QByteArray tokenDialogGeometry;
@ -67,6 +66,7 @@ private:
QString deckPath, replaysPath, picsPath, customPicsPath, cardDatabasePath, customCardDatabasePath, QString deckPath, replaysPath, picsPath, customPicsPath, cardDatabasePath, customCardDatabasePath,
spoilerDatabasePath, tokenDatabasePath, themeName; spoilerDatabasePath, tokenDatabasePath, themeName;
bool notifyAboutUpdates; bool notifyAboutUpdates;
bool notifyAboutNewVersion;
bool showTipsOnStartup; bool showTipsOnStartup;
QList<int> seenTips; QList<int> seenTips;
bool mbDownloadSpoilers; bool mbDownloadSpoilers;
@ -98,6 +98,7 @@ private:
QString picUrl; QString picUrl;
QString picUrlFallback; QString picUrlFallback;
QString clientID; QString clientID;
QString clientVersion;
QString knownMissingFeatures; QString knownMissingFeatures;
int pixmapCacheSize; int pixmapCacheSize;
bool scaleCards; bool scaleCards;
@ -201,6 +202,10 @@ public:
{ {
return notifyAboutUpdates; return notifyAboutUpdates;
} }
bool getNotifyAboutNewVersion() const
{
return notifyAboutNewVersion;
}
bool getShowTipsOnStartup() const bool getShowTipsOnStartup() const
{ {
return showTipsOnStartup; return showTipsOnStartup;
@ -302,14 +307,6 @@ public:
{ {
return ignoreUnregisteredUserMessages; return ignoreUnregisteredUserMessages;
} }
QString getPicUrl() const
{
return picUrl;
}
QString getPicUrlFallback() const
{
return picUrlFallback;
}
int getPixmapCacheSize() const int getPixmapCacheSize() const
{ {
return pixmapCacheSize; return pixmapCacheSize;
@ -396,11 +393,16 @@ public:
return maxFontSize; return maxFontSize;
} }
void setClientID(QString clientID); void setClientID(QString clientID);
void setClientVersion(QString clientVersion);
void setKnownMissingFeatures(QString _knownMissingFeatures); void setKnownMissingFeatures(QString _knownMissingFeatures);
QString getClientID() QString getClientID()
{ {
return clientID; return clientID;
} }
QString getClientVersion()
{
return clientVersion;
}
QString getKnownMissingFeatures() QString getKnownMissingFeatures()
{ {
return knownMissingFeatures; return knownMissingFeatures;
@ -429,6 +431,10 @@ public:
{ {
return *layoutsSettings; return *layoutsSettings;
} }
DownloadSettings &downloads() const
{
return *downloadSettings;
}
bool getIsPortableBuild() const bool getIsPortableBuild() const
{ {
return isPortableBuild; return isPortableBuild;
@ -477,8 +483,6 @@ public slots:
void setSoundThemeName(const QString &_soundThemeName); void setSoundThemeName(const QString &_soundThemeName);
void setIgnoreUnregisteredUsers(int _ignoreUnregisteredUsers); void setIgnoreUnregisteredUsers(int _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(int _ignoreUnregisteredUserMessages); void setIgnoreUnregisteredUserMessages(int _ignoreUnregisteredUserMessages);
void setPicUrl(const QString &_picUrl);
void setPicUrlFallback(const QString &_picUrlFallback);
void setPixmapCacheSize(const int _pixmapCacheSize); void setPixmapCacheSize(const int _pixmapCacheSize);
void setCardScaling(const int _scaleCards); void setCardScaling(const int _scaleCards);
void setShowMessagePopups(const int _showMessagePopups); void setShowMessagePopups(const int _showMessagePopups);
@ -499,6 +503,7 @@ public slots:
void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything); void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything);
void setRememberGameSettings(const bool _rememberGameSettings); void setRememberGameSettings(const bool _rememberGameSettings);
void setNotifyAboutUpdate(int _notifyaboutupdate); void setNotifyAboutUpdate(int _notifyaboutupdate);
void setNotifyAboutNewVersion(int _notifyaboutnewversion);
void setUpdateReleaseChannel(int _updateReleaseChannel); void setUpdateReleaseChannel(int _updateReleaseChannel);
void setMaxFontSize(int _max); void setMaxFontSize(int _max);
}; };

View file

@ -4,19 +4,18 @@
#include <QStringList> #include <QStringList>
#include <utility> #include <utility>
ShortcutsSettings::ShortcutsSettings(QString settingsPath, QObject *parent) : QObject(parent) ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *parent) : QObject(parent)
{ {
this->settingsFilePath = std::move(settingsPath); shortCuts = defaultShortCuts;
this->settingsFilePath.append("shortcuts.ini"); settingsFilePath = settingsPath;
fillDefaultShorcuts(); settingsFilePath.append("shortcuts.ini");
shortCuts = QMap<QString, QList<QKeySequence>>(defaultShortCuts);
bool exists = QFile(settingsFilePath).exists(); bool exists = QFile(settingsFilePath).exists();
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat); QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
if (exists) { if (exists) {
shortCutsFile.beginGroup("Custom"); shortCutsFile.beginGroup(custom);
const QStringList customKeys = shortCutsFile.allKeys(); const QStringList customKeys = shortCutsFile.allKeys();
QMap<QString, QString> invalidItems; QMap<QString, QString> invalidItems;
@ -55,114 +54,123 @@ ShortcutsSettings::ShortcutsSettings(QString settingsPath, QObject *parent) : QO
} }
} }
QList<QKeySequence> ShortcutsSettings::getShortcut(QString name) QList<QKeySequence> ShortcutsSettings::getDefaultShortcut(const QString &name) const
{
return defaultShortCuts.value(name, QList<QKeySequence>());
}
QList<QKeySequence> ShortcutsSettings::getShortcut(const QString &name) const
{ {
if (shortCuts.contains(name)) { if (shortCuts.contains(name)) {
return shortCuts.value(name); return shortCuts.value(name);
} }
return defaultShortCuts.value(name, QList<QKeySequence>()); 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<QKeySequence> Sequence) const QString ShortcutsSettings::stringifySequence(const QList<QKeySequence> &Sequence) const
{ {
QString stringSequence; QStringList stringSequence;
for (int i = 0; i < Sequence.size(); ++i) { for (const auto &i : Sequence) {
stringSequence.append(Sequence.at(i).toString(QKeySequence::PortableText)); stringSequence.append(i.toString(QKeySequence::PortableText));
if (i < Sequence.size() - 1) {
stringSequence.append(";");
}
} }
return stringSequence; return stringSequence.join(sep);
} }
QList<QKeySequence> ShortcutsSettings::parseSequenceString(QString stringSequence) QList<QKeySequence> ShortcutsSettings::parseSequenceString(const QString &stringSequence) const
{ {
QStringList Sequences = stringSequence.split(";");
QList<QKeySequence> SequenceList; QList<QKeySequence> SequenceList;
for (QStringList::const_iterator ss = Sequences.constBegin(); ss != Sequences.constEnd(); ++ss) { for (const QString &shortcut : stringSequence.split(sep)) {
SequenceList.append(QKeySequence(*ss, QKeySequence::PortableText)); SequenceList.append(QKeySequence(shortcut, QKeySequence::PortableText));
} }
return SequenceList; return SequenceList;
} }
void ShortcutsSettings::setShortcuts(QString name, QList<QKeySequence> Sequence) void ShortcutsSettings::setShortcuts(const QString &name, const QList<QKeySequence> &Sequence)
{ {
shortCuts[name] = Sequence; shortCuts[name] = Sequence;
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat); QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
shortCutsFile.beginGroup("Custom"); shortCutsFile.beginGroup(custom);
QString stringSequence = stringifySequence(Sequence); shortCutsFile.setValue(name, stringifySequence(Sequence));
shortCutsFile.setValue(name, stringSequence);
shortCutsFile.endGroup(); 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<QKeySequence>() << Sequence); setShortcuts(name, QList<QKeySequence>{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 the shortcut is not to be used in deck-editor then it doesn't matter
if (name.startsWith("Player")) { if (name.startsWith("Player")) {
return true; return true;
} }
QString checkSequence = Sequences.split(";").last(); QString checkSequence = Sequences.split(sep).last();
QStringList forbiddenKeys = (QStringList() << "Del" QStringList forbiddenKeys{"Del", "Backspace", "Down", "Up", "Left", "Right",
<< "Backspace" "Return", "Enter", "Menu", "Ctrl+Alt+-", "Ctrl+Alt+=", "Ctrl+Alt+[",
<< "Down" "Ctrl+Alt+]", "Tab", "Space", "Shift+S", "Shift+Left", "Shift+Right"};
<< "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); 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("/")); QString checkKey = name.left(name.indexOf("/"));
QList<QString> allKeys = shortCuts.keys(); QList<QString> allKeys = shortCuts.keys();
for (const auto &key : allKeys) { for (const auto &key : allKeys) {
if (key.startsWith(checkKey) || key.startsWith("MainWindow") || checkKey.startsWith("MainWindow")) { if (key.startsWith(checkKey) || key.startsWith("MainWindow") || checkKey.startsWith("MainWindow")) {
QString storedSequence = stringifySequence(shortCuts.value(key)); QString storedSequence = stringifySequence(shortCuts.value(key));
QStringList stringSequences = storedSequence.split(";"); QStringList stringSequences = storedSequence.split(sep);
if (stringSequences.contains(checkSequence)) { if (stringSequences.contains(checkSequence)) {
return false; return false;
} }
@ -170,161 +178,3 @@ bool ShortcutsSettings::isValid(QString name, QString Sequences)
} }
return true; 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");
}

View file

@ -1,8 +1,8 @@
#ifndef SHORTCUTSSETTINGS_H #ifndef SHORTCUTSSETTINGS_H
#define SHORTCUTSSETTINGS_H #define SHORTCUTSSETTINGS_H
#include <QHash>
#include <QKeySequence> #include <QKeySequence>
#include <QMap>
#include <QObject> #include <QObject>
#include <QSettings> #include <QSettings>
@ -10,37 +10,186 @@ class ShortcutsSettings : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
ShortcutsSettings(QString settingsFilePath, QObject *parent = nullptr); ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr);
QList<QKeySequence> getShortcut(QString name); QList<QKeySequence> getDefaultShortcut(const QString &name) const;
QKeySequence getSingleShortcut(QString name); QList<QKeySequence> 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); void setShortcuts(const QString &name, const QList<QKeySequence> &Sequence);
QString getShortcutString(QString name); void setShortcuts(const QString &name, const QKeySequence &Sequence);
void setShortcuts(const QString &name, const QString &Sequences);
void setShortcuts(QString name, QList<QKeySequence> Sequence); bool isKeyAllowed(const QString &name, const QString &Sequences) const;
void setShortcuts(QString name, QKeySequence Sequence); bool isValid(const QString &name, const QString &Sequences) const;
void setShortcuts(QString name, QString Sequences);
bool isKeyAllowed(QString name, QString Sequences);
bool isValid(QString name, QString Sequences);
void resetAllShortcuts(); void resetAllShortcuts();
void clearAllShortcuts(); void clearAllShortcuts();
signals: signals:
void shortCutchanged(); void shortCutChanged();
void allShortCutsReset(); void allShortCutsReset();
void allShortCutsClear(); void allShortCutsClear();
private: private:
const QChar sep = ';';
const QString custom = "Custom";
QString settingsFilePath; QString settingsFilePath;
QMap<QString, QList<QKeySequence>> shortCuts; QHash<QString, QList<QKeySequence>> shortCuts;
QMap<QString, QList<QKeySequence>> defaultShortCuts;
void fillDefaultShorcuts();
QString stringifySequence(QList<QKeySequence> Sequence) const; QString stringifySequence(const QList<QKeySequence> &Sequence) const;
QList<QKeySequence> parseSequenceString(QString stringSequence); QList<QKeySequence> parseSequenceString(const QString &stringSequence) const;
const QHash<QString, QList<QKeySequence>> 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 #endif // SHORTCUTSSETTINGS_H

View file

@ -590,7 +590,7 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent)
this->installEventFilter(this); this->installEventFilter(this);
retranslateUi(); retranslateUi();
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts(); refreshShortcuts();
QTimer::singleShot(0, this, SLOT(loadLayout())); QTimer::singleShot(0, this, SLOT(loadLayout()));

View file

@ -95,7 +95,7 @@ void ToggleButton::setState(bool _state)
} }
DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
: QWidget(0), parentGame(parent), playerId(_playerId) : QWidget(nullptr), parentGame(parent), playerId(_playerId)
{ {
loadLocalButton = new QPushButton; loadLocalButton = new QPushButton;
loadRemoteButton = 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(clicked()), this, SLOT(sideboardLockButtonClicked()));
connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText())); connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText()));
QHBoxLayout *buttonHBox = new QHBoxLayout; auto *buttonHBox = new QHBoxLayout;
buttonHBox->addWidget(loadLocalButton); buttonHBox->addWidget(loadLocalButton);
buttonHBox->addWidget(loadRemoteButton); buttonHBox->addWidget(loadRemoteButton);
buttonHBox->addWidget(readyStartButton); 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(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *)));
connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged())); connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged()));
QVBoxLayout *deckViewLayout = new QVBoxLayout; auto *deckViewLayout = new QVBoxLayout;
deckViewLayout->addLayout(buttonHBox); deckViewLayout->addLayout(buttonHBox);
deckViewLayout->addWidget(deckView); deckViewLayout->addWidget(deckView);
deckViewLayout->setContentsMargins(0, 0, 0, 0); deckViewLayout->setContentsMargins(0, 0, 0, 0);
setLayout(deckViewLayout); setLayout(deckViewLayout);
retranslateUi(); retranslateUi();
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts(); refreshShortcuts();
} }
@ -209,6 +209,9 @@ void TabGame::refreshShortcuts()
if (aNextPhase) { if (aNextPhase) {
aNextPhase->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextPhase")); aNextPhase->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextPhase"));
} }
if (aNextPhaseAction) {
aNextPhaseAction->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextPhaseAction"));
}
if (aNextTurn) { if (aNextTurn) {
aNextTurn->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextTurn")); aNextTurn->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextTurn"));
} }
@ -299,8 +302,8 @@ void DeckViewContainer::sideboardPlanChanged()
{ {
Command_SetSideboardPlan cmd; Command_SetSideboardPlan cmd;
const QList<MoveCard_ToZone> &newPlan = deckView->getSideboardPlan(); const QList<MoveCard_ToZone> &newPlan = deckView->getSideboardPlan();
for (int i = 0; i < newPlan.size(); ++i) for (const auto &i : newPlan)
cmd.add_move_list()->CopyFrom(newPlan.at(i)); cmd.add_move_list()->CopyFrom(i);
parentGame->sendGameCommand(cmd, playerId); parentGame->sendGameCommand(cmd, playerId);
} }
@ -330,7 +333,8 @@ void DeckViewContainer::setDeck(const DeckLoader &deck)
TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
: Tab(_tabSupervisor), secondsElapsed(0), hostId(-1), localPlayerId(-1), : Tab(_tabSupervisor), secondsElapsed(0), hostId(-1), localPlayerId(-1),
isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(true), gameStateKnown(false), resuming(false), 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 // THIS CTOR IS USED ON REPLAY
gameInfo.CopyFrom(replay->game_info()); gameInfo.CopyFrom(replay->game_info());
@ -375,7 +379,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
createReplayMenuItems(); createReplayMenuItems();
createViewMenuItems(); createViewMenuItems();
retranslateUi(); retranslateUi();
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts(); refreshShortcuts();
messageLog->logReplayStarted(gameInfo.game_id()); messageLog->logReplayStarted(gameInfo.game_id());
@ -389,8 +393,8 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
const QMap<int, QString> &_roomGameTypes) const QMap<int, QString> &_roomGameTypes)
: Tab(_tabSupervisor), clients(_clients), gameInfo(event.game_info()), roomGameTypes(_roomGameTypes), : Tab(_tabSupervisor), clients(_clients), gameInfo(event.game_info()), roomGameTypes(_roomGameTypes),
hostId(event.host_id()), localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()), hostId(event.host_id()), localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()),
spectator(event.spectator()), gameStateKnown(false), resuming(event.resuming()), currentPhase(-1), activeCard(0), spectator(event.spectator()), gameStateKnown(false), resuming(event.resuming()), currentPhase(-1),
gameClosed(false), replay(0), replayDock(0) activeCard(nullptr), gameClosed(false), replay(nullptr), replayDock(nullptr)
{ {
// THIS CTOR IS USED ON GAMES // THIS CTOR IS USED ON GAMES
gameInfo.set_started(false); gameInfo.set_started(false);
@ -414,7 +418,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
createMenuItems(); createMenuItems();
createViewMenuItems(); createViewMenuItems();
retranslateUi(); retranslateUi();
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts(); refreshShortcuts();
// append game to rooms game list for others to see // append game to rooms game list for others to see
@ -486,6 +490,9 @@ void TabGame::retranslateUi()
if (aNextPhase) { if (aNextPhase) {
aNextPhase->setText(tr("Next &phase")); aNextPhase->setText(tr("Next &phase"));
} }
if (aNextPhaseAction) {
aNextPhaseAction->setText(tr("Next phase with &action"));
}
if (aNextTurn) { if (aNextTurn) {
aNextTurn->setText(tr("Next &turn")); aNextTurn->setText(tr("Next &turn"));
} }
@ -554,7 +561,7 @@ void TabGame::closeRequest()
void TabGame::replayNextEvent() void TabGame::replayNextEvent()
{ {
processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), 0); processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), nullptr);
} }
void TabGame::replayFinished() void TabGame::replayFinished()
@ -620,11 +627,23 @@ void TabGame::actGameInfo()
void TabGame::actConcede() void TabGame::actConcede()
{ {
if (QMessageBox::question(this, tr("Concede"), tr("Are you sure you want to concede this game?"), Player *player = players.value(localPlayerId, nullptr);
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) if (player == nullptr)
return; 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() void TabGame::actLeaveGame()
@ -659,7 +678,7 @@ void TabGame::actPhaseAction()
{ {
int phase = phaseActions.indexOf(static_cast<QAction *>(sender())); int phase = phaseActions.indexOf(static_cast<QAction *>(sender()));
Command_SetActivePhase cmd; Command_SetActivePhase cmd;
cmd.set_phase(phase); cmd.set_phase(static_cast<google::protobuf::uint32>(phase));
sendGameCommand(cmd); sendGameCommand(cmd);
} }
@ -669,10 +688,29 @@ void TabGame::actNextPhase()
if (++phase >= phasesToolbar->phaseCount()) if (++phase >= phasesToolbar->phaseCount())
phase = 0; phase = 0;
Command_SetActivePhase cmd; Command_SetActivePhase cmd;
cmd.set_phase(phase); cmd.set_phase(static_cast<google::protobuf::uint32>(phase));
sendGameCommand(cmd); 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<google::protobuf::uint32>(phase));
sendGameCommand(cmd);
}
phasesToolbar->triggerPhaseAction(phase);
}
void TabGame::actNextTurn() void TabGame::actNextTurn()
{ {
sendGameCommand(Command_NextTurn()); sendGameCommand(Command_NextTurn());
@ -713,7 +751,7 @@ void TabGame::actCompleterChanged()
Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info)
{ {
bool local = ((clients.size() > 1) || (playerId == localPlayerId)); 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 *))); connect(newPlayer, SIGNAL(openDeckEditor(const DeckLoader *)), this, SIGNAL(openDeckEditor(const DeckLoader *)));
QString newPlayerName = "@" + newPlayer->getName(); QString newPlayerName = "@" + newPlayer->getName();
if (sayEdit && !autocompleteUserList.contains(newPlayerName)) { if (sayEdit && !autocompleteUserList.contains(newPlayerName)) {
@ -729,7 +767,7 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info)
if (clients.size() == 1) if (clients.size() == 1)
newPlayer->setShortcutsActive(); newPlayer->setShortcutsActive();
DeckViewContainer *deckView = new DeckViewContainer(playerId, this); auto *deckView = new DeckViewContainer(playerId, this);
connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *)));
deckViewContainers.insert(playerId, deckView); deckViewContainers.insert(playerId, deckView);
deckViewContainerLayout->addWidget(deckView); deckViewContainerLayout->addWidget(deckView);
@ -750,7 +788,7 @@ void TabGame::processGameEventContainer(const GameEventContainer &cont, Abstract
for (int i = 0; i < eventListSize; ++i) { for (int i = 0; i < eventListSize; ++i) {
const GameEvent &event = cont.event_list(i); const GameEvent &event = cont.event_list(i);
const int playerId = event.player_id(); const int playerId = event.player_id();
const GameEvent::GameEventType eventType = static_cast<GameEvent::GameEventType>(getPbExtension(event)); const auto eventType = static_cast<GameEvent::GameEventType>(getPbExtension(event));
if (spectators.contains(playerId)) { if (spectators.contains(playerId)) {
switch (eventType) { switch (eventType) {
case GameEvent::GAME_SAY: case GameEvent::GAME_SAY:
@ -822,7 +860,7 @@ AbstractClient *TabGame::getClientForPlayer(int playerId) const
return clients.at(playerId); return clients.at(playerId);
} else if (clients.isEmpty()) } else if (clients.isEmpty())
return 0; return nullptr;
else else
return clients.first(); return clients.first();
} }
@ -859,7 +897,7 @@ void TabGame::commandFinished(const Response &response)
PendingCommand *TabGame::prepareGameCommand(const ::google::protobuf::Message &cmd) PendingCommand *TabGame::prepareGameCommand(const ::google::protobuf::Message &cmd)
{ {
CommandContainer cont; CommandContainer cont;
cont.set_game_id(gameInfo.game_id()); cont.set_game_id(static_cast<google::protobuf::uint32>(gameInfo.game_id()));
GameCommand *c = cont.add_game_command(); GameCommand *c = cont.add_game_command();
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
return new PendingCommand(cont); return new PendingCommand(cont);
@ -868,13 +906,11 @@ PendingCommand *TabGame::prepareGameCommand(const ::google::protobuf::Message &c
PendingCommand *TabGame::prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList) PendingCommand *TabGame::prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList)
{ {
CommandContainer cont; CommandContainer cont;
cont.set_game_id(gameInfo.game_id()); cont.set_game_id(static_cast<google::protobuf::uint32>(gameInfo.game_id()));
for (int i = 0; i < cmdList.size(); ++i) { for (auto i : cmdList) {
GameCommand *c = cont.add_game_command(); GameCommand *c = cont.add_game_command();
c->GetReflection() c->GetReflection()->MutableMessage(c, i->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*i);
->MutableMessage(c, cmdList[i]->GetDescriptor()->FindExtensionByName("ext")) delete i;
->CopyFrom(*cmdList[i]);
delete cmdList[i];
} }
return new PendingCommand(cont); return new PendingCommand(cont);
} }
@ -1028,8 +1064,7 @@ void TabGame::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &
const ServerInfo_PlayerProperties &prop = event.player_properties(); const ServerInfo_PlayerProperties &prop = event.player_properties();
playerListWidget->updatePlayerProperties(prop, eventPlayerId); playerListWidget->updatePlayerProperties(prop, eventPlayerId);
const GameEventContext::ContextType contextType = const auto contextType = static_cast<GameEventContext::ContextType>(getPbExtension(context));
static_cast<GameEventContext::ContextType>(getPbExtension(context));
switch (contextType) { switch (contextType) {
case GameEventContext::READY_START: { case GameEventContext::READY_START: {
bool ready = prop.ready_start(); bool ready = prop.ready_start();
@ -1051,6 +1086,16 @@ void TabGame::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &
break; break;
} }
case GameEventContext::UNCONCEDE: {
messageLog->logUnconcede(player);
player->setConceded(false);
QMapIterator<int, Player *> playerIterator(players);
while (playerIterator.hasNext())
playerIterator.next().value()->updateZones();
break;
}
case GameEventContext::DECK_SELECT: { case GameEventContext::DECK_SELECT: {
Context_DeckSelect deckSelect = context.GetExtension(Context_DeckSelect::ext); Context_DeckSelect deckSelect = context.GetExtension(Context_DeckSelect::ext);
messageLog->logDeckSelect(player, QString::fromStdString(deckSelect.deck_hash()), messageLog->logDeckSelect(player, QString::fromStdString(deckSelect.deck_hash()),
@ -1173,7 +1218,7 @@ Player *TabGame::setActivePlayer(int id)
{ {
Player *player = players.value(id, 0); Player *player = players.value(id, 0);
if (!player) if (!player)
return 0; return nullptr;
activePlayer = id; activePlayer = id;
playerListWidget->setActivePlayer(id); playerListWidget->setActivePlayer(id);
QMapIterator<int, Player *> i(players); QMapIterator<int, Player *> i(players);
@ -1237,11 +1282,11 @@ CardItem *TabGame::getCard(int playerId, const QString &zoneName, int cardId) co
{ {
Player *player = players.value(playerId, 0); Player *player = players.value(playerId, 0);
if (!player) if (!player)
return 0; return nullptr;
CardZone *zone = player->getZones().value(zoneName, 0); CardZone *zone = player->getZones().value(zoneName, 0);
if (!zone) if (!zone)
return 0; return nullptr;
return zone->getCard(cardId, QString()); 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 TabGame::getTabText() const
{ {
QString gameTypeInfo; QString gameTypeInfo;
if (gameTypes.size() != 0) { if (!gameTypes.empty()) {
gameTypeInfo = gameTypes.at(0); gameTypeInfo = gameTypes.at(0);
if (gameTypes.size() > 1) if (gameTypes.size() > 1)
gameTypeInfo.append("..."); gameTypeInfo.append("...");
@ -1290,7 +1335,7 @@ Player *TabGame::getActiveLocalPlayer() const
return temp; return temp;
} }
return 0; return nullptr;
} }
void TabGame::updateCardMenu(AbstractCardItem *card) void TabGame::updateCardMenu(AbstractCardItem *card)
@ -1307,6 +1352,8 @@ void TabGame::createMenuItems()
{ {
aNextPhase = new QAction(this); aNextPhase = new QAction(this);
connect(aNextPhase, SIGNAL(triggered()), this, SLOT(actNextPhase())); connect(aNextPhase, SIGNAL(triggered()), this, SLOT(actNextPhase()));
aNextPhaseAction = new QAction(this);
connect(aNextPhaseAction, SIGNAL(triggered()), this, SLOT(actNextPhaseAction()));
aNextTurn = new QAction(this); aNextTurn = new QAction(this);
connect(aNextTurn, SIGNAL(triggered()), this, SLOT(actNextTurn())); connect(aNextTurn, SIGNAL(triggered()), this, SLOT(actNextTurn()));
aRemoveLocalArrows = new QAction(this); aRemoveLocalArrows = new QAction(this);
@ -1321,7 +1368,7 @@ void TabGame::createMenuItems()
connect(aConcede, SIGNAL(triggered()), this, SLOT(actConcede())); connect(aConcede, SIGNAL(triggered()), this, SLOT(actConcede()));
aLeaveGame = new QAction(this); aLeaveGame = new QAction(this);
connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame())); connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame()));
aCloseReplay = 0; aCloseReplay = nullptr;
phasesMenu = new QMenu(this); phasesMenu = new QMenu(this);
for (int i = 0; i < phasesToolbar->phaseCount(); ++i) { for (int i = 0; i < phasesToolbar->phaseCount(); ++i) {
@ -1333,6 +1380,7 @@ void TabGame::createMenuItems()
phasesMenu->addSeparator(); phasesMenu->addSeparator();
phasesMenu->addAction(aNextPhase); phasesMenu->addAction(aNextPhase);
phasesMenu->addAction(aNextPhaseAction);
gameMenu = new QMenu(this); gameMenu = new QMenu(this);
playersSeparator = gameMenu->addSeparator(); playersSeparator = gameMenu->addSeparator();
@ -1351,19 +1399,20 @@ void TabGame::createMenuItems()
void TabGame::createReplayMenuItems() void TabGame::createReplayMenuItems()
{ {
aNextPhase = 0; aNextPhase = nullptr;
aNextTurn = 0; aNextPhaseAction = nullptr;
aRemoveLocalArrows = 0; aNextTurn = nullptr;
aRotateViewCW = 0; aRemoveLocalArrows = nullptr;
aRotateViewCCW = 0; aRotateViewCW = nullptr;
aResetLayout = 0; aRotateViewCCW = nullptr;
aGameInfo = 0; aResetLayout = nullptr;
aConcede = 0; aGameInfo = nullptr;
aLeaveGame = 0; aConcede = nullptr;
aLeaveGame = nullptr;
aCloseReplay = new QAction(this); aCloseReplay = new QAction(this);
connect(aCloseReplay, SIGNAL(triggered()), this, SLOT(actLeaveGame())); connect(aCloseReplay, SIGNAL(triggered()), this, SLOT(actLeaveGame()));
phasesMenu = 0; phasesMenu = nullptr;
gameMenu = new QMenu(this); gameMenu = new QMenu(this);
gameMenu->addAction(aCloseReplay); gameMenu->addAction(aCloseReplay);
addTabMenu(gameMenu); addTabMenu(gameMenu);
@ -1637,7 +1686,7 @@ void TabGame::createCardInfoDock(bool bReplay)
void TabGame::createPlayerListDock(bool bReplay) void TabGame::createPlayerListDock(bool bReplay)
{ {
if (bReplay) { if (bReplay) {
playerListWidget = new PlayerListWidget(0, 0, this); playerListWidget = new PlayerListWidget(nullptr, nullptr, this);
} else { } else {
playerListWidget = new PlayerListWidget(tabSupervisor, clients.first(), this); playerListWidget = new PlayerListWidget(tabSupervisor, clients.first(), this);
connect(playerListWidget, SIGNAL(openMessageDialog(QString, bool)), this, connect(playerListWidget, SIGNAL(openMessageDialog(QString, bool)), this,

View file

@ -163,8 +163,8 @@ private:
QAction *playersSeparator; QAction *playersSeparator;
QMenu *gameMenu, *phasesMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu, QMenu *gameMenu, *phasesMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu,
*replayDockMenu; *replayDockMenu;
QAction *aGameInfo, *aConcede, *aLeaveGame, *aCloseReplay, *aNextPhase, *aNextTurn, *aRemoveLocalArrows, QAction *aGameInfo, *aConcede, *aLeaveGame, *aCloseReplay, *aNextPhase, *aNextPhaseAction, *aNextTurn,
*aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout; *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout;
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aMessageLayoutDockVisible, *aMessageLayoutDockFloating, QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aMessageLayoutDockVisible, *aMessageLayoutDockFloating,
*aPlayerListDockVisible, *aPlayerListDockFloating, *aReplayDockVisible, *aReplayDockFloating; *aPlayerListDockVisible, *aPlayerListDockFloating, *aReplayDockVisible, *aReplayDockFloating;
QList<QAction *> phaseActions; QList<QAction *> phaseActions;
@ -233,6 +233,7 @@ private slots:
void actSay(); void actSay();
void actPhaseAction(); void actPhaseAction();
void actNextPhase(); void actNextPhase();
void actNextPhaseAction();
void actNextTurn(); void actNextTurn();
void addMentionTag(QString value); void addMentionTag(QString value);
@ -246,7 +247,7 @@ private slots:
void actResetLayout(); void actResetLayout();
void freeDocksSize(); void freeDocksSize();
bool eventFilter(QObject *o, QEvent *e); bool eventFilter(QObject *o, QEvent *e) override;
void dockVisibleTriggered(); void dockVisibleTriggered();
void dockFloatingTriggered(); void dockFloatingTriggered();
void dockTopLevelChanged(bool topLevel); void dockTopLevelChanged(bool topLevel);
@ -257,10 +258,10 @@ public:
const Event_GameJoined &event, const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes); const QMap<int, QString> &_roomGameTypes);
TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay); TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay);
~TabGame(); ~TabGame() override;
void retranslateUi(); void retranslateUi() override;
void updatePlayerListDockTitle(); void updatePlayerListDockTitle();
void closeRequest(); void closeRequest() override;
const QMap<int, Player *> &getPlayers() const const QMap<int, Player *> &getPlayers() const
{ {
return players; return players;
@ -278,7 +279,7 @@ public:
{ {
return gameInfo.game_id(); return gameInfo.game_id();
} }
QString getTabText() const; QString getTabText() const override;
bool getSpectator() const bool getSpectator() const
{ {
return spectator; return spectator;

View file

@ -299,16 +299,17 @@ void MainWindow::actAbout()
QMessageBox::NoIcon, tr("About Cockatrice"), QMessageBox::NoIcon, tr("About Cockatrice"),
QString("<font size=\"8\"><b>Cockatrice</b></font> (" + QString::fromStdString(BUILD_ARCHITECTURE) + ")<br>" + QString("<font size=\"8\"><b>Cockatrice</b></font> (" + QString::fromStdString(BUILD_ARCHITECTURE) + ")<br>" +
tr("Version") + QString(" %1").arg(VERSION_STRING) + "<br><br><b><a href='" + GITHUB_PAGES_URL + "'>" + tr("Version") + QString(" %1").arg(VERSION_STRING) + "<br><br><b><a href='" + GITHUB_PAGES_URL + "'>" +
tr("Cockatrice Webpage") + "</a></b><br>" + "<br><br><b>" + tr("Project Manager:") + tr("Cockatrice Webpage") + "</a></b><br>" + "<br><b>" + tr("Project Manager:") +
"</b><br>Gavin Bisesi<br><br>" + "<b>" + tr("Past Project Managers:") + "</b><br>Zach Halpern<br><br>" + "<b>" + tr("Past Project Managers:") +
"</b><br>Max-Wilhelm Bruker<br>Marcus Schütz<br><br>" + "<b>" + tr("Developers:") + "</b><br>" + "</b><br>Gavin Bisesi<br>Max-Wilhelm Bruker<br>Marcus Schütz<br><br>" + "<b>" + tr("Developers:") +
"<a href='" + GITHUB_CONTRIBUTORS_URL + "'>" + tr("Our Developers") + "</a><br>" + "<a href='" + "</b><br>" + "<a href='" + GITHUB_CONTRIBUTORS_URL + "'>" + tr("Our Developers") + "</a><br>" +
GITHUB_CONTRIBUTE_URL + "'>" + tr("Help Develop!") + "</a><br><br>" + "<b>" + tr("Translators:") + "<a href='" + GITHUB_CONTRIBUTE_URL + "'>" + tr("Help Develop!") + "</a><br><br>" + "<b>" +
"</b><br>" + "<a href='" + GITHUB_TRANSIFEX_TRANSLATORS_URL + "'>" + tr("Our Translators") + tr("Translators:") + "</b><br>" + "<a href='" + GITHUB_TRANSIFEX_TRANSLATORS_URL + "'>" +
"</a><br>" + "<a href='" + GITHUB_TRANSLATOR_FAQ_URL + "'>" + tr("Help Translate!") + "</a><br><br>" + tr("Our Translators") + "</a><br>" + "<a href='" + GITHUB_TRANSLATOR_FAQ_URL + "'>" +
"<b>" + tr("Support:") + "</b><br>" + "<a href='" + GITHUB_ISSUES_URL + "'>" + tr("Report an Issue") + tr("Help Translate!") + "</a><br><br>" + "<b>" + tr("Support:") + "</b><br>" + "<a href='" +
"</a><br>" + "<a href='" + GITHUB_TROUBLESHOOTING_URL + "'>" + tr("Troubleshooting") + "</a><br>" + GITHUB_ISSUES_URL + "'>" + tr("Report an Issue") + "</a><br>" + "<a href='" +
"<a href='" + GITHUB_FAQ_URL + "'>" + tr("F.A.Q.") + "</a><br>"), GITHUB_TROUBLESHOOTING_URL + "'>" + tr("Troubleshooting") + "</a><br>" + "<a href='" + GITHUB_FAQ_URL +
"'>" + tr("F.A.Q.") + "</a><br>"),
QMessageBox::Ok, this); QMessageBox::Ok, this);
mb.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64)); mb.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
mb.setTextInteractionFlags(Qt::TextBrowserInteraction); mb.setTextInteractionFlags(Qt::TextBrowserInteraction);
@ -317,9 +318,9 @@ void MainWindow::actAbout()
void MainWindow::actTips() void MainWindow::actTips()
{ {
if (tip != NULL) { if (tip != nullptr) {
delete tip; delete tip;
tip = NULL; tip = nullptr;
} }
tip = new DlgTipOfTheDay(); tip = new DlgTipOfTheDay();
if (tip->successfulInit) { if (tip->successfulInit) {
@ -705,15 +706,33 @@ void MainWindow::createActions()
aExit->setMenuRole(QAction::QuitRole); aExit->setMenuRole(QAction::QuitRole);
aAbout->setMenuRole(QAction::AboutRole); aAbout->setMenuRole(QAction::AboutRole);
char const *foo; // avoid "warning: expression result unused" under clang Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Services"));
foo = QT_TRANSLATE_NOOP("QMenuBar", "Services"); Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Hide %1"));
foo = QT_TRANSLATE_NOOP("QMenuBar", "Hide %1"); Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Hide Others"));
foo = QT_TRANSLATE_NOOP("QMenuBar", "Hide Others"); Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Show All"));
foo = QT_TRANSLATE_NOOP("QMenuBar", "Show All"); Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Preferences..."));
foo = QT_TRANSLATE_NOOP("QMenuBar", "Preferences..."); Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Quit %1"));
foo = QT_TRANSLATE_NOOP("QMenuBar", "Quit %1"); Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "About %1"));
foo = QT_TRANSLATE_NOOP("QMenuBar", "About %1");
#endif #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() void MainWindow::createMenus()
@ -811,7 +830,7 @@ MainWindow::MainWindow(QWidget *parent)
createTrayIcon(); createTrayIcon();
} }
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts())); connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts(); refreshShortcuts();
connect(db, SIGNAL(cardDatabaseLoadingFailed()), this, SLOT(cardDatabaseLoadingFailed())); connect(db, SIGNAL(cardDatabaseLoadingFailed()), this, SLOT(cardDatabaseLoadingFailed()));
@ -828,13 +847,30 @@ MainWindow::MainWindow(QWidget *parent)
if (tip->successfulInit && settingsCache->getShowTipsOnStartup() && tip->newTipsAvailable) { if (tip->successfulInit && settingsCache->getShowTipsOnStartup() && tip->newTipsAvailable) {
tip->show(); 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() MainWindow::~MainWindow()
{ {
if (tip != NULL) { if (tip != nullptr) {
delete tip; delete tip;
tip = NULL; tip = nullptr;
} }
if (trayIcon) { if (trayIcon) {
trayIcon->hide(); trayIcon->hide();
@ -1271,3 +1307,10 @@ void MainWindow::promptForgotPasswordReset()
dlg.getPlayerName(), dlg.getToken(), dlg.getPassword()); dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
} }
} }
void MainUpdateHelper::testForNewVersion()
{
if (settingsCache->getClientVersion() != VERSION_STRING) {
emit newVersionDetected(VERSION_STRING);
}
}

View file

@ -101,6 +101,8 @@ private slots:
void actManageSets(); void actManageSets();
void actEditTokens(); void actEditTokens();
void alertForcedOracleRun(const QString &);
private: private:
static const QString appName; static const QString appName;
static const QStringList fileNameFilters; static const QStringList fileNameFilters;
@ -146,4 +148,17 @@ protected:
QString extractInvalidUsernameMessage(QString &in); QString extractInvalidUsernameMessage(QString &in);
}; };
class MainUpdateHelper : public QObject
{
Q_OBJECT
signals:
void newVersionDetected(QString);
public:
explicit MainUpdateHelper() = default;
~MainUpdateHelper() override = default;
void testForNewVersion();
};
#endif #endif

View file

@ -127,21 +127,32 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
labNotes->setOpenExternalLinks(true); labNotes->setOpenExternalLinks(true);
labNotes->setText( labNotes->setText(
"<b>" + tr("Deck Editor") + ":</b> " + "<b>" + tr("Deck Editor") + ":</b> " +
tr("Only cards in enabled sets will appear in the deck editor card list") + "<br><b>" + tr("Card Art") + tr("Only cards in enabled sets will appear in the deck editor card list") + "<br><br>" + "<b>" +
":</b> " + tr("Image priority is decided in the following order") + "<ol><li>" + tr("The") + tr("Card Art") + ":</b> " + tr("Image priority is decided in the following order") + "<ol><li>" + tr("The") +
"<a " "<a href='https://github.com/Cockatrice/Cockatrice/wiki/"
"href='https://github.com/Cockatrice/Cockatrice/wiki/"
"Custom-Cards-%26-Sets#to-add-custom-art-for-cards-the-easiest-way-is-to-use-the-custom-folder'> " + "Custom-Cards-%26-Sets#to-add-custom-art-for-cards-the-easiest-way-is-to-use-the-custom-folder'> " +
tr("CUSTOM Folder") + "</a></li><li>" + tr("Enabled Sets (Top to Bottom)") + "</li><li>" + tr("CUSTOM Folder") + "</a></li><li>" + tr("Enabled Sets (Top to Bottom)") + "</li><li>" +
tr("Disabled Sets (Top to Bottom)") + "</li></ol>"); tr("Disabled Sets (Top to Bottom)") + "</li></ol>");
sortWarning = new QLabel; QGridLayout *hintsGrid = new QGridLayout;
sortWarning->setWordWrap(true); hintsGrid->addWidget(labNotes, 0, 0);
sortWarning->setText( hintsGroupBox = new QGroupBox(tr("Hints"));
"<b>" + tr("Warning: ") + "</b><br>" + hintsGroupBox->setLayout(hintsGrid);
tr("While the set list is sorted by any of the columns, custom art priority setting is disabled.") + "<br>" +
tr("To disable sorting click on the same column header again until this message disappears.")); sortWarning = new QGroupBox(tr("Note"));
sortWarning->setStyleSheet("QLabel { background-color:red;}"); 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); sortWarning->setVisible(false);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
@ -157,7 +168,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
mainLayout->addWidget(enableSomeButton, 2, 1); mainLayout->addWidget(enableSomeButton, 2, 1);
mainLayout->addWidget(disableSomeButton, 2, 2); mainLayout->addWidget(disableSomeButton, 2, 2);
mainLayout->addWidget(sortWarning, 3, 1, 1, 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->addWidget(buttonBox, 5, 1, 1, 2);
mainLayout->setColumnStretch(1, 1); mainLayout->setColumnStretch(1, 1);
mainLayout->setColumnStretch(2, 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) void WndSets::actDisableSortButtons(int index)
{ {
if (index != SORT_RESET) { if (index != SORT_RESET) {

View file

@ -8,13 +8,14 @@
#include <QMainWindow> #include <QMainWindow>
#include <QSet> #include <QSet>
class CardDatabase;
class QGroupBox;
class QItemSelection;
class QPushButton;
class QTreeView;
class SetsDisplayModel;
class SetsModel; class SetsModel;
class SetsProxyModel; class SetsProxyModel;
class SetsDisplayModel;
class QPushButton;
class CardDatabase;
class QItemSelection;
class QTreeView;
class WndSets : public QMainWindow class WndSets : public QMainWindow
{ {
@ -22,6 +23,7 @@ class WndSets : public QMainWindow
private: private:
SetsModel *model; SetsModel *model;
SetsDisplayModel *displayModel; SetsDisplayModel *displayModel;
QGroupBox *hintsGroupBox;
QTreeView *view; QTreeView *view;
QPushButton *toggleAllButton, *toggleSelectedButton; QPushButton *toggleAllButton, *toggleSelectedButton;
QPushButton *enableAllButton, *disableAllButton, *enableSomeButton, *disableSomeButton; QPushButton *enableAllButton, *disableAllButton, *enableSomeButton, *disableSomeButton;
@ -29,7 +31,10 @@ private:
QAction *aUp, *aDown, *aBottom, *aTop; QAction *aUp, *aDown, *aBottom, *aTop;
QToolBar *setsEditToolBar; QToolBar *setsEditToolBar;
QDialogButtonBox *buttonBox; QDialogButtonBox *buttonBox;
QLabel *labNotes, *searchLabel, *sortWarning; QLabel *labNotes, *searchLabel;
QGroupBox *sortWarning;
QLabel *sortWarningText;
QPushButton *sortWarningButton;
QLineEdit *searchField; QLineEdit *searchField;
QGridLayout *mainLayout; QGridLayout *mainLayout;
QHBoxLayout *filterBox; QHBoxLayout *filterBox;
@ -65,6 +70,7 @@ private slots:
void actRestoreOriginalOrder(); void actRestoreOriginalOrder();
void actDisableResetButton(const QString &filterText); void actDisableResetButton(const QString &filterText);
void actSort(int index); void actSort(int index);
void actIgnoreWarning();
}; };
#endif #endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -193,22 +193,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa
<message> <message>
<location filename="../src/releasechannel.cpp" line="207"/> <location filename="../src/releasechannel.cpp" line="207"/>
<source>Beta Releases</source> <source>Beta Releases</source>
<translation type="unfinished"/> <translation>version bêta</translation>
</message> </message>
<message> <message>
<location filename="../src/releasechannel.cpp" line="233"/> <location filename="../src/releasechannel.cpp" line="233"/>
<source>No reply received from the release update server.</source> <source>No reply received from the release update server.</source>
<translation type="unfinished"/> <translation>Le serveur de mise à jour ne répond pas.</translation>
</message> </message>
<message> <message>
<location filename="../src/releasechannel.cpp" line="242"/> <location filename="../src/releasechannel.cpp" line="242"/>
<source>Invalid reply received from the release update server.</source> <source>Invalid reply received from the release update server.</source>
<translation type="unfinished"/> <translation>Réponse reçue du serveur de mise à jour de version invalide .</translation>
</message> </message>
<message> <message>
<location filename="../src/releasechannel.cpp" line="277"/> <location filename="../src/releasechannel.cpp" line="277"/>
<source>No reply received from the file update server.</source> <source>No reply received from the file update server.</source>
<translation type="unfinished"/> <translation>Le serveur de mise à jour de fichier ne répond pas.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -442,47 +442,47 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa
<location filename="../src/dlg_settings.cpp" line="511"/> <location filename="../src/dlg_settings.cpp" line="511"/>
<location filename="../src/dlg_settings.cpp" line="561"/> <location filename="../src/dlg_settings.cpp" line="561"/>
<source>Update Spoilers</source> <source>Update Spoilers</source>
<translation type="unfinished"/> <translation>mettre à jour les spoilers</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="550"/> <location filename="../src/dlg_settings.cpp" line="550"/>
<source>Updating...</source> <source>Updating...</source>
<translation type="unfinished"/> <translation>Mise à jours</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="580"/> <location filename="../src/dlg_settings.cpp" line="580"/>
<source>Choose path</source> <source>Choose path</source>
<translation type="unfinished"/> <translation>Choisir le chemin</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="606"/> <location filename="../src/dlg_settings.cpp" line="606"/>
<source>Spoilers</source> <source>Spoilers</source>
<translation type="unfinished"/> <translation>Spoilers</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="607"/> <location filename="../src/dlg_settings.cpp" line="607"/>
<source>Download Spoilers Automatically</source> <source>Download Spoilers Automatically</source>
<translation type="unfinished"/> <translation>Télécharger automatiquement les spoilers</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="608"/> <location filename="../src/dlg_settings.cpp" line="608"/>
<source>Spoiler Location:</source> <source>Spoiler Location:</source>
<translation type="unfinished"/> <translation>Emplacement des spoilers:</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="609"/> <location filename="../src/dlg_settings.cpp" line="609"/>
<source>Hey, something&apos;s here finally!</source> <source>Hey, something&apos;s here finally!</source>
<translation type="unfinished"/> <translation>Hey, quelque chose est enfin !</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="610"/> <location filename="../src/dlg_settings.cpp" line="610"/>
<source>Last Updated</source> <source>Last Updated</source>
<translation type="unfinished"/> <translation>dernière mise à jour</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="611"/> <location filename="../src/dlg_settings.cpp" line="611"/>
<source>Spoilers download automatically on launch</source> <source>Spoilers download automatically on launch</source>
<translation type="unfinished"/> <translation>spoilers téléchargé automatiquement au lancement</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="612"/> <location filename="../src/dlg_settings.cpp" line="612"/>
@ -585,7 +585,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa
<message> <message>
<location filename="../src/dlg_connect.cpp" line="28"/> <location filename="../src/dlg_connect.cpp" line="28"/>
<source>Refresh the server list with known public servers</source> <source>Refresh the server list with known public servers</source>
<translation type="unfinished"/> <translation>Actualiser la liste de serveurs avec des serveurs publics connus</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="40"/> <location filename="../src/dlg_connect.cpp" line="40"/>
@ -625,33 +625,33 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa
<message> <message>
<location filename="../src/dlg_connect.cpp" line="78"/> <location filename="../src/dlg_connect.cpp" line="78"/>
<source>If you have any trouble connecting or registering then contact the server staff for help!</source> <source>If you have any trouble connecting or registering then contact the server staff for help!</source>
<translation type="unfinished"/> <translation>Si vous rencontrez des difficultés pour vous connecter ou vous inscrire, contactez le personnel du serveur pour obtenir de l&apos;aide!</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="80"/> <location filename="../src/dlg_connect.cpp" line="80"/>
<location filename="../src/dlg_connect.cpp" line="262"/> <location filename="../src/dlg_connect.cpp" line="262"/>
<source>Webpage</source> <source>Webpage</source>
<translation type="unfinished"/> <translation>page internet</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="90"/> <location filename="../src/dlg_connect.cpp" line="90"/>
<source>Forgot Password</source> <source>Forgot Password</source>
<translation type="unfinished"/> <translation>Mot de passe oublié</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="94"/> <location filename="../src/dlg_connect.cpp" line="94"/>
<source>&amp;Connect</source> <source>&amp;Connect</source>
<translation type="unfinished"/> <translation>&amp;Connexion </translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="136"/> <location filename="../src/dlg_connect.cpp" line="136"/>
<source>Server Contact</source> <source>Server Contact</source>
<translation type="unfinished"/> <translation>Contact du serveur</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="149"/> <location filename="../src/dlg_connect.cpp" line="149"/>
<source>Connect to Server</source> <source>Connect to Server</source>
<translation type="unfinished"/> <translation>Connecter au serveur</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="117"/> <location filename="../src/dlg_connect.cpp" line="117"/>
@ -686,7 +686,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa
<message> <message>
<location filename="../src/dlg_connect.h" line="76"/> <location filename="../src/dlg_connect.h" line="76"/>
<source>Downloading...</source> <source>Downloading...</source>
<translation type="unfinished"/> <translation>Téléchargement</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1041,7 +1041,8 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image.</tr
<location filename="../src/dlg_edit_tokens.cpp" line="150"/> <location filename="../src/dlg_edit_tokens.cpp" line="150"/>
<source>The chosen name conflicts with an existing card or token. <source>The chosen name conflicts with an existing card or token.
Make sure to enable the &apos;Token&apos; set in the &quot;Manage sets&quot; dialog to display them correctly.</source> Make sure to enable the &apos;Token&apos; set in the &quot;Manage sets&quot; dialog to display them correctly.</source>
<translation type="unfinished"/> <translation>Le nom choisi est en conflit avec une carte ou un jeton existant.
Assurez-vous d&apos;activer les set de &apos;jeton&apos; dans le menu &quot;gérer les sets&quot; afin de les afficher correctement.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1536,17 +1537,17 @@ Voulez vous changer les paramètres d&apos;emplacement de base de données ?</tr
<message> <message>
<location filename="../src/dlg_tip_of_the_day.cpp" line="67"/> <location filename="../src/dlg_tip_of_the_day.cpp" line="67"/>
<source>Next</source> <source>Next</source>
<translation type="unfinished"/> <translation>suivant</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_tip_of_the_day.cpp" line="68"/> <location filename="../src/dlg_tip_of_the_day.cpp" line="68"/>
<source>Previous</source> <source>Previous</source>
<translation type="unfinished"/> <translation>précédent</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_tip_of_the_day.cpp" line="90"/> <location filename="../src/dlg_tip_of_the_day.cpp" line="90"/>
<source>Tip of the Day</source> <source>Tip of the Day</source>
<translation type="unfinished"/> <translation>Conseil du jour</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1637,7 +1638,7 @@ Please visit the download page to update manually.</source>
<location filename="../src/dlg_update.cpp" line="147"/> <location filename="../src/dlg_update.cpp" line="147"/>
<location filename="../src/dlg_update.cpp" line="158"/> <location filename="../src/dlg_update.cpp" line="158"/>
<source>Released</source> <source>Released</source>
<translation type="unfinished"/> <translation>Sortie</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_update.cpp" line="148"/> <location filename="../src/dlg_update.cpp" line="148"/>
@ -1715,7 +1716,7 @@ Vous devez compiler les sources vous-même.</translation>
<message> <message>
<location filename="../src/dlg_viewlog.cpp" line="18"/> <location filename="../src/dlg_viewlog.cpp" line="18"/>
<source>Clear log when closing</source> <source>Clear log when closing</source>
<translation type="unfinished"/> <translation>Effacer le journal lors de la fermeture</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_viewlog.cpp" line="25"/> <location filename="../src/dlg_viewlog.cpp" line="25"/>
@ -1728,7 +1729,7 @@ Vous devez compiler les sources vous-même.</translation>
<message> <message>
<location filename="../src/filterbuilder.cpp" line="28"/> <location filename="../src/filterbuilder.cpp" line="28"/>
<source>Type your filter here</source> <source>Type your filter here</source>
<translation type="unfinished"/> <translation>Tapez votre filtre ici</translation>
</message> </message>
</context> </context>
<context> <context>
@ -2041,7 +2042,7 @@ Vous devez compiler les sources vous-même.</translation>
<message> <message>
<location filename="../src/dlg_settings.cpp" line="317"/> <location filename="../src/dlg_settings.cpp" line="317"/>
<source>Show tips on startup</source> <source>Show tips on startup</source>
<translation type="unfinished"/> <translation>Afficher les astuces au démarrage</translation>
</message> </message>
</context> </context>
<context> <context>
@ -2589,7 +2590,7 @@ La version locale est %1, la nouvelle version est %2.</translation>
<message> <message>
<location filename="../src/window_main.cpp" line="641"/> <location filename="../src/window_main.cpp" line="641"/>
<source>&amp;Tip of the Day</source> <source>&amp;Tip of the Day</source>
<translation type="unfinished"/> <translation>&amp;Conseil du jour</translation>
</message> </message>
<message> <message>
<location filename="../src/window_main.cpp" line="642"/> <location filename="../src/window_main.cpp" line="642"/>
@ -2764,14 +2765,14 @@ Cockatrice va maintenant recharger de nouveau la base de données de cartes.</tr
<message> <message>
<location filename="../src/window_main.cpp" line="637"/> <location filename="../src/window_main.cpp" line="637"/>
<source>&amp;Manage sets...</source> <source>&amp;Manage sets...</source>
<translation type="unfinished"/> <translation>&amp;Gérer les sets...</translation>
</message> </message>
<message> <message>
<location filename="../src/window_main.cpp" line="983"/> <location filename="../src/window_main.cpp" line="983"/>
<source>Hi! It seems like you're running this version of Cockatrice for the first time. <source>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. 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 &quot;Manage Sets&quot; dialog.</source> Read more about changing the set order or disabling specific sets and consequent effects in the &quot;Manage Sets&quot; dialog.</source>
<translation type="unfinished"/> <translation>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 é activés. Pour plus d&apos;informations sur la modification de l&apos;ordre des sets ou la désactivation de sets spécifiques et de leurs effets, consultez le menu &quot;Gérer les sets&quot;.</translation>
</message> </message>
<message> <message>
<location filename="../src/window_main.cpp" line="1098"/> <location filename="../src/window_main.cpp" line="1098"/>
@ -2920,12 +2921,12 @@ Cockactrice va maintenant recharger la base de données de cartes.</translation>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="366"/> <location filename="../src/messagelogwidget.cpp" line="366"/>
<source>%1 turns %2 face-down.</source> <source>%1 turns %2 face-down.</source>
<translation type="unfinished"/> <translation>%1 tourner %2 face cachée.</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="368"/> <location filename="../src/messagelogwidget.cpp" line="368"/>
<source>%1 turns %2 face-up.</source> <source>%1 turns %2 face-up.</source>
<translation type="unfinished"/> <translation>%1 tourner %2 face visible.</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="402"/> <location filename="../src/messagelogwidget.cpp" line="402"/>
@ -3862,7 +3863,7 @@ Cockactrice va maintenant recharger la base de données de cartes.</translation>
<message> <message>
<location filename="../src/player.cpp" line="2722"/> <location filename="../src/player.cpp" line="2722"/>
<source>View related cards</source> <source>View related cards</source>
<translation type="unfinished"/> <translation>Voir les cartes associées</translation>
</message> </message>
<message> <message>
<location filename="../src/player.cpp" line="2750"/> <location filename="../src/player.cpp" line="2750"/>
@ -3890,7 +3891,7 @@ Cockactrice va maintenant recharger la base de données de cartes.</translation>
<location filename="../src/player.cpp" line="690"/> <location filename="../src/player.cpp" line="690"/>
<source>T&amp;urn Over</source> <source>T&amp;urn Over</source>
<extracomment>Turn face up/face down</extracomment> <extracomment>Turn face up/face down</extracomment>
<translation type="unfinished"/> <translation>Retourner</translation>
</message> </message>
<message> <message>
<location filename="../src/player.cpp" line="713"/> <location filename="../src/player.cpp" line="713"/>
@ -4148,7 +4149,7 @@ Cockactrice va maintenant recharger la base de données de cartes.</translation>
<message> <message>
<location filename="../src/sequenceEdit/sequenceedit.cpp" line="158"/> <location filename="../src/sequenceEdit/sequenceedit.cpp" line="158"/>
<source>Invalid key</source> <source>Invalid key</source>
<translation type="unfinished"/> <translation>Clé invalide</translation>
</message> </message>
</context> </context>
<context> <context>
@ -4185,13 +4186,14 @@ Cockactrice va maintenant recharger la base de données de cartes.</translation>
<location filename="../src/shortcutssettings.cpp" line="40"/> <location filename="../src/shortcutssettings.cpp" line="40"/>
<source>Your configuration file contained invalid shortcuts. <source>Your configuration file contained invalid shortcuts.
Please check your shortcut settings!</source> Please check your shortcut settings!</source>
<translation type="unfinished"/> <translation>Votre fichier de configuration contenait des raccourcis invalides. Merci de vérifier vos paramètres de raccourci !</translation>
</message> </message>
<message> <message>
<location filename="../src/shortcutssettings.cpp" line="42"/> <location filename="../src/shortcutssettings.cpp" line="42"/>
<source>The following shortcuts have been set to default: <source>The following shortcuts have been set to default:
</source> </source>
<translation type="unfinished"/> <translation>Les raccourcis suivants ont é définis par défaut:
</translation>
</message> </message>
</context> </context>
<context> <context>
@ -4268,48 +4270,48 @@ Please check your shortcut settings!</source>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="99"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="99"/>
<source>Spoilers season has ended</source> <source>Spoilers season has ended</source>
<translation type="unfinished"/> <translation>La saison des spoilers est terminée</translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="99"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="99"/>
<source>Deleting spoiler.xml. Please run Oracle</source> <source>Deleting spoiler.xml. Please run Oracle</source>
<translation type="unfinished"/> <translation>Suppression de spoiler.xml. S&apos;il vous plaît exécuter Oracle</translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="109"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="109"/>
<location filename="../src/spoilerbackgroundupdater.cpp" line="116"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="116"/>
<source>Spoilers download failed</source> <source>Spoilers download failed</source>
<translation type="unfinished"/> <translation>Échec du téléchargement des spoilers</translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="109"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="109"/>
<source>No internet connection</source> <source>No internet connection</source>
<translation type="unfinished"/> <translation>Pas de connexion Internet</translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="116"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="116"/>
<source>Error</source> <source>Error</source>
<translation type="unfinished"/> <translation>Erreur</translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="137"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="137"/>
<source>Spoilers already up to date</source> <source>Spoilers already up to date</source>
<translation type="unfinished"/> <translation>Spoilers déjà à jour</translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="137"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="137"/>
<source>No new spoilers added</source> <source>No new spoilers added</source>
<translation type="unfinished"/> <translation>Aucun nouveau spoilers ajouté</translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="178"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="178"/>
<source>Spoilers have been updated!</source> <source>Spoilers have been updated!</source>
<translation type="unfinished"/> <translation>Les spoilers ont é mis à jour!</translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="178"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="178"/>
<source>Last change:</source> <source>Last change:</source>
<translation type="unfinished"/> <translation>Dernier changement:</translation>
</message> </message>
</context> </context>
<context> <context>
@ -4317,7 +4319,7 @@ Please check your shortcut settings!</source>
<message> <message>
<location filename="../src/releasechannel.cpp" line="90"/> <location filename="../src/releasechannel.cpp" line="90"/>
<source>Stable Releases</source> <source>Stable Releases</source>
<translation type="unfinished"/> <translation>Version stable</translation>
</message> </message>
<message> <message>
<location filename="../src/releasechannel.cpp" line="108"/> <location filename="../src/releasechannel.cpp" line="108"/>
@ -4438,57 +4440,57 @@ Please check your shortcut settings!</source>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="347"/> <location filename="../src/tab_deck_editor.cpp" line="347"/>
<source>Search by card name</source> <source>Search by card name</source>
<translation type="unfinished"/> <translation>Rechercher par nom de carte</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="453"/> <location filename="../src/tab_deck_editor.cpp" line="453"/>
<source>Add to Deck</source> <source>Add to Deck</source>
<translation type="unfinished"/> <translation>Ajouter au Deck</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="454"/> <location filename="../src/tab_deck_editor.cpp" line="454"/>
<source>Add to Sideboard</source> <source>Add to Sideboard</source>
<translation type="unfinished"/> <translation>Ajouter à la réserve</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="459"/> <location filename="../src/tab_deck_editor.cpp" line="459"/>
<source>Show Related cards</source> <source>Show Related cards</source>
<translation type="unfinished"/> <translation>Afficher les cartes associées</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="623"/> <location filename="../src/tab_deck_editor.cpp" line="623"/>
<source>Save deck to clipboard</source> <source>Save deck to clipboard</source>
<translation type="unfinished"/> <translation>Enregistrer le deck dans le presse-papier</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="624"/> <location filename="../src/tab_deck_editor.cpp" line="624"/>
<source>Annotated</source> <source>Annotated</source>
<translation type="unfinished"/> <translation>Annoté</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="625"/> <location filename="../src/tab_deck_editor.cpp" line="625"/>
<source>Not Annotated</source> <source>Not Annotated</source>
<translation type="unfinished"/> <translation>sans annotation</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="629"/> <location filename="../src/tab_deck_editor.cpp" line="629"/>
<source>&amp;Send deck to online service</source> <source>&amp;Send deck to online service</source>
<translation type="unfinished"/> <translation>&amp; Envoyer le Deck au service en ligne</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="630"/> <location filename="../src/tab_deck_editor.cpp" line="630"/>
<source>Create decklist (decklist.org)</source> <source>Create decklist (decklist.org)</source>
<translation type="unfinished"/> <translation>Créer une liste de deck (decklist.org)</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="631"/> <location filename="../src/tab_deck_editor.cpp" line="631"/>
<source>Analyze deck (deckstats.net)</source> <source>Analyze deck (deckstats.net)</source>
<translation type="unfinished"/> <translation>Analyser le Deck (deckstats.net)</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="632"/> <location filename="../src/tab_deck_editor.cpp" line="632"/>
<source>Analyze deck (tappedout.net)</source> <source>Analyze deck (tappedout.net)</source>
<translation type="unfinished"/> <translation>Analyser le deck (tappedout.net)</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="634"/> <location filename="../src/tab_deck_editor.cpp" line="634"/>
@ -4619,12 +4621,12 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez.</transl
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="877"/> <location filename="../src/tab_deck_editor.cpp" line="877"/>
<source>There are no cards in your deck to be exported</source> <source>There are no cards in your deck to be exported</source>
<translation type="unfinished"/> <translation>Il n&apos;y a pas de cartes dans le deck à exporter</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_deck_editor.cpp" line="889"/> <location filename="../src/tab_deck_editor.cpp" line="889"/>
<source>No deck was selected to be saved.</source> <source>No deck was selected to be saved.</source>
<translation type="unfinished"/> <translation>Aucun deck n&apos;a é sélectionné pour être sauvegardé.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -5077,7 +5079,7 @@ Plus vous entrez d&apos;informations, meilleurs seront les résultats.</translat
<message> <message>
<location filename="../src/tab_message.cpp" line="148"/> <location filename="../src/tab_message.cpp" line="148"/>
<source>Private message from</source> <source>Private message from</source>
<translation type="unfinished"/> <translation>Message privé de</translation>
</message> </message>
<message> <message>
<location filename="../src/tab_message.cpp" line="165"/> <location filename="../src/tab_message.cpp" line="165"/>
@ -5350,13 +5352,15 @@ Merci de ne pas recommencer ou d&apos;autres mesures peuvent être prises contre
<location filename="../src/tip_of_the_day.cpp" line="25"/> <location filename="../src/tip_of_the_day.cpp" line="25"/>
<source>File does not exist. <source>File does not exist.
</source> </source>
<translation type="unfinished"/> <translation>Le fichier n&apos;existe pas.
</translation>
</message> </message>
<message> <message>
<location filename="../src/tip_of_the_day.cpp" line="28"/> <location filename="../src/tip_of_the_day.cpp" line="28"/>
<source>Failed to open file. <source>Failed to open file.
</source> </source>
<translation type="unfinished"/> <translation>Échec de l&apos;ouverture du fichier.
</translation>
</message> </message>
</context> </context>
<context> <context>
@ -5810,17 +5814,17 @@ Merci de ne pas recommencer ou d&apos;autres mesures peuvent être prises contre
<message> <message>
<location filename="../src/window_sets.cpp" line="61"/> <location filename="../src/window_sets.cpp" line="61"/>
<source>Search by set name, code, or type</source> <source>Search by set name, code, or type</source>
<translation type="unfinished"/> <translation>Rechercher par ensemble de nom, code ou type</translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="66"/> <location filename="../src/window_sets.cpp" line="66"/>
<source>Default order</source> <source>Default order</source>
<translation type="unfinished"/> <translation>Ordre par défaut</translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="67"/> <location filename="../src/window_sets.cpp" line="67"/>
<source>Restore original art priority order</source> <source>Restore original art priority order</source>
<translation type="unfinished"/> <translation>Restaurer l&apos;ordre originale de priorité artistique</translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="107"/> <location filename="../src/window_sets.cpp" line="107"/>
@ -5885,22 +5889,22 @@ Merci de ne pas recommencer ou d&apos;autres mesures peuvent être prises contre
<message> <message>
<location filename="../src/window_sets.cpp" line="139"/> <location filename="../src/window_sets.cpp" line="139"/>
<source>Warning: </source> <source>Warning: </source>
<translation type="unfinished"/> <translation>Attention:</translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="140"/> <location filename="../src/window_sets.cpp" line="140"/>
<source>While the set list is sorted by any of the columns, custom art priority setting is disabled.</source> <source>While the set list is sorted by any of the columns, custom art priority setting is disabled.</source>
<translation type="unfinished"/> <translation>lorsque la liste est triée dans une des colonnes, la priorité personnalisée est désactivée.</translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="141"/> <location filename="../src/window_sets.cpp" line="141"/>
<source>To disable sorting click on the same column header again until this message disappears.</source> <source>To disable sorting click on the same column header again until this message disappears.</source>
<translation type="unfinished"/> <translation>Pour désactiver le tri, cliquez à nouveau sur la même en-tête de colonne jusqu&apos;à ce que ce message disparaisse.</translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="170"/> <location filename="../src/window_sets.cpp" line="170"/>
<source>Manage sets</source> <source>Manage sets</source>
<translation type="unfinished"/> <translation>Gérer les sets</translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="204"/> <location filename="../src/window_sets.cpp" line="204"/>
@ -6330,22 +6334,22 @@ Merci de ne pas recommencer ou d&apos;autres mesures peuvent être prises contre
<message> <message>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1764"/> <location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1764"/>
<source>Manage sets</source> <source>Manage sets</source>
<translation type="unfinished"/> <translation>Gérer les sets</translation>
</message> </message>
<message> <message>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1770"/> <location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1770"/>
<source>Export deck</source> <source>Export deck</source>
<translation type="unfinished"/> <translation>Exporter le Deck</translation>
</message> </message>
<message> <message>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1774"/> <location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1774"/>
<source>Save deck (clip)</source> <source>Save deck (clip)</source>
<translation type="unfinished"/> <translation>Sauvegarder le Deck (du presse-papier)</translation>
</message> </message>
<message> <message>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1776"/> <location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1776"/>
<source>Save deck (clip; no annotations)</source> <source>Save deck (clip; no annotations)</source>
<translation type="unfinished"/> <translation>Enregistrer le Deck (du presse papier; sans annotations)</translation>
</message> </message>
<message> <message>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1855"/> <location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1855"/>

View file

@ -267,7 +267,7 @@ This is only saved for moderators and cannot be seen by the banned person.</sour
<message> <message>
<location filename="../src/cardinfotext.cpp" line="123"/> <location filename="../src/cardinfotext.cpp" line="123"/>
<source>Unknown card:</source> <source>Unknown card:</source>
<translation type="unfinished"/> <translation>:</translation>
</message> </message>
<message> <message>
<location filename="../src/cardinfotext.cpp" line="131"/> <location filename="../src/cardinfotext.cpp" line="131"/>
@ -585,7 +585,7 @@ This is only saved for moderators and cannot be seen by the banned person.</sour
<message> <message>
<location filename="../src/dlg_connect.cpp" line="28"/> <location filename="../src/dlg_connect.cpp" line="28"/>
<source>Refresh the server list with known public servers</source> <source>Refresh the server list with known public servers</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="40"/> <location filename="../src/dlg_connect.cpp" line="40"/>
@ -625,33 +625,33 @@ This is only saved for moderators and cannot be seen by the banned person.</sour
<message> <message>
<location filename="../src/dlg_connect.cpp" line="78"/> <location filename="../src/dlg_connect.cpp" line="78"/>
<source>If you have any trouble connecting or registering then contact the server staff for help!</source> <source>If you have any trouble connecting or registering then contact the server staff for help!</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="80"/> <location filename="../src/dlg_connect.cpp" line="80"/>
<location filename="../src/dlg_connect.cpp" line="262"/> <location filename="../src/dlg_connect.cpp" line="262"/>
<source>Webpage</source> <source>Webpage</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="90"/> <location filename="../src/dlg_connect.cpp" line="90"/>
<source>Forgot Password</source> <source>Forgot Password</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="94"/> <location filename="../src/dlg_connect.cpp" line="94"/>
<source>&amp;Connect</source> <source>&amp;Connect</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="136"/> <location filename="../src/dlg_connect.cpp" line="136"/>
<source>Server Contact</source> <source>Server Contact</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="149"/> <location filename="../src/dlg_connect.cpp" line="149"/>
<source>Connect to Server</source> <source>Connect to Server</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_connect.cpp" line="117"/> <location filename="../src/dlg_connect.cpp" line="117"/>
@ -1030,7 +1030,7 @@ To remove your current avatar, confirm without choosing a new image.</source>
<message> <message>
<location filename="../src/dlg_edit_tokens.cpp" line="145"/> <location filename="../src/dlg_edit_tokens.cpp" line="145"/>
<source>Please enter the name of the token:</source> <source>Please enter the name of the token:</source>
<translation>:</translation> <translation>:</translation>
</message> </message>
<message> <message>
<location filename="../src/dlg_edit_tokens.cpp" line="149"/> <location filename="../src/dlg_edit_tokens.cpp" line="149"/>
@ -1771,7 +1771,7 @@ You may have to build from source yourself.</source>
<message> <message>
<location filename="../src/gameselector.cpp" line="169"/> <location filename="../src/gameselector.cpp" line="169"/>
<source>The game does not exist any more.</source> <source>The game does not exist any more.</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/gameselector.cpp" line="172"/> <location filename="../src/gameselector.cpp" line="172"/>
@ -2818,12 +2818,12 @@ Cockatrice will now reload the card database.</source>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="391"/> <location filename="../src/messagelogwidget.cpp" line="391"/>
<source>%1 is now watching the game.</source> <source>%1 is now watching the game.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="251"/> <location filename="../src/messagelogwidget.cpp" line="251"/>
<source>%1 has loaded a deck (%2).</source> <source>%1 has loaded a deck (%2).</source>
<translation>%1:%2</translation> <translation>%1 :%2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="253"/> <location filename="../src/messagelogwidget.cpp" line="253"/>
@ -2889,57 +2889,57 @@ Cockatrice will now reload the card database.</source>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="290"/> <location filename="../src/messagelogwidget.cpp" line="290"/>
<source>%1 gives %2 control over %3.</source> <source>%1 gives %2 control over %3.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="302"/> <location filename="../src/messagelogwidget.cpp" line="302"/>
<source>%1 puts %2 into play tapped%3.</source> <source>%1 puts %2 into play tapped%3.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="304"/> <location filename="../src/messagelogwidget.cpp" line="304"/>
<source>%1 puts %2 into play%3.</source> <source>%1 puts %2 into play%3.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="308"/> <location filename="../src/messagelogwidget.cpp" line="308"/>
<source>%1 exiles %2%3.</source> <source>%1 exiles %2%3.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="321"/> <location filename="../src/messagelogwidget.cpp" line="321"/>
<source>%1 puts %2%3 into their library %4 cards from the top.</source> <source>%1 puts %2%3 into their library %4 cards from the top.</source>
<translation>%1%2%3%4</translation> <translation>%1 %2 %3%4</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="324"/> <location filename="../src/messagelogwidget.cpp" line="324"/>
<source>%1 moves %2%3 to sideboard.</source> <source>%1 moves %2%3 to sideboard.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="327"/> <location filename="../src/messagelogwidget.cpp" line="327"/>
<source>%1 plays %2%3.</source> <source>%1 plays %2%3.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="366"/> <location filename="../src/messagelogwidget.cpp" line="366"/>
<source>%1 turns %2 face-down.</source> <source>%1 turns %2 face-down.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="368"/> <location filename="../src/messagelogwidget.cpp" line="368"/>
<source>%1 turns %2 face-up.</source> <source>%1 turns %2 face-up.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="402"/> <location filename="../src/messagelogwidget.cpp" line="402"/>
<source>%1 has left the game (%2).</source> <source>%1 has left the game (%2).</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="410"/> <location filename="../src/messagelogwidget.cpp" line="410"/>
<source>%1 is not watching the game any more (%2).</source> <source>%1 is not watching the game any more (%2).</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="546"/> <location filename="../src/messagelogwidget.cpp" line="546"/>
@ -3004,7 +3004,7 @@ Cockatrice will now reload the card database.</source>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="612"/> <location filename="../src/messagelogwidget.cpp" line="612"/>
<source>%1&apos;s turn.</source> <source>%1&apos;s turn.</source>
<translation>%1</translation> <translation> %1 </translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<location filename="../src/messagelogwidget.cpp" line="636"/> <location filename="../src/messagelogwidget.cpp" line="636"/>
@ -3024,12 +3024,12 @@ Cockatrice will now reload the card database.</source>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="151"/> <location filename="../src/messagelogwidget.cpp" line="151"/>
<source>%1 is now keeping the top card %2 revealed.</source> <source>%1 is now keeping the top card %2 revealed.</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="152"/> <location filename="../src/messagelogwidget.cpp" line="152"/>
<source>%1 is not revealing the top card %2 any longer.</source> <source>%1 is not revealing the top card %2 any longer.</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="446"/> <location filename="../src/messagelogwidget.cpp" line="446"/>
@ -3039,47 +3039,47 @@ Cockatrice will now reload the card database.</source>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="385"/> <location filename="../src/messagelogwidget.cpp" line="385"/>
<source>%1 has joined the game.</source> <source>%1 has joined the game.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="451"/> <location filename="../src/messagelogwidget.cpp" line="451"/>
<source>%1 is ready to start the game.</source> <source>%1 is ready to start the game.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="415"/> <location filename="../src/messagelogwidget.cpp" line="415"/>
<source>%1 is not ready to start the game any more.</source> <source>%1 is not ready to start the game any more.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="691"/> <location filename="../src/messagelogwidget.cpp" line="691"/>
<source>%1 has locked their sideboard.</source> <source>%1 has locked their sideboard.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="693"/> <location filename="../src/messagelogwidget.cpp" line="693"/>
<source>%1 has unlocked their sideboard.</source> <source>%1 has unlocked their sideboard.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="169"/> <location filename="../src/messagelogwidget.cpp" line="169"/>
<source>%1 has conceded the game.</source> <source>%1 has conceded the game.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="176"/> <location filename="../src/messagelogwidget.cpp" line="176"/>
<source>%1 has restored connection to the game.</source> <source>%1 has restored connection to the game.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="180"/> <location filename="../src/messagelogwidget.cpp" line="180"/>
<source>%1 has lost connection to the game.</source> <source>%1 has lost connection to the game.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="721"/> <location filename="../src/messagelogwidget.cpp" line="721"/>
<source>%1 shuffles %2.</source> <source>%1 shuffles %2.</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="522"/> <location filename="../src/messagelogwidget.cpp" line="522"/>
@ -3094,27 +3094,27 @@ Cockatrice will now reload the card database.</source>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="523"/> <location filename="../src/messagelogwidget.cpp" line="523"/>
<source>%1 flipped a coin. It landed as %2.</source> <source>%1 flipped a coin. It landed as %2.</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="527"/> <location filename="../src/messagelogwidget.cpp" line="527"/>
<source>%1 rolls a %2 with a %3-sided die.</source> <source>%1 rolls a %2 with a %3-sided die.</source>
<translation>%1%3%2</translation> <translation>%1 %3%2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="344"/> <location filename="../src/messagelogwidget.cpp" line="344"/>
<source>%1 draws %2 card(s).</source> <source>%1 draws %2 card(s).</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="749"/> <location filename="../src/messagelogwidget.cpp" line="749"/>
<source>%1 undoes their last draw.</source> <source>%1 undoes their last draw.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="752"/> <location filename="../src/messagelogwidget.cpp" line="752"/>
<source>%1 undoes their last draw (%2).</source> <source>%1 undoes their last draw (%2).</source>
<translation>%1 (%2) </translation> <translation>%1 ( %2 ) </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="72"/> <location filename="../src/messagelogwidget.cpp" line="72"/>
@ -3154,202 +3154,202 @@ Cockatrice will now reload the card database.</source>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="306"/> <location filename="../src/messagelogwidget.cpp" line="306"/>
<source>%1 puts %2%3 into their graveyard.</source> <source>%1 puts %2%3 into their graveyard.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="310"/> <location filename="../src/messagelogwidget.cpp" line="310"/>
<source>%1 moves %2%3 to their hand.</source> <source>%1 moves %2%3 to their hand.</source>
<translation>%1%2%3</translation> <translation>% 1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="313"/> <location filename="../src/messagelogwidget.cpp" line="313"/>
<source>%1 puts %2%3 into their library.</source> <source>%1 puts %2%3 into their library.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="315"/> <location filename="../src/messagelogwidget.cpp" line="315"/>
<source>%1 puts %2%3 on bottom of their library.</source> <source>%1 puts %2%3 on bottom of their library.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="317"/> <location filename="../src/messagelogwidget.cpp" line="317"/>
<source>%1 puts %2%3 on top of their library.</source> <source>%1 puts %2%3 on top of their library.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="439"/> <location filename="../src/messagelogwidget.cpp" line="439"/>
<source>%1 takes a mulligan to %2.</source> <source>%1 takes a mulligan to %2.</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="441"/> <location filename="../src/messagelogwidget.cpp" line="441"/>
<source>%1 draws their initial hand.</source> <source>%1 draws their initial hand.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="261"/> <location filename="../src/messagelogwidget.cpp" line="261"/>
<source>%1 destroys %2.</source> <source>%1 destroys %2.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="743"/> <location filename="../src/messagelogwidget.cpp" line="743"/>
<source>%1 unattaches %2.</source> <source>%1 unattaches %2.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="242"/> <location filename="../src/messagelogwidget.cpp" line="242"/>
<source>%1 creates token: %2%3.</source> <source>%1 creates token: %2%3.</source>
<translation>%1:%2%3</translation> <translation>%1 : %2 %3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="196"/> <location filename="../src/messagelogwidget.cpp" line="196"/>
<source>%1 points from their %2 to themselves.</source> <source>%1 points from their %2 to themselves.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="199"/> <location filename="../src/messagelogwidget.cpp" line="199"/>
<source>%1 points from their %2 to %3.</source> <source>%1 points from their %2 to %3.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="203"/> <location filename="../src/messagelogwidget.cpp" line="203"/>
<source>%1 points from %2&apos;s %3 to themselves.</source> <source>%1 points from %2&apos;s %3 to themselves.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="207"/> <location filename="../src/messagelogwidget.cpp" line="207"/>
<source>%1 points from %2&apos;s %3 to %4.</source> <source>%1 points from %2&apos;s %3 to %4.</source>
<translation>%1%2%3%4</translation> <translation>%1 %2 %3 %4 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="215"/> <location filename="../src/messagelogwidget.cpp" line="215"/>
<source>%1 points from their %2 to their %3.</source> <source>%1 points from their %2 to their %3.</source>
<translation>%1%2%3</translation> <translation>%1 %2 %3 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="218"/> <location filename="../src/messagelogwidget.cpp" line="218"/>
<source>%1 points from their %2 to %3&apos;s %4.</source> <source>%1 points from their %2 to %3&apos;s %4.</source>
<translation>%1%2%3%4</translation> <translation>%1 %2 %3 %4 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="224"/> <location filename="../src/messagelogwidget.cpp" line="224"/>
<source>%1 points from %2&apos;s %3 to their own %4.</source> <source>%1 points from %2&apos;s %3 to their own %4.</source>
<translation>%1%2%3%4</translation> <translation>%1 %2 %3 %4 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="230"/> <location filename="../src/messagelogwidget.cpp" line="230"/>
<source>%1 points from %2&apos;s %3 to %4&apos;s %5.</source> <source>%1 points from %2&apos;s %3 to %4&apos;s %5.</source>
<translation>%1%2%3%4%5</translation> <translation>%1 %2 %3 %4 %5 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="629"/> <location filename="../src/messagelogwidget.cpp" line="629"/>
<source>%1 places %2 %3 counter(s) on %4 (now %5).</source> <source>%1 places %2 %3 counter(s) on %4 (now %5).</source>
<translation>%1%4%3%2 (%5) </translation> <translation>%1 %4 %3%2 (%5) </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="631"/> <location filename="../src/messagelogwidget.cpp" line="631"/>
<source>%1 removes %2 %3 counter(s) from %4 (now %5).</source> <source>%1 removes %2 %3 counter(s) from %4 (now %5).</source>
<translation>%1%4%3%2 (%5) </translation> <translation>%1 %4 %3%2 (%5) </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="708"/> <location filename="../src/messagelogwidget.cpp" line="708"/>
<source>%1 taps their permanents.</source> <source>%1 taps their permanents.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="708"/> <location filename="../src/messagelogwidget.cpp" line="708"/>
<source>%1 untaps their permanents.</source> <source>%1 untaps their permanents.</source>
<translation>%1</translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="711"/> <location filename="../src/messagelogwidget.cpp" line="711"/>
<source>%1 taps %2.</source> <source>%1 taps %2.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="711"/> <location filename="../src/messagelogwidget.cpp" line="711"/>
<source>%1 untaps %2.</source> <source>%1 untaps %2.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="659"/> <location filename="../src/messagelogwidget.cpp" line="659"/>
<source>%1 sets counter %2 to %3 (%4%5).</source> <source>%1 sets counter %2 to %3 (%4%5).</source>
<translation>%1%2%3 (%4%5)</translation> <translation>%1 %2%3 (%4%5)</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="671"/> <location filename="../src/messagelogwidget.cpp" line="671"/>
<source>%1 sets %2 to not untap normally.</source> <source>%1 sets %2 to not untap normally.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="673"/> <location filename="../src/messagelogwidget.cpp" line="673"/>
<source>%1 sets %2 to untap normally.</source> <source>%1 sets %2 to untap normally.</source>
<translation>%1%2</translation> <translation>%1 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="682"/> <location filename="../src/messagelogwidget.cpp" line="682"/>
<source>%1 sets PT of %2 to %3.</source> <source>%1 sets PT of %2 to %3.</source>
<translation>%1%2P/Tを%3</translation> <translation>%1 %2 P/Tを%3</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="618"/> <location filename="../src/messagelogwidget.cpp" line="618"/>
<source>%1 sets annotation of %2 to %3.</source> <source>%1 sets annotation of %2 to %3.</source>
<translation>%1%2 (%3)</translation> <translation>%1 %2 (%3)</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="353"/> <location filename="../src/messagelogwidget.cpp" line="353"/>
<source>%1 is looking at %2.</source> <source>%1 is looking at %2.</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="357"/> <location filename="../src/messagelogwidget.cpp" line="357"/>
<source>%1 is looking at the top %2 card(s) %3.</source> <source>%1 is looking at the top %2 card(s) %3.</source>
<translation>%1%3%2</translation> <translation>%1 %3%2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="736"/> <location filename="../src/messagelogwidget.cpp" line="736"/>
<source>%1 stops looking at %2.</source> <source>%1 stops looking at %2.</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="479"/> <location filename="../src/messagelogwidget.cpp" line="479"/>
<source>%1 reveals %2 to %3.</source> <source>%1 reveals %2 to %3.</source>
<translation>%1%3%2</translation> <translation>%1 %3 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="484"/> <location filename="../src/messagelogwidget.cpp" line="484"/>
<source>%1 reveals %2.</source> <source>%1 reveals %2.</source>
<translation>%1%2</translation> <translation>%1 %2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="489"/> <location filename="../src/messagelogwidget.cpp" line="489"/>
<source>%1 randomly reveals %2%3 to %4.</source> <source>%1 randomly reveals %2%3 to %4.</source>
<translation>%1%3%2%4</translation> <translation>%1 %3 %2 %4</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="496"/> <location filename="../src/messagelogwidget.cpp" line="496"/>
<source>%1 randomly reveals %2%3.</source> <source>%1 randomly reveals %2%3.</source>
<translation>%1%3%2</translation> <translation>%1 %3 %2 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="501"/> <location filename="../src/messagelogwidget.cpp" line="501"/>
<source>%1 peeks at face down card #%2.</source> <source>%1 peeks at face down card #%2.</source>
<translation>%1#%2</translation> <translation>%1 #%2</translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="503"/> <location filename="../src/messagelogwidget.cpp" line="503"/>
<source>%1 peeks at face down card #%2: %3.</source> <source>%1 peeks at face down card #%2: %3.</source>
<translation>%1#%2 (%3) </translation> <translation>%1 #%2 ( %3 ) </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="508"/> <location filename="../src/messagelogwidget.cpp" line="508"/>
<source>%1 reveals %2%3 to %4.</source> <source>%1 reveals %2%3 to %4.</source>
<translation>%1%3%2%4</translation> <translation>%1 %3 %2 %4 </translation>
</message> </message>
<message> <message>
<location filename="../src/messagelogwidget.cpp" line="515"/> <location filename="../src/messagelogwidget.cpp" line="515"/>
<source>%1 reveals %2%3.</source> <source>%1 reveals %2%3.</source>
<translation>%1%3%2</translation> <translation>%1 %3 %2 </translation>
</message> </message>
</context> </context>
<context> <context>
@ -3515,7 +3515,7 @@ Cockatrice will now reload the card database.</source>
<message> <message>
<location filename="../src/player.cpp" line="617"/> <location filename="../src/player.cpp" line="617"/>
<source>Player &quot;%1&quot;</source> <source>Player &quot;%1&quot;</source>
<translation> &quot;%1&quot;</translation> <translation>: %1</translation>
</message> </message>
<message> <message>
<location filename="../src/player.cpp" line="618"/> <location filename="../src/player.cpp" line="618"/>
@ -3836,7 +3836,7 @@ Cockatrice will now reload the card database.</source>
<location filename="../src/player.cpp" line="1085"/> <location filename="../src/player.cpp" line="1085"/>
<location filename="../src/player.cpp" line="2877"/> <location filename="../src/player.cpp" line="2877"/>
<source>C&amp;reate another %1 token</source> <source>C&amp;reate another %1 token</source>
<translation>%1</translation> <translation> %1 </translation>
</message> </message>
<message> <message>
<location filename="../src/player.cpp" line="1228"/> <location filename="../src/player.cpp" line="1228"/>
@ -4274,7 +4274,7 @@ Please check your shortcut settings!</source>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="99"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="99"/>
<source>Spoilers season has ended</source> <source>Spoilers season has ended</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="99"/> <location filename="../src/spoilerbackgroundupdater.cpp" line="99"/>
@ -5063,7 +5063,7 @@ The more information you put in, the more specific your results will be.</source
<message> <message>
<location filename="../src/tab_message.cpp" line="66"/> <location filename="../src/tab_message.cpp" line="66"/>
<source>Private &amp;chat</source> <source>Private &amp;chat</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/tab_message.cpp" line="67"/> <location filename="../src/tab_message.cpp" line="67"/>
@ -5073,7 +5073,7 @@ The more information you put in, the more specific your results will be.</source
<message> <message>
<location filename="../src/tab_message.cpp" line="83"/> <location filename="../src/tab_message.cpp" line="83"/>
<source>%1 - Private chat</source> <source>%1 - Private chat</source>
<translation>%1 - </translation> <translation>%1 - </translation>
</message> </message>
<message> <message>
<location filename="../src/tab_message.cpp" line="110"/> <location filename="../src/tab_message.cpp" line="110"/>
@ -5893,17 +5893,17 @@ Please refrain from engaging in this activity or further actions may be taken ag
<message> <message>
<location filename="../src/window_sets.cpp" line="139"/> <location filename="../src/window_sets.cpp" line="139"/>
<source>Warning: </source> <source>Warning: </source>
<translation type="unfinished"/> <translation>:</translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="140"/> <location filename="../src/window_sets.cpp" line="140"/>
<source>While the set list is sorted by any of the columns, custom art priority setting is disabled.</source> <source>While the set list is sorted by any of the columns, custom art priority setting is disabled.</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="141"/> <location filename="../src/window_sets.cpp" line="141"/>
<source>To disable sorting click on the same column header again until this message disappears.</source> <source>To disable sorting click on the same column header again until this message disappears.</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/window_sets.cpp" line="170"/> <location filename="../src/window_sets.cpp" line="170"/>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,7 @@ SET(common_SOURCES
server_room.cpp server_room.cpp
serverinfo_user_container.cpp serverinfo_user_container.cpp
sfmt/SFMT.c sfmt/SFMT.c
expression.cpp
) )
set(ORACLE_LIBS) set(ORACLE_LIBS)

View file

@ -2,8 +2,17 @@
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QRegularExpression>
#include <QTextStream> #include <QTextStream>
#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<MoveCard_ToZone> &_moveList) SideboardPlan::SideboardPlan(const QString &_name, const QList<MoveCard_ToZone> &_moveList)
: name(_name), moveList(_moveList) : name(_name), moveList(_moveList)
{ {
@ -477,161 +486,131 @@ bool DeckList::saveToFile_Native(QIODevice *device)
bool DeckList::loadFromStream_Plain(QTextStream &in) 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<QRegularExpression, QString> differences{{QRegularExpression(""), QString("'")},
{QRegularExpression("Æ"), QString("Ae")},
{QRegularExpression("æ"), QString("ae")},
{QRegularExpression(" ?[|/]+ ?"), QString(" // ")},
{QRegularExpression("(?<![A-Z]) ?& ?"), QString(" // ")}};
cleanList(); cleanList();
QVector<QString> inputs; // QTextStream -> QVector
bool priorEntryIsBlank = true, isAtBeginning = true; QStringList inputs = in.readAll().trimmed().split('\n');
int blankLines = 0; int max_line = inputs.size();
while (!in.atEnd()) {
QString line = in.readLine().simplified().toLower();
/* // start at the first empty line before the first cardline
* Removes all blank lines at start of inputs int deckStart = inputs.indexOf(reCardLine);
* Ex: ("", "", "", "Card1", "Card2") => ("Card1", "Card2") if (deckStart == -1) { // there are no cards?
* if (inputs.indexOf(reComment) == -1)
* This will also concise multiple blank lines in a row to just one blank return false; // input is empty
* Ex: ("Card1", "Card2", "", "", "", "Card3") => ("Card1", "Card2", "", "Card3") deckStart = max_line;
*/ } else {
if (line.isEmpty()) { deckStart = inputs.lastIndexOf(reEmpty, deckStart);
if (priorEntryIsBlank || isAtBeginning) { if (deckStart == -1) {
continue; deckStart = 0;
}
priorEntryIsBlank = true;
blankLines++;
} else {
isAtBeginning = false;
priorEntryIsBlank = false;
} }
inputs.push_back(line);
} }
/* // find sideboard position, if marks are used this won't be needed
* Removes blank line at end of inputs (if applicable) int sBStart = -1;
* Ex: ("Card1", "Card2", "") => ("Card1", "Card2") if (inputs.indexOf(reSBMark, deckStart) == -1) {
* NOTE: Any duplicates were taken care of above, so there can be sBStart = inputs.indexOf(reSBComment, deckStart);
* at most one blank line at the very end if (sBStart == -1) {
*/ sBStart = inputs.indexOf(reEmpty, deckStart + 1);
if (!inputs.empty() && inputs.last().isEmpty()) { if (sBStart == -1) {
blankLines--; sBStart = max_line;
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";
} }
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; continue;
} QString cardName = match.captured().simplified();
// If we have a blank line and it's the _ONLY_ blank line in the paste // check if card should be sideboard
// and it follows at least one valid card bool sideboard = false;
// Then we assume it means to start the sideboard section of the paste. if (sBStart < 0) {
// If we have the word "Sideboard" appear on any line, then that will match = reSBMark.match(cardName);
// also indicate the start of the sideboard. if (match.hasMatch()) {
if ((line.isEmpty() && blankLines == 1 && okRows > 0) || line.startsWith("sideboard")) { sideboard = true;
inSideboard = true; cardName = match.captured(1);
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;
} }
} else {
if (index == sBStart) // skip sideboard line itself
continue;
sideboard = index > sBStart;
} }
bool ok; // check if a specific amount is mentioned
int number = line.left(i).toInt(&ok); int amount = 1;
match = reMultiplier.match(cardName);
if (!ok) { if (match.hasMatch()) {
number = 1; // If input is "cardName" assume it's "1x cardName" 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 // replace common differences in cardnames
// and what's commonly used in decklists for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) {
rx.setPattern(""); cardName.replace(diff.key(), diff.value());
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)));
} }
// We need to get the name of the card from the database, // get cardname, this function does nothing if the name is not found
// 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")
cardName = getCompleteCardName(cardName); cardName = getCompleteCardName(cardName);
// Look for the correct card zone of where to place the new card // get zone name based on if it's in sideboard
QString zoneName = getCardZoneFromName(cardName, isSideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN); QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
okRows++; // make new entry in decklist
new DecklistCardNode(cardName, number, getZoneObjFromName(zoneName)); new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName));
} }
updateDeckHash(); updateDeckHash();
return (okRows > 0); return true;
} }
InnerDecklistNode *DeckList::getZoneObjFromName(const QString zoneName) InnerDecklistNode *DeckList::getZoneObjFromName(const QString zoneName)

108
common/expression.cpp Normal file
View file

@ -0,0 +1,108 @@
#include "expression.h"
#include "./lib/peglib.h"
#include <QByteArray>
#include <QString>
#include <cmath>
#include <functional>
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<QString, std::function<double(double)>> *default_functions = nullptr;
Expression::Expression(double initial) : value(initial)
{
if (default_functions == nullptr) {
default_functions = new QMap<QString, std::function<double(double)>>();
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<QString, std::function<double(double)>>(*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<peg::Ast> ast;
if (math.parse(ba.data(), ast)) {
ast = peg::AstOptimizer(true).optimize(ast);
return eval(*ast);
}
return 0;
}

28
common/expression.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef EXPRESSION_H
#define EXPRESSION_H
#include <QMap>
#include <QString>
#include <functional>
namespace peg
{
template <typename Annotation> struct AstBase;
struct EmptyType;
typedef AstBase<EmptyType> 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<QString, std::function<double(double)>> fns;
};
#endif

3293
common/lib/peglib.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -169,3 +169,12 @@ if (UNIX)
set(cockatrice_protocol_LIBS ${cockatrice_protocol_LIBS} -lpthread) set(cockatrice_protocol_LIBS ${cockatrice_protocol_LIBS} -lpthread)
endif (UNIX) endif (UNIX)
target_link_libraries(cockatrice_protocol ${cockatrice_protocol_LIBS}) target_link_libraries(cockatrice_protocol ${cockatrice_protocol_LIBS})
# ubuntu uses an outdated package for protobuf, 3.1.0 is required
if(${Protobuf_VERSION} VERSION_LESS "3.1.0")
# remove unused parameter and misleading indentation warnings when compiling to avoid errors
set(CMAKE_CXX_FLAGS_DEBUG
"${CMAKE_CXX_FLAGS_DEBUG} -Wno-unused-parameter -Wno-misleading-indentation")
message(WARNING "Outdated protobuf version found (${Protobuf_VERSION} < 3.1.0), "
"disabled warnings to avoid compilation errors.")
endif()

View file

@ -5,3 +5,10 @@ message Command_Concede {
optional Command_Concede ext = 1017; optional Command_Concede ext = 1017;
} }
} }
message Command_Unconcede {
extend GameCommand {
optional Command_Unconcede ext = 1032;
}
}

View file

@ -6,3 +6,9 @@ message Context_Concede {
optional Context_Concede ext = 1001; optional Context_Concede ext = 1001;
} }
} }
message Context_Unconcede {
extend GameEventContext {
optional Context_Unconcede ext = 1009;
}
}

Some files were not shown because too many files have changed in this diff Show more