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
cache:
- c:\openssl-release
- c:\protobuf-release
- c:\zlib-release
# TODO: set dependency on ps skript file (in ./ci) / "-> appveyor.yml" maybe not ideal
# that way when we update specific cached tools there (like protobuf or zlib), cache will be newly created automatically
# https://www.appveyor.com/docs/build-cache/#cleaning-up-cache
- c:\Tools\vcpkg\installed
environment:
openssl_ver: 1.0.2o
protobuf_ver: 3.6.0
zlib_ver: 1.2.11
matrix:
- target_arch: win64
qt_ver: 5.9\msvc2017_64
cmake_generator: Visual Studio 15 2017 Win64
cmake_toolset: v141,host=x64
vc_arch: amd64
vcpkg_arch: x64
- target_arch: win32
qt_ver: 5.9\msvc2015 # Qt doesn't provide a msvc2017_32
cmake_generator: Visual Studio 15 2017
cmake_toolset: v141
vc_arch: amd64_x86
vcpkg_arch: x86
install:
- ps: |
if (Test-Path c:\openssl-release) {
echo "using openssl from cache"
} else {
if ($env:target_arch -eq "win64") { # 64bit filename
# echo "downloading 64bit version of openssl"
Invoke-WebRequest "https://indy.fulgan.com/SSL/openssl-$env:openssl_ver-x64_86-win64.zip" -OutFile c:\openssl-$env:openssl_ver.zip
} else { # 32bit filename
# echo "downloading 32bit version of openssl"
Invoke-WebRequest "https://indy.fulgan.com/SSL/openssl-$env:openssl_ver-i386-win32.zip" -OutFile c:\openssl-$env:openssl_ver.zip
}
Expand-Archive -Path c:\openssl-$env:openssl_ver.zip -DestinationPath c:\openssl-release
Set-Location -Path C:\openssl-release
}
if (Test-Path c:\protobuf-release) {
echo "using protobuf from cache"
} else {
Invoke-WebRequest "https://github.com/protocolbuffers/protobuf/releases/download/v$env:protobuf_ver/protobuf-cpp-$env:protobuf_ver.zip" -OutFile c:\protobuf-cpp-$env:protobuf_ver.zip
Expand-Archive -Path c:\protobuf-cpp-$env:protobuf_ver.zip -DestinationPath c:\
Set-Location -Path C:\protobuf-$env:protobuf_ver\cmake
cmake . -G "$env:cmake_generator" -T "$env:cmake_toolset" -Dprotobuf_BUILD_TESTS=0 -Dprotobuf_MSVC_STATIC_RUNTIME=0 -DCMAKE_INSTALL_PREFIX=c:/protobuf-release
msbuild INSTALL.vcxproj /p:Configuration=Release
}
if (Test-Path c:\zlib-release) {
echo "using zlib from cache"
} else {
Invoke-WebRequest "https://github.com/madler/zlib/archive/v$env:zlib_ver.zip" -OutFile c:\zlib-$env:zlib_ver.zip
Expand-Archive -Path c:\zlib-$env:zlib_ver.zip -DestinationPath c:\
Set-Location -Path C:\zlib-$env:zlib_ver
cmake . -G "$env:cmake_generator" -T "$env:cmake_toolset" -DCMAKE_INSTALL_PREFIX=c:/zlib-release
msbuild INSTALL.vcxproj /p:Configuration=Release
}
- vcpkg remove --outdated --recurse
- vcpkg install openssl protobuf liblzma zlib --triplet %vcpkg_arch%-windows
services:
- mysql
@ -92,13 +51,10 @@ build_script:
- ps: |
New-Item -ItemType directory -Path $env:APPVEYOR_BUILD_FOLDER\build
Set-Location -Path $env:APPVEYOR_BUILD_FOLDER\build
$zlibdir = "c:\zlib-release"
$openssldir = "C:\openssl-release"
$protodir = "c:\protobuf-release"
$protoc = "c:\protobuf-release\bin\protoc.exe"
$vcpkgbindir = "C:\Tools\vcpkg\installed\$env:vcpkg_arch-windows\bin"
$mysqldll = "c:\Program Files\MySQL\MySQL Server 5.7\lib\libmysql.dll"
cmake --version
cmake .. -G "$env:cmake_generator" -T "$env:cmake_toolset" "-DCMAKE_PREFIX_PATH=c:/Qt/$env:qt_ver;$protodir;$zlibdir;$openssldir" "-DWITH_SERVER=1" "-DPROTOBUF_PROTOC_EXECUTABLE=$protoc" "-DMYSQLCLIENT_LIBRARIES=$mysqldll"
cmake .. -G "$env:cmake_generator" -T "$env:cmake_toolset" "-DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake" "-DCMAKE_PREFIX_PATH=c:/Qt/$env:qt_ver;$vcpkgbindir" "-DWITH_SERVER=1" "-DMYSQLCLIENT_LIBRARIES=$mysqldll"
- msbuild PACKAGE.vcxproj /p:Configuration=Release
- ps: |
$exe = dir -name *.exe
@ -110,7 +66,7 @@ build_script:
(New-Object PSObject | Add-Member -PassThru NoteProperty bin $new_name | Add-Member -PassThru NoteProperty cmake $cmake_name | Add-Member -PassThru NoteProperty commit $env:APPVEYOR_REPO_COMMIT) | ConvertTo-JSON | Out-File -FilePath "latest-$env:target_arch" -Encoding ASCII
Push-AppveyorArtifact "latest-$env:target_arch"
$version = $matches['content']
test: off

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
# 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
# Setup
./servatrice/check_schema_version.sh
mkdir -p build
cd build
prefix=""
if [[ $TRAVIS_OS_NAME == "osx" ]]; then
export PATH="/usr/local/opt/ccache/bin:$PATH"
prefix="-DCMAKE_PREFIX_PATH=$(echo /usr/local/opt/qt5/)"
# Add cmake flags
if [[ $MAKE_SERVER ]]; then
flags+=" -DWITH_SERVER=1"
fi
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
prefix="-DCMAKE_PREFIX_PATH=$(echo /opt/qt5*/lib/cmake/)"
if [[ $MAKE_TEST ]]; then
flags+=" -DTEST=1"
BUILDTYPE="Debug" # test requires buildtype Debug
fi
if [[ $BUILDTYPE ]]; then
flags+=" -DCMAKE_BUILD_TYPE=$BUILDTYPE"
fi
if [[ $PACKAGE_TYPE ]]; then
flags+=" -DCPACK_GENERATOR=$PACKAGE_TYPE"
fi
# Add qt install location when using brew
if [[ $(uname) == "Darwin" ]]; then
PATH="/usr/local/opt/ccache/bin:$PATH"
flags+=" -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5/"
fi
# Compile
cmake --version
cmake .. $flags
make -j2
if [[ $BUILDTYPE == "Debug" ]]; then
cmake .. -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=$BUILDTYPE $prefix -DTEST=1
make -j2
if [[ $MAKE_TEST ]]; then
make test
if [[ $TRAVIS_OS_NAME == "osx" ]]; then
make install
fi
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
cd ..
clang-format -version
clang-format -i \
common/*.h \
common/*.cpp \
cockatrice/src/*.h \
cockatrice/src/*.cpp \
oracle/src/*.h \
oracle/src/*.cpp \
servatrice/src/*.h \
servatrice/src/*.cpp
git clean -f
git diff --quiet || (
echo "*****************************************************";
echo "*** This PR is not clean against our code style ***";
echo "*** Run clang-format and fix up any differences ***";
echo "*** Check our CONTRIBUTING.md file for details! ***";
echo "*** Thank you ♥ ***";
echo "*****************************************************";
)
git diff --exit-code
fi
fi
if [[ $BUILDTYPE == "Release" ]]; then
cmake .. -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=$BUILDTYPE $prefix
make package -j2
if [[ $MAKE_INSTALL ]]; then
make install
fi
if [[ $MAKE_PACKAGE ]]; then
make package
if [[ $PACKAGE_NAME ]]; then
found=$(find . -maxdepth 1 -type f -name "Cockatrice-*.*" -print -quit)
path=${found%/*}
file=${found##*/}
if [[ ! $file ]]; then
echo "could not find package" >&2
exit 1
fi
mv "$path/$file" "$path/${file%.*}-$PACKAGE_NAME.${file##*.}"
fi
fi

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 #
### Formatting and continuous integration (ci) ###
We currently use Travis CI to check your code for formatting issues, if your pull request was rejected because of this it would show a message in the logs. Click on "Details" next to the failed Travis CI build and then click on the failed build (most likely the fastest one) to see the log.
The message will look somewhat similar to this:
```
************************************************************
*** Your code does not meet our formatting guidelines. ***
*** Please correct it then commit and push your changes. ***
*** See our CONTRIBUTING.md file for more information. ***
************************************************************
```
The CONTRIBUTING.md file mentioned is this file. Please read [this section](#Formatting) for full information on our formatting guidelines.
### Compatibility ###
Cockatrice is currently compiled on all platforms using <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
`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
`.cpp`.
@ -38,28 +62,28 @@ Use header guards in the form of `FILE_NAME_H`.
Simple functions, such as getters, may be written inline in the header file,
but other functions should be written in the source file.
Keep library includes and project includes grouped together. So this is okay:
Group library includes after project includes, and in alphabetic order. Like this:
```c++
// Good
#include <QList>
#include <QString>
#include "card.h"
#include "deck.h"
// Good
#include "card.h"
#include "deck.h"
#include <QList>
#include <QString>
// Bad:
// Bad
#include <QList>
#include "card.h"
#include <QString>
#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
function and variable names.
@ -89,16 +113,16 @@ Bar& bar2 = *bar1;
Use `nullptr` instead of `NULL` (or `0`) for null pointers.
If you find any usage of the old keywords, we encourage you to fix it.
### Braces ###
#### Braces ####
Braces should go on their own line except for control statements, the use of braces around single line statements is preferred.
See the following example:
```c++
int main()
{ // function or class: own line
if (someCondition) { // control statement: same line
doSomething(); // single line statement, braces preferred
} else if (someOtherCondition1) { // else goes after closing brace
{ // function or class: own line
if (someCondition) { // control statement: same line
doSomething(); // single line statement, braces preferred
} else if (someOtherCondition1) { // else goes on the same line as a closing brace
for (int i = 0; i < 100; i++) {
doSomethingElse();
}
@ -110,20 +134,19 @@ int main()
}
```
### Indentation ###
#### Indentation and Spacing ####
Always indent using 4 spaces, do not use tabs. Opening and closing braces should be on the same indentation layer, member access specifiers in classes or structs should not be indented.
### Lines ###
All operators and braces should be separated by spaces, do not add a space next to the inside of a brace.
Do not have trailing whitespace in your lines, if possible. Most IDEs check for this nowadays and clean it up for you.
If multiple lines of code that follow eachother have single line comments behind them, place all of them on the same indentation level. This indentation level should be equal to the longest line of code for each of these comments, without added spacing.
Lines should be 120 characters or less, but you can exceed this if you find it necessary.
#### Lines ####
### Automatic Formatting ###
Do not have trailing whitespace in your lines. Most IDEs check for this nowadays and clean it up for you.
The handy tool `clang-format` can format your code for you, a special `.clang-format` configuration file is included [here](https://github.com/Cockatrice/Cockatrice/blob/master/.clang-format).
See [the clang-format documentation](https://clang.llvm.org/docs/ClangFormat.html) for more information.
Lines should be 120 characters or less. Please break up lines that are too long into smaller parts, for example at spaces or after opening a brace.
### Memory Management ###
@ -177,35 +200,32 @@ You can find more information on how we use Protobuf on [our wiki!](https://gith
# Translations #
**Basic workflow for translations:**
Basic workflow for translations:
1. Developer adds a `tr("foo")` string in the code;
2. Every few days, a maintainer updates the `*_en.ts files` with the new strings;
3. Transifex picks up the new files from github every 24 hours;
4. Translators translate the new untraslated strings on Transifex;
4. Translators translate the new untranslated strings on Transifex;
5. Before a release, a maintainer fetches the updated translations from Transifex.
### Translations (for developers) ###
### Using Translations (for developers) ###
**Step 1: Adding translatable strings to the code (`tr("foo")`)**
All the user-interface strings inside Cockatrice's source code must be written in
english language.<br>
All the user-interface strings inside Cockatrice's source code must be written in english.
Translations to other languages are managed using [Transifex](https://www.transifex.com/projects/p/cockatrice/).
If you're about to propose a change that adds or modifies any translatable string
in the code, you don't need to take care of adding the new strings to the
translation files. Every few days, or when a lot of new strings have been added,
someone from the development team will take care of extracing all the new strings,
adding them to the english translation files and making them available to
translators on Transifex.
Adding a new string to translate is as easy as adding the string in the 'tr("")' function, the string will be picked up as translatable automatically and translated as needed.
For example setting the text of this label in a way that the string "My name is:" can be translated:
```c++
nameLabel.setText(tr("My name is:"));
```
### Translations (for maintainers) ###
If you're about to propose a change that adds or modifies any translatable string in the code, you don't need to take care of adding the new strings to the translation files.
Every few days, or when a lot of new strings have been added, someone from the development team will take care of extracting all the new strings and adding them to the english translation files and making them available to translators on Transifex.
**Step 2: Updating `*_en.ts` files with new strings**
### Maintaining Translations (for maintainers) ###
When new translatable strings have been added to the code, it would be nice to
When new translatable strings have been added to the code, a maintainer should
make them available to translators on Transifex. Every few days, or when a lot
of new strings have been added, a maintainer should take care of extracing all
of new strings have been added, a maintainer should take care of extracting all
the new strings and add them to the english translation files.
To update the english translation files, re-run cmake enabling the appropriate
@ -227,20 +247,18 @@ You should then notice that the following files have uncommitted changes:
cockatrice/translations/cockatrice_en.ts
oracle/translations/oracle_en.ts
It's now suggested to disable the parameter using:
It is recommended to disable the parameter afterwards using:
```sh
cmake .. -DUPDATE_TRANSLATIONS=OFF
```
Now you are ready to propose your change.
**Step 3: Automatic pushing to Transifex**
Once your change gets merged, Transifex will pick up the modified files automatically (checks every 24 hours)
Once your change gets merged, Transifex will pick up the modified files automatically (checked every 24 hours)
and update the interface where translators will be able to translate the new strings.
**Step 5: Fetching new translations from Transifex**
### Releasing Translations (for maintainers) ###
Before rushing out a new release, it would be nice to fetch the most up to date
Before rushing out a new release, a maintainer should fetch the most up to date
translations from Transifex and commit them into the Cockatrice source code.
This can be done manually from the Transifex web interface, but it's quite time
consuming.
@ -252,10 +270,9 @@ As an alternative, you can install the Transifex CLI:
You'll then be able to use a git-like cli command to push and pull translations
from Transifex to the source code and vice versa.
### Translations (for translators) ###
**Step 4: Editing translations at Transifex**
### Adding Translations (for translators) ###
As a translator you can help translate the new strings on [Transifex](https://www.transifex.com/projects/p/cockatrice/).
Please have a look at the specific [FAQ for translators](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ).
@ -295,11 +312,11 @@ git tag -d $TAG_NAME
**NOTE:** Unfortunately, due to the method of how Travis and AppVeyor work, to publish a stable release you will need to make a copy of the release notes locally and then paste them into the GitHub GUI once the binaries have been uploaded by them. These CI services will automatically overwrite the name of the release (to "Cockatrice $TAG_NAME"), the status of the release (to "Pre-release"), and the release body (to "Beta build of Cockatrice").
**NOTE 2:** In the first lines of https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt there's an hardcoded version number used when compiling custom (not tagged) versions. While on tagged versions these numbers are overriden by the version numbers coming from the tag title, it's a good practice to keep them aligned with the real ones.
The preferred flow of operations is:
**NOTE 2:** In the first lines of https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt there's an hardcoded version number used when compiling custom (not tagged) versions. While on tagged versions these numbers are overridden by the version numbers coming from the tag title, it's good practice to keep them aligned with the real ones.
The preferred flow of operation is:
* just before a release, update the version number in CMakeLists.txt to "next release version";
* tag the release following the previously described syntax in order to get it built by CI;
* wait for CI to upload the binaries, double check if everything is in order
* after the release is complete, update the version number again to "next targeted beta version", typically increasing `PROJECT_VERSION_PATCH` by one.
**NOTE 3:** When releasing a new stable version, all the previous beta versions should be deleted. This is needed for Cockatrice to pick up the stable release also for users that chose the "beta" release channel.
**NOTE 3:** When releasing a new stable version, all the previous beta versions should be deleted. This is needed for Cockatrice to update users of the "beta" release channel to the latest version like other users.

1
.gitignore vendored
View File

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

View File

@ -1,54 +1,114 @@
language: cpp
compiler: gcc
cache: ccache
matrix:
include:
#Ubuntu
- name: Ubuntu (Debug)
#Ubuntu Xenial (Debug only)
- name: Ubuntu Xenial (Debug)
if: tag IS NOT present
os: linux
dist: xenial
group: stable
env: BUILDTYPE=Debug
- name: Ubuntu (Release)
cache: ccache
addons:
apt:
packages:
- libprotobuf-dev
- protobuf-compiler
- liblzma-dev
- qt5-default
- qttools5-dev
- qttools5-dev-tools
- qtmultimedia5-dev
- libqt5multimedia5-plugins
- libqt5svg5-dev
- libqt5sql5-mysql
- libqt5websockets5-dev
script: bash ./.ci/travis-compile.sh --format --server --test --debug
#Ubuntu Bionic (on docker)
- name: Ubuntu Bionic (Debug)
if: tag IS NOT present
services: docker
env: NAME=UbuntuBionic
cache:
directories:
- $HOME/$NAME/
before_install: docker build -t "cockatrice_${NAME,,}" .ci/$NAME && mkdir -p $HOME/$NAME/.ccache
script: docker run --mount "type=bind,source=$(pwd),target=/src" -w="/src"
--mount "type=bind,source=$HOME/$NAME/.ccache,target=/.ccache" -e "CCACHE_DIR=/.ccache"
"cockatrice_${NAME,,}"
bash .ci/travis-compile.sh --server --debug
- name: Ubuntu Bionic (Release)
if: (branch = master AND NOT type = pull_request) OR tag IS present
os: linux
dist: xenial
group: stable
env: BUILDTYPE=Release
services: docker
env: NAME=UbuntuBionic
cache:
directories:
- $HOME/$NAME/
before_install: docker build -t "cockatrice_${NAME,,}" .ci/$NAME && mkdir -p $HOME/$NAME/.ccache
script: docker run --mount "type=bind,source=$(pwd),target=/src" -w="/src"
--mount "type=bind,source=$HOME/$NAME/.ccache,target=/.ccache" -e "CCACHE_DIR=/.ccache"
"cockatrice_${NAME,,}"
bash .ci/travis-compile.sh --server --package "$NAME" --release
#Fedora 29 (on docker)
- name: Fedora 29 (Debug)
if: tag IS NOT present
services: docker
env: NAME=Fedora29
cache:
directories:
- $HOME/$NAME/
before_install: docker build -t "cockatrice_${NAME,,}" .ci/$NAME && mkdir -p $HOME/$NAME/.ccache
script: docker run --mount "type=bind,source=$(pwd),target=/src" -w="/src"
--mount "type=bind,source=$HOME/$NAME/.ccache,target=/.ccache" -e "CCACHE_DIR=/.ccache"
"cockatrice_${NAME,,}"
bash .ci/travis-compile.sh --server --debug
- name: Fedora 29 (Release)
if: (branch = master AND NOT type = pull_request) OR tag IS present
services: docker
env: NAME=Fedora29
cache:
directories:
- $HOME/$NAME/
before_install: docker build -t "cockatrice_${NAME,,}" .ci/$NAME && mkdir -p $HOME/$NAME/.ccache
script: docker run --mount "type=bind,source=$(pwd),target=/src" -w="/src"
--mount "type=bind,source=$HOME/$NAME/.ccache,target=/.ccache" -e "CCACHE_DIR=/.ccache"
"cockatrice_${NAME,,}"
bash .ci/travis-compile.sh --server --package "$NAME" "RPM" --release
#macOS
- name: macOS (Debug)
if: tag IS NOT present
os: osx
osx_image: xcode8
env: BUILDTYPE=Debug
osx_image: xcode10.1
cache: ccache
addons:
homebrew:
packages:
- ccache
- protobuf
- qt
- xz
script: bash ./.ci/travis-compile.sh --server --install --debug
- name: macOS (Release)
if: (branch = master AND NOT type = pull_request) OR tag IS present
os: osx
osx_image: xcode8
env: BUILDTYPE=Release
#install dependencies for container-based "linux" builds
addons:
apt:
packages:
- libprotobuf-dev
- protobuf-compiler
- qt5-default
- qttools5-dev
- qttools5-dev-tools
- qtmultimedia5-dev
- libqt5multimedia5-plugins
- libqt5svg5-dev
- libqt5sql5-mysql
- libqt5websockets5-dev
before_install: bash ./.ci/travis-dependencies.sh
script: bash ./.ci/travis-compile.sh
osx_image: xcode9.2
cache: ccache
addons:
homebrew:
packages:
- ccache
- protobuf
- qt
- xz
update: true
script: bash ./.ci/travis-compile.sh --server --package "$TRAVIS_OS_NAME" --release
# Builds for pull requests skip the deployment step altogether
deploy:
@ -67,7 +127,7 @@ deploy:
on:
tags: true
repo: Cockatrice/Cockatrice
condition: $BUILDTYPE = Release && $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}-beta(\.([2-9]|[1-9][0-9]))?$ # regex to match semver naming convention for beta pre-releases
condition: $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}-beta(\.([2-9]|[1-9][0-9]))?$ # regex to match semver naming convention for beta pre-releases
# Deploy configuration for "stable" releases
- provider: releases
@ -82,7 +142,7 @@ deploy:
on:
tags: true
repo: Cockatrice/Cockatrice
condition: $BUILDTYPE = Release && $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}$ # regex to match semver naming convention for stable full releases
condition: $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}$ # regex to match semver naming convention for stable full releases
notifications:

View File

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

View File

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

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)**
Dependencies:
Dependencies: *(for minimum requirements search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
- [Qt](https://www.qt.io/developers/)
- [protobuf](https://github.com/google/protobuf)
- [CMake](https://www.cmake.org/)
Oracle can optionally use zlib to load zipped files:
Oracle can optionally use zlib and xz to load compressed files:
- [zlib](https://www.zlib.net/)
- [xz](https://tukaani.org/xz/)
To compile:

View File

@ -1,22 +1,209 @@
#!/bin/bash
# This script will run clang-format on all modified, non-3rd-party C++/Header files.
# Never, ever, should this recieve a path with a newline in it. Don't bother proofing it for that.
set -e
if hash clang-format 2>/dev/null && hash git 2>/dev/null; then
files_to_clean=($(git diff --name-only $(git merge-base origin/master HEAD)))
# go to the project root directory, this file should be located in the project root directory
cd "${BASH_SOURCE%/*}/" || exit 2 # could not find path, this could happen with special links etc.
printf "%s\n" ${files_to_clean[@]} | \
xargs -I{} find '{}' \( -name "*.cpp" -o -name "*.h" \) \
-not -path "./cockatrice/src/qt-json/*" \
-not -path "./servatrice/src/smtp/*" \
-not -path "./common/sfmt/*" \
-not -path "./oracle/src/zip/*" \
-not -path "./build*/*" \
-exec clang-format -style=file -i {} \;
echo "Successfully formatted following files:"
printf "%s\n" ${files_to_clean[@]}
else
echo "Please install clang-format and git to use this program"
# defaults
include=("common" \
"cockatrice/src" \
"oracle/src" \
"servatrice/src")
exclude=("servatrice/src/smtp" \
"common/sfmt" \
"common/lib" \
"oracle/src/zip" \
"oracle/src/lzma" \
"oracle/src/qt-json")
exts=("cpp" "h")
cf_cmd="clang-format"
branch="origin/master"
# parse options
while [[ $@ ]]; do
case "$1" in
'-b'|'--branch')
branch=$2
set_branch=1
shift 2
;;
'-c'|'--color-diff')
color=" --color=always"
mode=diff
shift
;;
'-d'|'--diff')
mode=diff
shift
;;
'-h'|'--help')
cat <<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
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
if (WIN32)
set(VCREDISTRUNTIME_FOUND "NO")
if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64-bit
set(REDIST_ARCH x64)
else()
set(REDIST_ARCH x86)
endif()
set(REDIST_FILE vc_redist.${REDIST_ARCH}.exe)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
# Check if the list contains minimum one element, to get the path from
list(LENGTH CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS libsCount)
if (libsCount GREATER 0)
list(GET CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS 0 _path)
get_filename_component(_path ${_path} DIRECTORY)
get_filename_component(_path ${_path}/../../ ABSOLUTE)
if (EXISTS "${_path}/${REDIST_FILE}") # VS 2017
set(VCREDISTRUNTIME_FOUND "YES")
set(VCREDISTRUNTIME_FILE ${_path}/${REDIST_FILE})
endif()
endif()
if(VCREDISTRUNTIME_FOUND)
message(STATUS "Found VCredist ${VCREDISTRUNTIME_FILE}")
else()
message(WARNING "Could not find VCredist package. It's not required for compiling, but needs to be available at runtime.")
endif()
endif()
# Find the MS Visual Studio VC redistributable package
if (WIN32)
set(VCREDISTRUNTIME_FOUND "NO")
if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64-bit
set(REDIST_ARCH x64)
else()
set(REDIST_ARCH x86)
endif()
set(REDIST_FILE vcredist_${REDIST_ARCH}.exe)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
# Check if the list contains minimum one element, to get the path from
list(LENGTH CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS libsCount)
if (libsCount GREATER 0)
list(GET CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS 0 _path)
get_filename_component(_path ${_path} DIRECTORY)
get_filename_component(_path ${_path}/../../ ABSOLUTE)
if (EXISTS "${_path}/${REDIST_FILE}") # VS 2017
set(VCREDISTRUNTIME_FOUND "YES")
set(VCREDISTRUNTIME_FILE ${_path}/${REDIST_FILE})
endif()
endif()
if(VCREDISTRUNTIME_FOUND)
message(STATUS "Found VCredist ${VCREDISTRUNTIME_FILE}")
else()
message(WARNING "Could not find VCredist package. It's not required for compiling, but needs to be available at runtime.")
endif()
endif()

View File

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

View File

@ -34,5 +34,7 @@
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSRequiresAquaSystemAppearance</key>
<true/>
</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" "VersionMinor" "@CPACK_PACKAGE_VERSION_MINOR@"
IfFileExists "$INSTDIR\vc_redist.x86.exe" VcRedist86Exists PastVcRedist86Check
IfFileExists "$INSTDIR\vcredist_x86.exe" VcRedist86Exists PastVcRedist86Check
VcRedist86Exists:
ExecWait '"$INSTDIR\vc_redist.x86.exe" /passive /norestart'
ExecWait '"$INSTDIR\vcredist_x86.exe" /passive /norestart'
DetailPrint "Sleep to ensure unlock of vc_redist file after installation..."
Sleep 3000
Delete "$INSTDIR\vc_redist.x86.exe"
Delete "$INSTDIR\vcredist_x86.exe"
PastVcRedist86Check:
IfFileExists "$INSTDIR\vc_redist.x64.exe" VcRedist64Exists PastVcRedist64Check
IfFileExists "$INSTDIR\vcredist_x64.exe" VcRedist64Exists PastVcRedist64Check
VcRedist64Exists:
ExecWait '"$INSTDIR\vc_redist.x64.exe" /passive /norestart'
ExecWait '"$INSTDIR\vcredist_x64.exe" /passive /norestart'
DetailPrint "Sleep to ensure unlock of vc_redist file after installation..."
Sleep 3000
Delete "$INSTDIR\vc_redist.x64.exe"
Delete "$INSTDIR\vcredist_x64.exe"
PastVcRedist64Check:
${Else}

View File

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

View File

@ -351,6 +351,7 @@
<file>resources/tips/images/cockatrice_register.png</file>
<file>resources/tips/images/cockatrice_wiki.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/filter_games.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>
<date>2018-03-01</date>
</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>

View File

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

View File

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

View File

@ -1,5 +1,7 @@
#include "carddatabase.h"
#include "carddbparser/cockatricexml3.h"
#include "carddbparser/cockatricexml4.h"
#include "game_specific_terms.h"
#include "pictureloader.h"
#include "settingscache.h"
#include "spoilerbackgroundupdater.h"
@ -207,30 +209,23 @@ void SetList::guessSortKeys()
}
}
CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set)
{
}
CardInfo::CardInfo(const QString &_name,
bool _isToken,
const QString &_manacost,
const QString &_cmc,
const QString &_cardtype,
const QString &_powtough,
const QString &_text,
const QStringList &_colors,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
bool _upsideDownArt,
const QString &_loyalty,
CardInfoPerSetMap _sets,
bool _cipt,
int _tableRow,
const SetList &_sets,
const QStringMap &_customPicURLs,
MuidMap _muIds,
QStringMap _collectorNumbers,
QStringMap _rarities)
: name(_name), isToken(_isToken), sets(_sets), manacost(_manacost), cmc(_cmc), cardtype(_cardtype),
powtough(_powtough), text(_text), colors(_colors), relatedCards(_relatedCards),
reverseRelatedCards(_reverseRelatedCards), setsNames(), upsideDownArt(_upsideDownArt), loyalty(_loyalty),
customPicURLs(_customPicURLs), muIds(std::move(_muIds)), collectorNumbers(std::move(_collectorNumbers)),
rarities(std::move(_rarities)), cipt(_cipt), tableRow(_tableRow)
bool _upsideDownArt)
: name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), tableRow(_tableRow),
upsideDownArt(_upsideDownArt)
{
pixmapCacheKey = QLatin1String("card_") + name;
simpleName = CardInfo::simplifyName(name);
@ -244,76 +239,27 @@ CardInfo::~CardInfo()
}
CardInfoPtr CardInfo::newInstance(const QString &_name,
bool _isToken,
const QString &_manacost,
const QString &_cmc,
const QString &_cardtype,
const QString &_powtough,
const QString &_text,
const QStringList &_colors,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
bool _upsideDownArt,
const QString &_loyalty,
CardInfoPerSetMap _sets,
bool _cipt,
int _tableRow,
const SetList &_sets,
const QStringMap &_customPicURLs,
MuidMap _muIds,
QStringMap _collectorNumbers,
QStringMap _rarities)
bool _upsideDownArt)
{
CardInfoPtr ptr(new CardInfo(_name, _isToken, _manacost, _cmc, _cardtype, _powtough, _text, _colors, _relatedCards,
_reverseRelatedCards, _upsideDownArt, _loyalty, _cipt, _tableRow, _sets,
_customPicURLs, std::move(_muIds), std::move(_collectorNumbers),
std::move(_rarities)));
CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
_sets, _cipt, _tableRow, _upsideDownArt));
ptr->setSmartPointer(ptr);
for (int i = 0; i < _sets.size(); i++) {
_sets[i]->append(ptr);
for (const CardInfoPerSet &set : _sets) {
set.getPtr()->append(ptr);
}
return ptr;
}
QString CardInfo::getMainCardType() const
{
QString result = getCardType();
/*
Legendary Artifact Creature - Golem
Instant // Instant
*/
int pos;
if ((pos = result.indexOf('-')) != -1) {
result.remove(pos, result.length());
}
if ((pos = result.indexOf("")) != -1) {
result.remove(pos, result.length());
}
if ((pos = result.indexOf("//")) != -1) {
result.remove(pos, result.length());
}
result = result.simplified();
/*
Legendary Artifact Creature
Instant
*/
if ((pos = result.lastIndexOf(' ')) != -1) {
result = result.mid(pos + 1);
}
/*
Creature
Instant
*/
return result;
}
QString CardInfo::getCorrectedName() const
{
QString result = name;
@ -321,26 +267,21 @@ QString CardInfo::getCorrectedName() const
return result.remove(" // ").remove(':').remove('"').remove('?').replace('/', ' ');
}
void CardInfo::addToSet(CardSetPtr set)
void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info)
{
if (set.isNull()) {
qDebug() << "addToSet(nullptr)";
return;
}
set->append(smartThis);
sets << set;
_set->append(smartThis);
sets.insert(_set->getShortName(), _info);
refreshCachedSetNames();
}
void CardInfo::refreshCachedSetNames()
{
// update the cached list of set names
QStringList setList;
for (int i = 0; i < sets.size(); i++) {
if (sets[i]->getEnabled()) {
setList << sets[i]->getShortName();
// update the cached list of set names
for (const auto &set : sets) {
if (set.getPtr()->getEnabled()) {
setList << set.getPtr()->getShortName();
}
}
setsNames = setList.join(", ");
@ -369,11 +310,12 @@ QString CardInfo::simplifyName(const QString &name)
const QChar CardInfo::getColorChar() const
{
QString colors = getColors();
switch (colors.size()) {
case 0:
return QChar();
case 1:
return colors.first().isEmpty() ? QChar() : colors.first().at(0);
return colors.at(0);
default:
return QChar('m');
}
@ -386,6 +328,7 @@ CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoa
// add new parsers here
availableParsers << new CockatriceXml3Parser;
availableParsers << new CockatriceXml4Parser;
for (auto &parser : availableParsers) {
connect(parser, SIGNAL(addCard(CardInfoPtr)), this, SLOT(addCard(CardInfoPtr)), Qt::DirectConnection);
@ -417,9 +360,7 @@ void CardDatabase::clear()
simpleNameCards.clear();
sets.clear();
for (auto parser : availableParsers) {
parser->clearSetlist();
}
ICardDatabaseParser::clearSetlist();
loadStatus = NotLoaded;
@ -436,12 +377,8 @@ void CardDatabase::addCard(CardInfoPtr card)
// if card already exists just add the new set property
if (cards.contains(card->getName())) {
CardInfoPtr sameCard = cards[card->getName()];
for (auto set : card->getSets()) {
QString setName = set->getCorrectedShortName();
sameCard->setSet(set);
sameCard->setMuId(setName, card->getMuId(setName));
sameCard->setRarity(setName, card->getRarity(setName));
sameCard->setSetNumber(setName, card->getCollectorNumber(setName));
for (const CardInfoPerSet &set : card->getSets()) {
sameCard->addToSet(set.getPtr(), set);
}
return;
}
@ -582,7 +519,7 @@ LoadStatus CardDatabase::loadCardDatabases()
// load custom card databases
QDir dir(settingsCache->getCustomCardDatabasePath());
for (QString fileName :
for (const QString &fileName :
dir.entryList(QStringList("*.xml"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase)) {
loadCardDatabase(dir.absoluteFilePath(fileName));
}
@ -614,20 +551,13 @@ void CardDatabase::refreshCachedReverseRelatedCards()
continue;
}
QString relatedCardName;
if (card->getPowTough().size() > 0) {
relatedCardName = card->getPowTough() + " " + card->getName(); // "n/n name"
} else {
relatedCardName = card->getName(); // "name"
}
foreach (CardRelation *cardRelation, card->getReverseRelatedCards()) {
const QString &targetCard = cardRelation->getName();
if (!cards.contains(targetCard)) {
continue;
}
auto *newCardRelation = new CardRelation(relatedCardName, cardRelation->getDoesAttach(),
auto *newCardRelation = new CardRelation(card->getName(), cardRelation->getDoesAttach(),
cardRelation->getIsCreateAllExclusion(),
cardRelation->getIsVariable(), cardRelation->getDefaultCount());
cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation);
@ -635,23 +565,6 @@ void CardDatabase::refreshCachedReverseRelatedCards()
}
}
QStringList CardDatabase::getAllColors() const
{
QSet<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
{
QSet<QString> types;
@ -717,8 +630,8 @@ bool CardDatabase::saveCustomTokensToFile()
tmpSets.insert(CardDatabase::TOKENS_SETNAME, customTokensSet);
CardNameMap tmpCards;
for (CardInfoPtr card : cards) {
if (card->getSets().contains(customTokensSet)) {
for (const CardInfoPtr &card : cards) {
if (card->getSets().contains(CardDatabase::TOKENS_SETNAME)) {
tmpCards.insert(card->getName(), card);
}
}
@ -743,4 +656,46 @@ void CardInfo::resetReverseRelatedCards2Me()
cardRelation->deleteLater();
}
reverseRelatedCardsToMe = QList<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 <QSharedPointer>
#include <QStringList>
#include <QVariant>
#include <QVector>
#include <utility>
class CardDatabase;
class CardInfo;
class CardInfoPerSet;
class CardSet;
class CardRelation;
class ICardDatabaseParser;
@ -21,6 +24,7 @@ typedef QMap<QString, QString> QStringMap;
typedef QMap<QString, int> MuidMap;
typedef QSharedPointer<CardInfo> CardInfoPtr;
typedef QSharedPointer<CardSet> CardSetPtr;
typedef QMap<QString, CardInfoPerSet> CardInfoPerSetMap;
Q_DECLARE_METATYPE(CardInfoPtr)
@ -112,175 +116,162 @@ public:
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
{
Q_OBJECT
private:
CardInfoPtr smartThis;
// The card name
QString name;
/*
* The name without punctuation or capitalization, for better card tag name
* recognition.
*/
// The name without punctuation or capitalization, for better card name recognition.
QString simpleName;
bool isToken;
SetList sets;
QString manacost;
QString cmc;
QString cardtype;
QString powtough;
// The key used to identify this card in the cache
QString pixmapCacheKey;
// card text
QString text;
QStringList colors;
// whether this is not a "real" card but a token
bool isToken;
// basic card properties; common for all the sets
QVariantHash properties;
// the cards i'm related to
QList<CardRelation *> relatedCards;
// the card i'm reverse-related to
QList<CardRelation *> reverseRelatedCards;
// the cards thare are reverse-related to me
QList<CardRelation *> reverseRelatedCardsToMe;
// card sets
CardInfoPerSetMap sets;
// cached set names
QString setsNames;
bool upsideDownArt;
QString loyalty;
QStringMap customPicURLs;
MuidMap muIds;
QStringMap collectorNumbers;
QStringMap rarities;
// positioning properties; used by UI
bool cipt;
int tableRow;
QString pixmapCacheKey;
bool upsideDownArt;
public:
explicit CardInfo(const QString &_name = QString(),
bool _isToken = false,
const QString &_manacost = QString(),
const QString &_cmc = QString(),
const QString &_cardtype = QString(),
const QString &_powtough = QString(),
const QString &_text = QString(),
const QStringList &_colors = QStringList(),
bool _isToken = false,
QVariantHash _properties = QVariantHash(),
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
bool _upsideDownArt = false,
const QString &_loyalty = QString(),
CardInfoPerSetMap _sets = CardInfoPerSetMap(),
bool _cipt = false,
int _tableRow = 0,
const SetList &_sets = SetList(),
const QStringMap &_customPicURLs = QStringMap(),
MuidMap muids = MuidMap(),
QStringMap _collectorNumbers = QStringMap(),
QStringMap _rarities = QStringMap());
bool _upsideDownArt = false);
~CardInfo() override;
static CardInfoPtr newInstance(const QString &_name = QString(),
bool _isToken = false,
const QString &_manacost = QString(),
const QString &_cmc = QString(),
const QString &_cardtype = QString(),
const QString &_powtough = QString(),
const QString &_text = QString(),
const QStringList &_colors = QStringList(),
bool _isToken = false,
QVariantHash _properties = QVariantHash(),
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
bool _upsideDownArt = false,
const QString &_loyalty = QString(),
CardInfoPerSetMap _sets = CardInfoPerSetMap(),
bool _cipt = false,
int _tableRow = 0,
const SetList &_sets = SetList(),
const QStringMap &_customPicURLs = QStringMap(),
MuidMap muids = MuidMap(),
QStringMap _collectorNumbers = QStringMap(),
QStringMap _rarities = QStringMap());
bool _upsideDownArt = false);
void setSmartPointer(CardInfoPtr _ptr)
{
smartThis = _ptr;
smartThis = std::move(_ptr);
}
// basic properties
inline const QString &getName() const
{
return name;
}
inline const QString &getSetsNames() const
{
return setsNames;
}
const QString &getSimpleName() const
{
return simpleName;
}
bool getIsToken() const
{
return isToken;
}
const SetList &getSets() const
{
return sets;
}
inline const QString &getManaCost() const
{
return manacost;
}
inline const QString &getCmc() const
{
return cmc;
}
inline const QString &getCardType() const
{
return cardtype;
}
inline const QString &getPowTough() const
{
return powtough;
}
const QString &getText() const
{
return text;
}
const QString &getPixmapCacheKey() const
{
return pixmapCacheKey;
}
const QString &getLoyalty() const
const QString &getText() const
{
return loyalty;
}
bool getCipt() const
{
return cipt;
}
// void setManaCost(const QString &_manaCost) { manacost = _manaCost; emit cardInfoChanged(smartThis); }
// void setCmc(const QString &_cmc) { cmc = _cmc; emit cardInfoChanged(smartThis); }
void setCardType(const QString &_cardType)
{
cardtype = _cardType;
emit cardInfoChanged(smartThis);
}
void setPowTough(const QString &_powTough)
{
powtough = _powTough;
emit cardInfoChanged(smartThis);
return text;
}
void setText(const QString &_text)
{
text = _text;
emit cardInfoChanged(smartThis);
}
void setColors(const QStringList &_colors)
bool getIsToken() const
{
colors = _colors;
return isToken;
}
const QStringList getProperties() const
{
return properties.keys();
}
const QString getProperty(const QString &propertyName) const
{
return properties.value(propertyName).toString();
}
void setProperty(const QString &_name, const QString &_value)
{
properties.insert(_name, _value);
emit cardInfoChanged(smartThis);
}
const QChar getColorChar() const;
const QStringList &getColors() const
const CardInfoPerSetMap &getSets() const
{
return colors;
return sets;
}
const QString &getSetsNames() const
{
return setsNames;
}
const QString getSetProperty(const QString &setName, const QString &propertyName) const
{
if (!sets.contains(setName))
return "";
return sets[setName].getProperty(propertyName);
}
void setSetProperty(const QString &setName, const QString &_name, const QString &_value)
{
if (!sets.contains(setName))
return;
sets[setName].setProperty(_name, _value);
emit cardInfoChanged(smartThis);
}
// related cards
const QList<CardRelation *> &getRelatedCards() const
{
return relatedCards;
@ -298,32 +289,12 @@ public:
{
reverseRelatedCardsToMe.append(cardRelation);
}
bool getUpsideDownArt() const
// positioning
bool getCipt() const
{
return upsideDownArt;
return cipt;
}
QString getCustomPicURL(const QString &set) const
{
return customPicURLs.value(set);
}
int getMuId(const QString &set) const
{
return muIds.value(set);
}
QString getCollectorNumber(const QString &set) const
{
return collectorNumbers.value(set);
}
QString getRarity(const QString &set) const
{
return rarities.value(set);
}
QStringMap getRarities() const
{
return rarities;
}
QString getMainCardType() const;
QString getCorrectedName() const;
int getTableRow() const
{
return tableRow;
@ -332,27 +303,31 @@ public:
{
tableRow = _tableRow;
}
// void setLoyalty(int _loyalty) { loyalty = _loyalty; emit cardInfoChanged(smartThis); }
// void setCustomPicURL(const QString &_set, const QString &_customPicURL) { customPicURLs.insert(_set,
// _customPicURL); }
void setSet(const CardSetPtr &_set)
bool getUpsideDownArt() const
{
sets.append(_set);
refreshCachedSetNames();
return upsideDownArt;
}
void setMuId(const QString &_set, const int &_muId)
const QChar getColorChar() const;
// Back-compatibility methods. Remove ASAP
const QString getCardType() const;
void setCardType(const QString &value);
const QString getCmc() const;
const QString getColors() const;
void setColors(const QString &value);
const QString getLoyalty() const;
const QString getMainCardType() const;
const QString getManaCost() const;
const QString getPowTough() const;
void setPowTough(const QString &value);
// methods using per-set properties
QString getCustomPicURL(const QString &set) const
{
muIds.insert(_set, _muId);
return getSetProperty(set, "picurl");
}
void setSetNumber(const QString &_set, const QString &_setNumber)
{
collectorNumbers.insert(_set, _setNumber);
}
void setRarity(const QString &_set, const QString &_setNumber)
{
rarities.insert(_set, _setNumber);
}
void addToSet(CardSetPtr set);
QString getCorrectedName() const;
void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet());
void emitPixmapUpdated()
{
emit pixmapUpdated();
@ -439,7 +414,6 @@ public:
SetList getSetList() const;
LoadStatus loadFromFile(const QString &fileName);
bool saveCustomTokensToFile();
QStringList getAllColors() const;
QStringList getAllMainCardTypes() const;
LoadStatus getLoadStatus() const
{
@ -506,4 +480,4 @@ public:
return defaultCount;
}
};
#endif
#endif

View File

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

View File

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

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
{
public:
virtual ~ICardDatabaseParser()
{
}
~ICardDatabaseParser() override = default;
virtual bool getCanParseFile(const QString &name, QIODevice &device) = 0;
virtual void parseFile(QIODevice &device) = 0;
virtual bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName) = 0;
virtual void clearSetlist() = 0;
static void clearSetlist();
protected:
/*
* A cached list of the available sets, needed to cross-reference sets from cards.
* Shared between all parsers
*/
static SetNameMap sets;
CardSetPtr internalAddSet(const QString &setName,
const QString &longName = "",
const QString &setType = "",
const QDate &releaseDate = QDate());
signals:
virtual void addCard(CardInfoPtr card) = 0;
virtual void addSet(CardSetPtr set) = 0;
};
Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser")

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

View File

@ -11,27 +11,18 @@ class CockatriceXml3Parser : public ICardDatabaseParser
Q_INTERFACES(ICardDatabaseParser)
public:
CockatriceXml3Parser() = default;
~CockatriceXml3Parser() = default;
bool getCanParseFile(const QString &name, QIODevice &device);
void parseFile(QIODevice &device);
bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName);
void clearSetlist();
~CockatriceXml3Parser() override = default;
bool getCanParseFile(const QString &name, QIODevice &device) override;
void parseFile(QIODevice &device) override;
bool saveToFile(SetNameMap sets, CardNameMap cards, const QString &fileName) override;
private:
/*
* A cached list of the available sets, needed to cross-reference sets from cards.
*/
SetNameMap sets;
CardSetPtr internalAddSet(const QString &setName,
const QString &longName = "",
const QString &setType = "",
const QDate &releaseDate = QDate());
void loadCardsFromXml(QXmlStreamReader &xml);
void loadSetsFromXml(QXmlStreamReader &xml);
QString getMainCardType(QString &type);
signals:
void addCard(CardInfoPtr card);
void addSet(CardSetPtr set);
void addCard(CardInfoPtr card) override;
void addSet(CardSetPtr set) override;
};
#endif

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

View File

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

View File

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

View File

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

View File

@ -1,137 +1,83 @@
#include "cardinfotext.h"
#include "carditem.h"
#include "game_specific_terms.h"
#include "main.h"
#include <QGridLayout>
#include <QLabel>
#include <QTextEdit>
CardInfoText::CardInfoText(QWidget *parent) : QFrame(parent), info(nullptr)
{
nameLabel1 = new QLabel;
nameLabel2 = new QLabel;
nameLabel2->setWordWrap(true);
nameLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction);
manacostLabel1 = new QLabel;
manacostLabel2 = new QLabel;
manacostLabel2->setWordWrap(true);
manacostLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction);
colorLabel1 = new QLabel;
colorLabel2 = new QLabel;
colorLabel2->setWordWrap(true);
colorLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction);
cardtypeLabel1 = new QLabel;
cardtypeLabel2 = new QLabel;
cardtypeLabel2->setWordWrap(true);
cardtypeLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction);
powtoughLabel1 = new QLabel;
powtoughLabel2 = new QLabel;
powtoughLabel2->setTextInteractionFlags(Qt::TextBrowserInteraction);
loyaltyLabel1 = new QLabel;
loyaltyLabel2 = new QLabel;
loyaltyLabel1->setTextInteractionFlags(Qt::TextBrowserInteraction);
nameLabel = new QLabel;
nameLabel->setOpenExternalLinks(false);
connect(nameLabel, SIGNAL(linkActivated(const QString &)), this, SIGNAL(linkActivated(const QString &)));
textLabel = new QTextEdit();
textLabel->setReadOnly(true);
QGridLayout *grid = new QGridLayout(this);
int row = 0;
grid->addWidget(nameLabel1, row, 0);
grid->addWidget(nameLabel2, row++, 1);
grid->addWidget(manacostLabel1, row, 0);
grid->addWidget(manacostLabel2, row++, 1);
grid->addWidget(colorLabel1, row, 0);
grid->addWidget(colorLabel2, row++, 1);
grid->addWidget(cardtypeLabel1, row, 0);
grid->addWidget(cardtypeLabel2, row++, 1);
grid->addWidget(powtoughLabel1, row, 0);
grid->addWidget(powtoughLabel2, row++, 1);
grid->addWidget(loyaltyLabel1, row, 0);
grid->addWidget(loyaltyLabel2, row++, 1);
grid->addWidget(textLabel, row, 0, -1, 2);
grid->setRowStretch(row, 1);
grid->addWidget(nameLabel, 0, 0);
grid->addWidget(textLabel, 1, 0, -1, 2);
grid->setRowStretch(1, 1);
grid->setColumnStretch(1, 1);
retranslateUi();
}
// Reset every label which is optionally hidden
void CardInfoText::resetLabels()
{
nameLabel1->show();
nameLabel2->show();
manacostLabel1->show();
manacostLabel2->show();
colorLabel1->show();
colorLabel2->show();
cardtypeLabel1->show();
cardtypeLabel2->show();
powtoughLabel1->show();
powtoughLabel2->show();
loyaltyLabel1->show();
loyaltyLabel2->show();
textLabel->show();
}
void CardInfoText::setCard(CardInfoPtr card)
{
if (card) {
resetLabels();
nameLabel2->setText(card->getName());
if (!card->getManaCost().isEmpty()) {
manacostLabel2->setText(card->getManaCost());
} else {
manacostLabel1->hide();
manacostLabel2->hide();
}
if (!card->getColors().isEmpty()) {
colorLabel2->setText(card->getColors().join(""));
} else {
colorLabel2->setText("Colorless");
}
cardtypeLabel2->setText(card->getCardType());
if (!card->getPowTough().isEmpty()) {
powtoughLabel2->setText(card->getPowTough());
} else {
powtoughLabel1->hide();
powtoughLabel2->hide();
}
if (!card->getLoyalty().isEmpty()) {
loyaltyLabel2->setText(card->getLoyalty());
} else {
loyaltyLabel1->hide();
loyaltyLabel2->hide();
}
textLabel->setText(card->getText());
} else {
nameLabel1->hide();
nameLabel2->hide();
manacostLabel1->hide();
manacostLabel2->hide();
colorLabel1->hide();
colorLabel2->hide();
cardtypeLabel1->hide();
cardtypeLabel2->hide();
powtoughLabel1->hide();
powtoughLabel2->hide();
loyaltyLabel1->hide();
loyaltyLabel2->hide();
textLabel->hide();
if (card == nullptr) {
nameLabel->setText("");
textLabel->setText("");
return;
}
QString text = "<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)
{
nameLabel1->setText(tr("Unknown card:"));
nameLabel1->show();
nameLabel2->setText(cardName);
nameLabel2->show();
nameLabel->setText(tr("Unknown card:") + " " + cardName);
textLabel->setText("");
}
void CardInfoText::retranslateUi()
{
nameLabel1->setText(tr("Name:"));
manacostLabel1->setText(tr("Mana cost:"));
colorLabel1->setText(tr("Color(s):"));
cardtypeLabel1->setText(tr("Card type:"));
powtoughLabel1->setText(tr("P / T:"));
loyaltyLabel1->setText(tr("Loyalty:"));
/*
* There's no way we can really translate the text currently being rendered.
* The best we can do is invalidate the current text.
*/
setInvalidCardName("");
}

View File

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

View File

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

View File

@ -22,7 +22,7 @@ private:
CardInfoText *text;
public:
CardInfoWidget(const QString &cardName, QWidget *parent = 0, Qt::WindowFlags f = 0);
explicit CardInfoWidget(const QString &cardName, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr);
public slots:
void setCard(CardInfoPtr card);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,8 +45,6 @@ GeneralSettingsPage::GeneralSettingsPage()
languageBox.setCurrentIndex(i);
}
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
// updates
QList<ReleaseChannel *> channels = settingsCache->getUpdateReleaseChannels();
foreach (ReleaseChannel *chan, channels) {
@ -55,6 +53,7 @@ GeneralSettingsPage::GeneralSettingsPage()
updateReleaseChannelBox.setCurrentIndex(settingsCache->getUpdateReleaseChannel()->getIndex());
updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates());
newVersionOracleCheckBox.setChecked(settingsCache->getNotifyAboutNewVersion());
// pixmap cache
pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
@ -64,27 +63,16 @@ GeneralSettingsPage::GeneralSettingsPage()
pixmapCacheEdit.setValue(settingsCache->getPixmapCacheSize());
pixmapCacheEdit.setSuffix(" MB");
defaultUrlEdit = new QLineEdit(settingsCache->getPicUrl());
fallbackUrlEdit = new QLineEdit(settingsCache->getPicUrlFallback());
showTipsOnStartup.setChecked(settingsCache->getShowTipsOnStartup());
connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked()));
connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int)));
connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int)));
connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int)));
connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), settingsCache,
SLOT(setUpdateReleaseChannel(int)));
connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int)));
connect(&picDownloadCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool)));
connect(defaultUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrl(QString)));
connect(fallbackUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlFallback(QString)));
connect(&defaultUrlRestoreButton, SIGNAL(clicked()), this, SLOT(defaultUrlRestoreButtonClicked()));
connect(&fallbackUrlRestoreButton, SIGNAL(clicked()), this, SLOT(fallbackUrlRestoreButtonClicked()));
connect(&newVersionOracleCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutNewVersion(int)));
connect(&showTipsOnStartup, SIGNAL(clicked(bool)), settingsCache, SLOT(setShowTipsOnStartup(bool)));
setEnabledStatus(settingsCache->getPicDownload());
auto *personalGrid = new QGridLayout;
personalGrid->addWidget(&languageLabel, 0, 0);
personalGrid->addWidget(&languageBox, 0, 1);
@ -93,19 +81,8 @@ GeneralSettingsPage::GeneralSettingsPage()
personalGrid->addWidget(&pixmapCacheLabel, 2, 0);
personalGrid->addWidget(&pixmapCacheEdit, 2, 1);
personalGrid->addWidget(&updateNotificationCheckBox, 3, 0);
personalGrid->addWidget(&showTipsOnStartup, 4, 0);
personalGrid->addWidget(&picDownloadCheckBox, 5, 0);
personalGrid->addWidget(&urlLinkLabel, 5, 1);
personalGrid->addWidget(&defaultUrlLabel, 6, 0, 1, 1);
personalGrid->addWidget(defaultUrlEdit, 6, 1, 1, 1);
personalGrid->addWidget(&defaultUrlRestoreButton, 6, 2, 1, 1);
personalGrid->addWidget(&fallbackUrlLabel, 7, 0, 1, 1);
personalGrid->addWidget(fallbackUrlEdit, 7, 1, 1, 1);
personalGrid->addWidget(&fallbackUrlRestoreButton, 7, 2, 1, 1);
personalGrid->addWidget(&clearDownloadedPicsButton, 8, 1);
urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
urlLinkLabel.setOpenExternalLinks(true);
personalGrid->addWidget(&newVersionOracleCheckBox, 4, 0);
personalGrid->addWidget(&showTipsOnStartup, 5, 0);
personalGroupBox = new QGroupBox;
personalGroupBox->setLayout(personalGrid);
@ -194,20 +171,6 @@ QString GeneralSettingsPage::languageName(const QString &qmFile)
return translator.translate("i18n", DEFAULT_LANG_NAME);
}
void GeneralSettingsPage::defaultUrlRestoreButtonClicked()
{
QString path = PIC_URL_DEFAULT;
defaultUrlEdit->setText(path);
settingsCache->setPicUrl(path);
}
void GeneralSettingsPage::fallbackUrlRestoreButtonClicked()
{
QString path = PIC_URL_FALLBACK;
fallbackUrlEdit->setText(path);
settingsCache->setPicUrlFallback(path);
}
void GeneralSettingsPage::deckPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"));
@ -238,30 +201,6 @@ void GeneralSettingsPage::picsPathButtonClicked()
settingsCache->setPicsPath(path);
}
void GeneralSettingsPage::clearDownloadedPicsButtonClicked()
{
QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/";
QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
bool outerSuccessRemove = true;
for (int i = 0; i < dirs.length(); i++) {
QString currentPath = picsPath + dirs.at(i) + "/";
QStringList files = QDir(currentPath).entryList(QDir::Files);
bool innerSuccessRemove = true;
for (int j = 0; j < files.length(); j++)
if (!QDir(currentPath).remove(files.at(j))) {
qDebug() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8();
outerSuccessRemove = false;
innerSuccessRemove = false;
}
if (innerSuccessRemove)
QDir(picsPath).rmdir(dirs.at(i));
}
if (outerSuccessRemove)
QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset."));
else
QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared."));
}
void GeneralSettingsPage::cardDatabasePathButtonClicked()
{
QString path = QFileDialog::getOpenFileName(this, tr("Choose path"));
@ -291,7 +230,6 @@ void GeneralSettingsPage::retranslateUi()
{
personalGroupBox->setTitle(tr("Personal settings"));
languageLabel.setText(tr("Language:"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
if (settingsCache->getIsPortableBuild()) {
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
@ -305,26 +243,12 @@ void GeneralSettingsPage::retranslateUi()
cardDatabasePathLabel.setText(tr("Card database:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
pixmapCacheLabel.setText(tr("Picture cache size:"));
defaultUrlLabel.setText(tr("Primary download URL:"));
fallbackUrlLabel.setText(tr("Fallback download URL:"));
urlLinkLabel.setText(
QString("<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"));
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
defaultUrlRestoreButton.setText(tr("Reset"));
fallbackUrlRestoreButton.setText(tr("Reset"));
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
showTipsOnStartup.setText(tr("Show tips on startup"));
}
void GeneralSettingsPage::setEnabledStatus(bool status)
{
defaultUrlEdit->setEnabled(status);
fallbackUrlEdit->setEnabled(status);
defaultUrlRestoreButton.setEnabled(status);
fallbackUrlRestoreButton.setEnabled(status);
}
AppearanceSettingsPage::AppearanceSettingsPage()
{
QString themeName = settingsCache->getThemeName();
@ -498,6 +422,15 @@ void UserInterfaceSettingsPage::retranslateUi()
DeckEditorSettingsPage::DeckEditorSettingsPage()
{
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int)));
urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
urlLinkLabel.setOpenExternalLinks(true);
connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked()));
connect(&resetDownloadURLs, SIGNAL(clicked()), this, SLOT(resetDownloadedURLsButtonClicked()));
auto *lpGeneralGrid = new QGridLayout;
auto *lpSpoilerGrid = new QGridLayout;
@ -515,9 +448,46 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
// Update the GUI depending on if the box is ticked or not
setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked());
// Create the layout
lpGeneralGrid->addWidget(&mcGeneralMessageLabel, 0, 0);
urlList = new QListWidget;
urlList->setSelectionMode(QAbstractItemView::SingleSelection);
urlList->setAlternatingRowColors(true);
urlList->setDragEnabled(true);
urlList->setDragDropMode(QAbstractItemView::InternalMove);
connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this,
SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int)));
for (int i = 0; i < settingsCache->downloads().getCount(); i++)
urlList->addItem(settingsCache->downloads().getDownloadUrlAt(i));
auto aAdd = new QAction(this);
aAdd->setIcon(QPixmap("theme:icons/increment"));
connect(aAdd, SIGNAL(triggered()), this, SLOT(actAddURL()));
auto aEdit = new QAction(this);
aEdit->setIcon(QPixmap("theme:icons/pencil"));
connect(aEdit, SIGNAL(triggered()), this, SLOT(actEditURL()));
auto aRemove = new QAction(this);
aRemove->setIcon(QPixmap("theme:icons/decrement"));
connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL()));
auto *messageToolBar = new QToolBar;
messageToolBar->setOrientation(Qt::Vertical);
messageToolBar->addAction(aAdd);
messageToolBar->addAction(aRemove);
messageToolBar->addAction(aEdit);
messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
auto *messageListLayout = new QHBoxLayout;
messageListLayout->addWidget(messageToolBar);
messageListLayout->addWidget(urlList);
// Top Layout
lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0);
lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1);
lpGeneralGrid->addLayout(messageListLayout, 1, 0, 1, 2);
lpGeneralGrid->addWidget(&urlLinkLabel, 2, 0);
lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 2, 1);
// Spoiler Layout
lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0);
lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0);
lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1);
@ -543,6 +513,94 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
setLayout(lpMainLayout);
}
void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked()
{
settingsCache->downloads().clear();
urlList->clear();
urlList->addItems(settingsCache->downloads().getAllURLs());
QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset."));
}
void DeckEditorSettingsPage::clearDownloadedPicsButtonClicked()
{
QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/";
QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
bool outerSuccessRemove = true;
for (const auto &dir : dirs) {
QString currentPath = picsPath + dir + "/";
QStringList files = QDir(currentPath).entryList(QDir::Files);
bool innerSuccessRemove = true;
for (int j = 0; j < files.length(); j++) {
if (!QDir(currentPath).remove(files.at(j))) {
qInfo() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8();
outerSuccessRemove = false;
innerSuccessRemove = false;
}
qInfo() << "Removed " << currentPath << files.at(j);
}
if (innerSuccessRemove) {
bool success = QDir(picsPath).rmdir(dir);
if (!success) {
qInfo() << "Failed to remove inner directory" << picsPath;
} else {
qInfo() << "Removed" << currentPath;
}
}
}
if (outerSuccessRemove) {
QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset."));
} else {
QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared."));
}
}
void DeckEditorSettingsPage::actAddURL()
{
bool ok;
QString msg = QInputDialog::getText(this, tr("Add URL"), tr("URL:"), QLineEdit::Normal, QString(), &ok);
if (ok) {
urlList->addItem(msg);
storeSettings();
}
}
void DeckEditorSettingsPage::actRemoveURL()
{
if (urlList->currentItem() != nullptr) {
delete urlList->takeItem(urlList->currentRow());
storeSettings();
}
}
void DeckEditorSettingsPage::actEditURL()
{
if (urlList->currentItem()) {
QString oldText = urlList->currentItem()->text();
bool ok;
QString msg = QInputDialog::getText(this, tr("Edit URL"), tr("URL:"), QLineEdit::Normal, oldText, &ok);
if (ok) {
urlList->currentItem()->setText(msg);
storeSettings();
}
}
}
void DeckEditorSettingsPage::storeSettings()
{
qInfo() << "URL Priority Reset";
settingsCache->downloads().clear();
for (int i = 0; i < urlList->count(); i++) {
qInfo() << "Priority" << i << ":" << urlList->item(i)->text();
settingsCache->downloads().setDownloadUrlAt(i, urlList->item(i)->text());
}
}
void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int)
{
storeSettings();
}
void DeckEditorSettingsPage::updateSpoilers()
{
// Disable the button so the user can only press it once at a time
@ -603,14 +661,18 @@ void DeckEditorSettingsPage::setSpoilersEnabled(bool anInput)
void DeckEditorSettingsPage::retranslateUi()
{
mpGeneralGroupBox->setTitle(tr("URL Download Priority"));
mpSpoilerGroupBox->setTitle(tr("Spoilers"));
mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically"));
mcSpoilerSaveLabel.setText(tr("Spoiler Location:"));
mcGeneralMessageLabel.setText(tr("Hey, something's here finally!"));
lastUpdatedLabel.setText(tr("Last Updated") + ": " + getLastUpdateTime());
infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" +
tr("Press the button to manually update without relaunching") + "\n\n" +
tr("Do not close settings until manual update complete"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
urlLinkLabel.setText(QString("<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()
@ -649,7 +711,7 @@ MessagesSettingsPage::MessagesSettingsPage()
connect(&roomHistory, SIGNAL(stateChanged(int)), settingsCache, SLOT(setRoomHistory(int)));
customAlertString = new QLineEdit();
customAlertString->setPlaceholderText("Word1 Word2 Word3");
customAlertString->setPlaceholderText(tr("Word1 Word2 Word3"));
customAlertString->setText(settingsCache->getHighlightWords());
connect(customAlertString, SIGNAL(textChanged(QString)), settingsCache, SLOT(setHighlightWords(QString)));
@ -689,12 +751,16 @@ MessagesSettingsPage::MessagesSettingsPage()
aAdd = new QAction(this);
aAdd->setIcon(QPixmap("theme:icons/increment"));
aAdd->setStatusTip(tr("Add New URL"));
connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd()));
aEdit = new QAction(this);
aEdit->setIcon(QPixmap("theme:icons/pencil"));
aEdit->setStatusTip(tr("Edit URL"));
connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit()));
aRemove = new QAction(this);
aRemove->setIcon(QPixmap("theme:icons/decrement"));
aRemove->setStatusTip(tr("Remove URL"));
connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove()));
auto *messageToolBar = new QToolBar;
@ -798,7 +864,7 @@ void MessagesSettingsPage::actEdit()
void MessagesSettingsPage::actRemove()
{
if (messageList->currentItem()) {
if (messageList->currentItem() != nullptr) {
delete messageList->takeItem(messageList->currentRow());
storeSettings();
}
@ -1000,7 +1066,7 @@ void DlgSettings::setTab(int index)
void DlgSettings::updateLanguage()
{
qApp->removeTranslator(translator);
qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
installNewTranslator();
}
@ -1094,7 +1160,7 @@ void DlgSettings::retranslateUi()
generalButton->setText(tr("General"));
appearanceButton->setText(tr("Appearance"));
userInterfaceButton->setText(tr("User Interface"));
deckEditorButton->setText(tr("Deck Editor"));
deckEditorButton->setText(tr("Card Sources"));
messagesButton->setText(tr("Chat"));
soundButton->setText(tr("Sound"));
shortcutsButton->setText(tr("Shortcuts"));

View File

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

View File

@ -13,7 +13,7 @@
#define MIN_TIP_IMAGE_HEIGHT 200
#define MIN_TIP_IMAGE_WIDTH 200
#define MAX_TIP_IMAGE_HEIGHT 300
#define MAX_TIP_IMAGE_WIDTH 300
#define MAX_TIP_IMAGE_WIDTH 500
DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent)
{
@ -149,9 +149,9 @@ void DlgTipOfTheDay::updateTip(int tipId)
qDebug() << "Image failed to load from" << imagePath;
imageLabel->clear();
} else {
int h = std::min(std::max(image->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT);
int h = std::min(std::max(imageLabel->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT);
int w = std::min(std::max(imageLabel->width(), MIN_TIP_IMAGE_WIDTH), MAX_TIP_IMAGE_WIDTH);
imageLabel->setPixmap(image->scaled(h, w, Qt::KeepAspectRatio, Qt::SmoothTransformation));
imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
date->setText("<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)
{
int h = imageLabel->height();
int w = imageLabel->width();
imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
imageLabel->setPixmap(image->scaled(imageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
QWidget::resizeEvent(event);
}

View File

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

View File

@ -187,11 +187,8 @@ bool FilterItem::acceptColor(const CardInfoPtr info) const
*/
int match_count = 0;
for (auto &it : converted_term) {
for (auto i = info->getColors().constBegin(); i != info->getColors().constEnd(); i++) {
if ((*i).contains(it, Qt::CaseInsensitive)) {
match_count++;
}
}
if (info->getColors().contains(it, Qt::CaseInsensitive))
match_count++;
}
return match_count == converted_term.length();
@ -205,9 +202,9 @@ bool FilterItem::acceptText(const CardInfoPtr info) const
bool FilterItem::acceptSet(const CardInfoPtr info) const
{
bool status = false;
for (auto i = info->getSets().constBegin(); i != info->getSets().constEnd(); i++) {
if ((*i)->getShortName().compare(term, Qt::CaseInsensitive) == 0 ||
(*i)->getLongName().compare(term, Qt::CaseInsensitive) == 0) {
for (const auto &set : info->getSets()) {
if (set.getPtr()->getShortName().compare(term, Qt::CaseInsensitive) == 0 ||
set.getPtr()->getLongName().compare(term, Qt::CaseInsensitive) == 0) {
status = true;
break;
}
@ -299,7 +296,7 @@ bool FilterItem::acceptRarity(const CardInfoPtr info) const
/*
* The purpose of this loop is to only apply one of the replacement
* policies and then escape. If we attempt to layer them ontop of
* policies and then escape. If we attempt to layer them on top of
* each other, we will get awkward results (i.e. comythic rare mythic rareon)
* Conditional statement will exit once a case is successful in
* replacement OR we go through all possible cases.
@ -334,8 +331,8 @@ bool FilterItem::acceptRarity(const CardInfoPtr info) const
}
}
for (const QString &rareLevel : info->getRarities()) {
if (rareLevel.compare(converted_term, Qt::CaseInsensitive) == 0) {
for (const auto &set : info->getSets()) {
if (set.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) {
return true;
}
}

View File

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

View File

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

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;
addItem(player);
connect(player, SIGNAL(sizeChanged()), this, SLOT(rearrange()));
connect(player, SIGNAL(gameConceded()), this, SLOT(rearrange()));
connect(player, SIGNAL(playerCountChanged()), this, SLOT(rearrange()));
}
void GameScene::removePlayer(Player *player)

View File

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

View File

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

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)
{
appendHtmlServerMessage(QString("%1 attaches %2 to %3's %4.")
appendHtmlServerMessage(tr("%1 attaches %2 to %3's %4.")
.arg(sanitizeHtml(player->getName()))
.arg(cardLink(cardName))
.arg(sanitizeHtml(targetPlayer->getName()))
@ -169,6 +169,12 @@ void MessageLogWidget::logConcede(Player *player)
appendHtmlServerMessage(tr("%1 has conceded the game.").arg(sanitizeHtml(player->getName())), true);
}
void MessageLogWidget::logUnconcede(Player *player)
{
soundEngine->playSound("player_concede");
appendHtmlServerMessage(tr("%1 has unconceded the game.").arg(sanitizeHtml(player->getName())), true);
}
void MessageLogWidget::logConnectionStateChanged(Player *player, bool connectionState)
{
if (connectionState) {
@ -341,7 +347,7 @@ void MessageLogWidget::logDrawCards(Player *player, int number)
mulliganPlayer = player;
else {
soundEngine->playSound("draw_card");
appendHtmlServerMessage(tr("%1 draws %2 card(s).")
appendHtmlServerMessage(tr("%1 draws %2 card(s).", "", number)
.arg(sanitizeHtml(player->getName()))
.arg("<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(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone)));
else
appendHtmlServerMessage(tr("%1 is looking at the top %2 card(s) %3.")
.arg(sanitizeHtml(player->getName()))
.arg("<font color=\"blue\">" + QString::number(numberCards) + "</font>")
.arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone)));
appendHtmlServerMessage(
tr("%1 is looking at the top %3 card(s) %2.", "top card for singular, top %3 cards for plural", numberCards)
.arg(sanitizeHtml(player->getName()))
.arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone))
.arg("<font color=\"blue\">" + QString::number(numberCards) + "</font>"));
}
void MessageLogWidget::logFlipCard(Player *player, QString cardName, bool faceDown)
@ -456,9 +463,11 @@ void MessageLogWidget::logRevealCards(Player *player,
int cardId,
QString cardName,
Player *otherPlayer,
bool faceDown)
bool faceDown,
int amount)
{
QPair<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;
if (!temp.first.isEmpty()) {
cardNameContainsStartZone = true;
@ -467,52 +476,61 @@ void MessageLogWidget::logRevealCards(Player *player,
QString fromStr = temp.second;
QString cardStr;
if (cardNameContainsStartZone)
if (cardNameContainsStartZone) {
cardStr = cardName;
else if (cardName.isEmpty())
cardStr = tr("a card");
else
} else if (cardName.isEmpty()) {
if (amount == 0) {
cardStr = tr("cards", "an unknown amount of cards");
} else {
cardStr = tr("%1 card(s)", "a card for singular, %1 cards for plural", amount)
.arg("<font color=\"blue\">" + QString::number(amount) + "</font>");
}
} else {
cardStr = cardLink(cardName);
}
if (cardId == -1) {
if (otherPlayer)
if (otherPlayer) {
appendHtmlServerMessage(tr("%1 reveals %2 to %3.")
.arg(sanitizeHtml(player->getName()))
.arg(zone->getTranslatedName(true, CaseRevealZone))
.arg(sanitizeHtml(otherPlayer->getName())));
else
} else {
appendHtmlServerMessage(tr("%1 reveals %2.")
.arg(sanitizeHtml(player->getName()))
.arg(zone->getTranslatedName(true, CaseRevealZone)));
}
} else if (cardId == -2) {
if (otherPlayer)
if (otherPlayer) {
appendHtmlServerMessage(tr("%1 randomly reveals %2%3 to %4.")
.arg(sanitizeHtml(player->getName()))
.arg(cardStr)
.arg(fromStr)
.arg(sanitizeHtml(otherPlayer->getName())));
else
} else {
appendHtmlServerMessage(
tr("%1 randomly reveals %2%3.").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr));
}
} else {
if (faceDown && player == otherPlayer) {
if (cardName.isEmpty())
if (cardName.isEmpty()) {
appendHtmlServerMessage(
tr("%1 peeks at face down card #%2.").arg(sanitizeHtml(player->getName())).arg(cardId));
else
} else {
appendHtmlServerMessage(tr("%1 peeks at face down card #%2: %3.")
.arg(sanitizeHtml(player->getName()))
.arg(cardId)
.arg(cardStr));
} else if (otherPlayer)
}
} else if (otherPlayer) {
appendHtmlServerMessage(tr("%1 reveals %2%3 to %4.")
.arg(sanitizeHtml(player->getName()))
.arg(cardStr)
.arg(fromStr)
.arg(sanitizeHtml(otherPlayer->getName())));
else
} else {
appendHtmlServerMessage(
tr("%1 reveals %2%3.").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr));
}
}
}
@ -626,20 +644,20 @@ void MessageLogWidget::logSetCardCounter(Player *player, QString cardName, int c
QString finalStr;
int delta = abs(oldValue - value);
if (value > oldValue)
finalStr = tr("%1 places %2 %3 counter(s) on %4 (now %5).");
finalStr = tr("%1 places %2 %3 on %4 (now %5).");
else
finalStr = tr("%1 removes %2 %3 counter(s) from %4 (now %5).");
finalStr = tr("%1 removes %2 %3 from %4 (now %5).");
QString colorStr;
switch (counterId) {
case 0:
colorStr = tr("red", "", delta);
colorStr = tr("red counter(s)", "", delta);
break;
case 1:
colorStr = tr("yellow", "", delta);
colorStr = tr("yellow counter(s)", "", delta);
break;
case 2:
colorStr = tr("green", "", delta);
colorStr = tr("green counter(s)", "", delta);
break;
default:;
}
@ -676,13 +694,22 @@ void MessageLogWidget::logSetDoesntUntap(Player *player, CardItem *card, bool do
void MessageLogWidget::logSetPT(Player *player, CardItem *card, QString newPT)
{
if (currentContext == MessageContext_MoveCard)
if (currentContext == MessageContext_MoveCard) {
moveCardPT.insert(card, newPT);
else
appendHtmlServerMessage(tr("%1 sets PT of %2 to %3.")
.arg(sanitizeHtml(player->getName()))
.arg(cardLink(card->getName()))
.arg(QString("<font color=\"blue\">%1</font>").arg(sanitizeHtml(newPT))));
} else {
QString name = card->getName();
if (name.isEmpty()) {
name = QString("<font color=\"blue\">card #%1</font>").arg(sanitizeHtml(QString::number(card->getId())));
} else {
name = cardLink(name);
}
if (newPT.isEmpty()) {
appendHtmlServerMessage(tr("%1 removes the PT of %2.").arg(sanitizeHtml(player->getName())).arg(name));
} else {
appendHtmlServerMessage(
tr("%1 sets PT of %2 to %3.").arg(sanitizeHtml(player->getName())).arg(name).arg(newPT));
}
}
}
void MessageLogWidget::logSetSideboardLock(Player *player, bool locked)
@ -786,8 +813,8 @@ void MessageLogWidget::connectToPlayer(Player *player)
connect(player, SIGNAL(logStopDumpZone(Player *, CardZone *)), this, SLOT(logStopDumpZone(Player *, CardZone *)));
connect(player, SIGNAL(logDrawCards(Player *, int)), this, SLOT(logDrawCards(Player *, int)));
connect(player, SIGNAL(logUndoDraw(Player *, QString)), this, SLOT(logUndoDraw(Player *, QString)));
connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool)), this,
SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *, bool)));
connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int)), this,
SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int)));
connect(player, SIGNAL(logAlwaysRevealTopCard(Player *, CardZone *, bool)), this,
SLOT(logAlwaysRevealTopCard(Player *, CardZone *, bool)));
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,23 @@ class QThread;
class PictureToLoad
{
private:
class SetDownloadPriorityComparator;
class SetDownloadPriorityComparator
{
public:
/*
* Returns true if a has higher download priority than b
* Enabled sets have priority over disabled sets
* Both groups follows the user-defined order
*/
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
{
if (a->getEnabled()) {
return !b->getEnabled() || a->getSortKey() < b->getSortKey();
} else {
return !b->getEnabled() && a->getSortKey() < b->getSortKey();
}
}
};
CardInfoPtr card;
QList<CardSetPtr> sortedSets;
@ -24,7 +40,8 @@ private:
CardSetPtr currentSet;
public:
PictureToLoad(CardInfoPtr _card = CardInfoPtr());
explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr());
CardInfoPtr getCard() const
{
return card;
@ -42,7 +59,7 @@ public:
return currentSet;
}
QString getSetName() const;
QString transformUrl(QString urlTemplate) const;
QString transformUrl(const QString &urlTemplate) const;
bool nextSet();
bool nextUrl();
void populateSetUrls();
@ -52,8 +69,8 @@ class PictureLoaderWorker : public QObject
{
Q_OBJECT
public:
PictureLoaderWorker();
~PictureLoaderWorker();
explicit PictureLoaderWorker();
~PictureLoaderWorker() override;
void enqueueImageLoad(CardInfoPtr card);
@ -70,8 +87,8 @@ private:
PictureToLoad cardBeingDownloaded;
bool picDownload, downloadRunning, loadQueueRunning;
void startNextPicDownload();
bool cardImageExistsOnDisk(QString &setName, QString &correctedCardname);
bool imageIsBlackListed(const QByteArray &picData);
bool cardImageExistsOnDisk(QString &, QString &);
bool imageIsBlackListed(const QByteArray &);
private slots:
void picDownloadFinished(QNetworkReply *reply);
void picDownloadFailed();
@ -96,8 +113,8 @@ public:
}
private:
PictureLoader();
~PictureLoader();
explicit PictureLoader();
~PictureLoader() override;
// Singleton - Don't implement copy constructor and assign operator
PictureLoader(PictureLoader const &);
void operator=(PictureLoader const &);

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -4,19 +4,18 @@
#include <QStringList>
#include <utility>
ShortcutsSettings::ShortcutsSettings(QString settingsPath, QObject *parent) : QObject(parent)
ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *parent) : QObject(parent)
{
this->settingsFilePath = std::move(settingsPath);
this->settingsFilePath.append("shortcuts.ini");
fillDefaultShorcuts();
shortCuts = QMap<QString, QList<QKeySequence>>(defaultShortCuts);
shortCuts = defaultShortCuts;
settingsFilePath = settingsPath;
settingsFilePath.append("shortcuts.ini");
bool exists = QFile(settingsFilePath).exists();
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
if (exists) {
shortCutsFile.beginGroup("Custom");
shortCutsFile.beginGroup(custom);
const QStringList customKeys = shortCutsFile.allKeys();
QMap<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)) {
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;
for (int i = 0; i < Sequence.size(); ++i) {
stringSequence.append(Sequence.at(i).toString(QKeySequence::PortableText));
if (i < Sequence.size() - 1) {
stringSequence.append(";");
}
QStringList stringSequence;
for (const auto &i : Sequence) {
stringSequence.append(i.toString(QKeySequence::PortableText));
}
return stringSequence;
return stringSequence.join(sep);
}
QList<QKeySequence> ShortcutsSettings::parseSequenceString(QString stringSequence)
QList<QKeySequence> ShortcutsSettings::parseSequenceString(const QString &stringSequence) const
{
QStringList Sequences = stringSequence.split(";");
QList<QKeySequence> SequenceList;
for (QStringList::const_iterator ss = Sequences.constBegin(); ss != Sequences.constEnd(); ++ss) {
SequenceList.append(QKeySequence(*ss, QKeySequence::PortableText));
for (const QString &shortcut : stringSequence.split(sep)) {
SequenceList.append(QKeySequence(shortcut, QKeySequence::PortableText));
}
return SequenceList;
}
void ShortcutsSettings::setShortcuts(QString name, QList<QKeySequence> Sequence)
void ShortcutsSettings::setShortcuts(const QString &name, const QList<QKeySequence> &Sequence)
{
shortCuts[name] = Sequence;
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
shortCutsFile.beginGroup("Custom");
QString stringSequence = stringifySequence(Sequence);
shortCutsFile.setValue(name, stringSequence);
shortCutsFile.beginGroup(custom);
shortCutsFile.setValue(name, stringifySequence(Sequence));
shortCutsFile.endGroup();
emit shortCutchanged();
emit shortCutChanged();
}
void ShortcutsSettings::setShortcuts(QString name, QKeySequence Sequence)
void ShortcutsSettings::setShortcuts(const QString &name, const QKeySequence &Sequence)
{
setShortcuts(std::move(name), QList<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 (name.startsWith("Player")) {
return true;
}
QString checkSequence = Sequences.split(";").last();
QStringList forbiddenKeys = (QStringList() << "Del"
<< "Backspace"
<< "Down"
<< "Up"
<< "Left"
<< "Right"
<< "Return"
<< "Enter"
<< "Menu"
<< "Ctrl+Alt+-"
<< "Ctrl+Alt+="
<< "Ctrl+Alt+["
<< "Ctrl+Alt+]"
<< "Tab"
<< "Space"
<< "Shift+S"
<< "Shift+Left"
<< "Shift+Right");
QString checkSequence = Sequences.split(sep).last();
QStringList forbiddenKeys{"Del", "Backspace", "Down", "Up", "Left", "Right",
"Return", "Enter", "Menu", "Ctrl+Alt+-", "Ctrl+Alt+=", "Ctrl+Alt+[",
"Ctrl+Alt+]", "Tab", "Space", "Shift+S", "Shift+Left", "Shift+Right"};
return !forbiddenKeys.contains(checkSequence);
}
bool ShortcutsSettings::isValid(QString name, QString Sequences)
bool ShortcutsSettings::isValid(const QString &name, const QString &Sequences) const
{
QString checkSequence = Sequences.split(";").last();
QString checkSequence = Sequences.split(sep).last();
QString checkKey = name.left(name.indexOf("/"));
QList<QString> allKeys = shortCuts.keys();
for (const auto &key : allKeys) {
if (key.startsWith(checkKey) || key.startsWith("MainWindow") || checkKey.startsWith("MainWindow")) {
QString storedSequence = stringifySequence(shortCuts.value(key));
QStringList stringSequences = storedSequence.split(";");
QStringList stringSequences = storedSequence.split(sep);
if (stringSequences.contains(checkSequence)) {
return false;
}
@ -170,161 +178,3 @@ bool ShortcutsSettings::isValid(QString name, QString Sequences)
}
return true;
}
void ShortcutsSettings::resetAllShortcuts()
{
for (auto it = defaultShortCuts.begin(); it != defaultShortCuts.end(); ++it) {
setShortcuts(it.key(), it.value());
}
emit allShortCutsReset();
}
void ShortcutsSettings::clearAllShortcuts()
{
for (auto it = shortCuts.begin(); it != shortCuts.end(); ++it) {
setShortcuts(it.key(), "");
}
emit allShortCutsClear();
}
void ShortcutsSettings::fillDefaultShorcuts()
{
defaultShortCuts["MainWindow/aCheckCardUpdates"] = parseSequenceString("");
defaultShortCuts["MainWindow/aConnect"] = parseSequenceString("Ctrl+L");
defaultShortCuts["MainWindow/aDeckEditor"] = parseSequenceString("");
defaultShortCuts["MainWindow/aDisconnect"] = parseSequenceString("");
defaultShortCuts["MainWindow/aExit"] = parseSequenceString("");
defaultShortCuts["MainWindow/aFullScreen"] = parseSequenceString("Ctrl+F");
defaultShortCuts["MainWindow/aRegister"] = parseSequenceString("");
defaultShortCuts["MainWindow/aSettings"] = parseSequenceString("");
defaultShortCuts["MainWindow/aSinglePlayer"] = parseSequenceString("");
defaultShortCuts["MainWindow/aWatchReplay"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aAnalyzeDeck"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aClearFilterAll"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aClearFilterOne"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aClose"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aDecrement"] = parseSequenceString("-");
defaultShortCuts["TabDeckEditor/aManageSets"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aEditTokens"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aExportDeckDecklist"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aIncrement"] = parseSequenceString("+");
defaultShortCuts["TabDeckEditor/aLoadDeck"] = parseSequenceString("Ctrl+O");
defaultShortCuts["TabDeckEditor/aLoadDeckFromClipboard"] = parseSequenceString("Ctrl+Shift+V");
defaultShortCuts["TabDeckEditor/aNewDeck"] = parseSequenceString("Ctrl+N");
defaultShortCuts["TabDeckEditor/aOpenCustomFolder"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aPrintDeck"] = parseSequenceString("Ctrl+P");
defaultShortCuts["TabDeckEditor/aRemoveCard"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aResetLayout"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aSaveDeck"] = parseSequenceString("Ctrl+S");
defaultShortCuts["TabDeckEditor/aSaveDeckAs"] = parseSequenceString("");
defaultShortCuts["TabDeckEditor/aSaveDeckToClipboard"] = parseSequenceString("Ctrl+Shift+C");
defaultShortCuts["TabDeckEditor/aSaveDeckToClipboardRaw"] = parseSequenceString("Ctrl+Shift+R");
defaultShortCuts["DeckViewContainer/loadLocalButton"] = parseSequenceString("Ctrl+O");
defaultShortCuts["DeckViewContainer/loadRemoteButton"] = parseSequenceString("Ctrl+Alt+O");
defaultShortCuts["Player/aDec"] = parseSequenceString("F11");
defaultShortCuts["Player/aInc"] = parseSequenceString("F12");
defaultShortCuts["Player/aSet"] = parseSequenceString("Ctrl+L");
defaultShortCuts["Player/aCloseMostRecentZoneView"] = parseSequenceString("Esc");
defaultShortCuts["Player/IncP"] = parseSequenceString("Ctrl++");
defaultShortCuts["Player/aAlwaysRevealTopCard"] = parseSequenceString("Ctrl+N");
defaultShortCuts["Player/aAttach"] = parseSequenceString("Ctrl+Alt+A");
defaultShortCuts["Player/aCCGreen"] = parseSequenceString("");
defaultShortCuts["Player/aCCRed"] = parseSequenceString("");
defaultShortCuts["Player/aCCYellow"] = parseSequenceString("");
defaultShortCuts["Player/aClone"] = parseSequenceString("Ctrl+J");
defaultShortCuts["Player/aCreateAnotherToken"] = parseSequenceString("Ctrl+G");
defaultShortCuts["Player/aCreateToken"] = parseSequenceString("Ctrl+T");
defaultShortCuts["Player/aCreateRelatedTokens"] = parseSequenceString("Ctrl+Shift+T");
defaultShortCuts["Player/aDecP"] = parseSequenceString("Ctrl+-");
defaultShortCuts["Player/aDecPT"] = parseSequenceString("Ctrl+Alt+-");
defaultShortCuts["Player/aDecT"] = parseSequenceString("Alt+-");
defaultShortCuts["Player/aDoesntUntap"] = parseSequenceString("");
defaultShortCuts["Player/aDrawArrow"] = parseSequenceString("");
defaultShortCuts["Player/aDrawCard"] = parseSequenceString("Ctrl+D");
defaultShortCuts["Player/aDrawCards"] = parseSequenceString("Ctrl+E");
defaultShortCuts["Player/aFlip"] = parseSequenceString("");
defaultShortCuts["Player/aIncPT"] = parseSequenceString("Ctrl+Alt++");
defaultShortCuts["Player/aIncT"] = parseSequenceString("Alt++");
defaultShortCuts["Player/aMoveToBottomLibrary"] = parseSequenceString("");
defaultShortCuts["Player/aMoveToExile"] = parseSequenceString("");
defaultShortCuts["Player/aMoveToGraveyard"] = parseSequenceString("Ctrl+Del");
defaultShortCuts["Player/aMoveToHand"] = parseSequenceString("");
defaultShortCuts["Player/aMoveToTopLibrary"] = parseSequenceString("");
defaultShortCuts["Player/aMulligan"] = parseSequenceString("Ctrl+M");
defaultShortCuts["Player/aPeek"] = parseSequenceString("");
defaultShortCuts["Player/aPlay"] = parseSequenceString("");
defaultShortCuts["Player/aRCGreen"] = parseSequenceString("");
defaultShortCuts["Player/aRCRed"] = parseSequenceString("");
defaultShortCuts["Player/aRCYellow"] = parseSequenceString("");
defaultShortCuts["Player/aRollDie"] = parseSequenceString("Ctrl+I");
defaultShortCuts["Player/aSCGreen"] = parseSequenceString("");
defaultShortCuts["Player/aSCRed"] = parseSequenceString("");
defaultShortCuts["Player/aSCYellow"] = parseSequenceString("");
defaultShortCuts["Player/aSetAnnotation"] = parseSequenceString("");
defaultShortCuts["Player/aSetPT"] = parseSequenceString("Ctrl+P");
defaultShortCuts["Player/aResetPT"] = parseSequenceString("Ctrl+Alt+0");
defaultShortCuts["Player/aShuffle"] = parseSequenceString("Ctrl+S");
defaultShortCuts["Player/aTap"] = parseSequenceString("");
defaultShortCuts["Player/aUnattach"] = parseSequenceString("");
defaultShortCuts["Player/aUndoDraw"] = parseSequenceString("Ctrl+Shift+D");
defaultShortCuts["Player/aUntapAll"] = parseSequenceString("Ctrl+U");
defaultShortCuts["Player/aViewGraveyard"] = parseSequenceString("F4");
defaultShortCuts["Player/aViewLibrary"] = parseSequenceString("F3");
defaultShortCuts["Player/aViewRfg"] = parseSequenceString("");
defaultShortCuts["Player/aViewSideboard"] = parseSequenceString("Ctrl+F3");
defaultShortCuts["Player/aViewTopCards"] = parseSequenceString("Ctrl+W");
defaultShortCuts["Player/aConcede"] = parseSequenceString("F2");
defaultShortCuts["Player/aLeaveGame"] = parseSequenceString("Ctrl+Q");
defaultShortCuts["Player/aNextPhase"] = parseSequenceString("Ctrl+Space;Tab");
defaultShortCuts["Player/aNextTurn"] = parseSequenceString("Ctrl+Return;Ctrl+Enter");
defaultShortCuts["Player/aRemoveLocalArrows"] = parseSequenceString("Ctrl+R");
defaultShortCuts["Player/aRotateViewCCW"] = parseSequenceString("");
defaultShortCuts["Player/aRotateViewCW"] = parseSequenceString("");
defaultShortCuts["Player/phase0"] = parseSequenceString("F5");
defaultShortCuts["Player/phase1"] = parseSequenceString("");
defaultShortCuts["Player/phase10"] = parseSequenceString("F10");
defaultShortCuts["Player/phase2"] = parseSequenceString("F6");
defaultShortCuts["Player/phase3"] = parseSequenceString("F7");
defaultShortCuts["Player/phase4"] = parseSequenceString("F8");
defaultShortCuts["Player/phase5"] = parseSequenceString("");
defaultShortCuts["Player/phase6"] = parseSequenceString("");
defaultShortCuts["Player/phase7"] = parseSequenceString("");
defaultShortCuts["Player/phase8"] = parseSequenceString("");
defaultShortCuts["Player/phase9"] = parseSequenceString("F9");
defaultShortCuts["Player/aIncCounter_w"] = parseSequenceString("");
defaultShortCuts["Player/aDecCounter_w"] = parseSequenceString("");
defaultShortCuts["Player/aSetCounter_w"] = parseSequenceString("");
defaultShortCuts["Player/aIncCounter_u"] = parseSequenceString("");
defaultShortCuts["Player/aDecCounter_u"] = parseSequenceString("");
defaultShortCuts["Player/aSetCounter_u"] = parseSequenceString("");
defaultShortCuts["Player/aIncCounter_b"] = parseSequenceString("");
defaultShortCuts["Player/aDecCounter_b"] = parseSequenceString("");
defaultShortCuts["Player/aSetCounter_b"] = parseSequenceString("");
defaultShortCuts["Player/aIncCounter_r"] = parseSequenceString("");
defaultShortCuts["Player/aDecCounter_r"] = parseSequenceString("");
defaultShortCuts["Player/aSetCounter_r"] = parseSequenceString("");
defaultShortCuts["Player/aIncCounter_g"] = parseSequenceString("");
defaultShortCuts["Player/aDecCounter_g"] = parseSequenceString("");
defaultShortCuts["Player/aSetCounter_g"] = parseSequenceString("");
defaultShortCuts["Player/aIncCounter_x"] = parseSequenceString("");
defaultShortCuts["Player/aDecCounter_x"] = parseSequenceString("");
defaultShortCuts["Player/aSetCounter_x"] = parseSequenceString("");
defaultShortCuts["Player/aIncCounter_storm"] = parseSequenceString("");
defaultShortCuts["Player/aDecCounter_storm"] = parseSequenceString("");
defaultShortCuts["Player/aSetCounter_storm"] = parseSequenceString("");
defaultShortCuts["tab_room/aClearChat"] = parseSequenceString("F12");
defaultShortCuts["DlgLoadDeckFromClipboard/refreshButton"] = parseSequenceString("F5");
defaultShortCuts["Player/aResetLayout"] = parseSequenceString("");
defaultShortCuts["Player/aMoveTopToPlayFaceDown"] = parseSequenceString("Ctrl+Shift+E");
}

View File

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

View File

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

View File

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

View File

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

View File

@ -299,16 +299,17 @@ void MainWindow::actAbout()
QMessageBox::NoIcon, tr("About Cockatrice"),
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("Cockatrice Webpage") + "</a></b><br>" + "<br><br><b>" + tr("Project Manager:") +
"</b><br>Gavin Bisesi<br><br>" + "<b>" + tr("Past Project Managers:") +
"</b><br>Max-Wilhelm Bruker<br>Marcus Schütz<br><br>" + "<b>" + tr("Developers:") + "</b><br>" +
"<a href='" + GITHUB_CONTRIBUTORS_URL + "'>" + tr("Our Developers") + "</a><br>" + "<a href='" +
GITHUB_CONTRIBUTE_URL + "'>" + tr("Help Develop!") + "</a><br><br>" + "<b>" + tr("Translators:") +
"</b><br>" + "<a href='" + GITHUB_TRANSIFEX_TRANSLATORS_URL + "'>" + tr("Our Translators") +
"</a><br>" + "<a href='" + GITHUB_TRANSLATOR_FAQ_URL + "'>" + tr("Help Translate!") + "</a><br><br>" +
"<b>" + tr("Support:") + "</b><br>" + "<a href='" + GITHUB_ISSUES_URL + "'>" + tr("Report an Issue") +
"</a><br>" + "<a href='" + GITHUB_TROUBLESHOOTING_URL + "'>" + tr("Troubleshooting") + "</a><br>" +
"<a href='" + GITHUB_FAQ_URL + "'>" + tr("F.A.Q.") + "</a><br>"),
tr("Cockatrice Webpage") + "</a></b><br>" + "<br><b>" + tr("Project Manager:") +
"</b><br>Zach Halpern<br><br>" + "<b>" + tr("Past Project Managers:") +
"</b><br>Gavin Bisesi<br>Max-Wilhelm Bruker<br>Marcus Schütz<br><br>" + "<b>" + tr("Developers:") +
"</b><br>" + "<a href='" + GITHUB_CONTRIBUTORS_URL + "'>" + tr("Our Developers") + "</a><br>" +
"<a href='" + GITHUB_CONTRIBUTE_URL + "'>" + tr("Help Develop!") + "</a><br><br>" + "<b>" +
tr("Translators:") + "</b><br>" + "<a href='" + GITHUB_TRANSIFEX_TRANSLATORS_URL + "'>" +
tr("Our Translators") + "</a><br>" + "<a href='" + GITHUB_TRANSLATOR_FAQ_URL + "'>" +
tr("Help Translate!") + "</a><br><br>" + "<b>" + tr("Support:") + "</b><br>" + "<a href='" +
GITHUB_ISSUES_URL + "'>" + tr("Report an Issue") + "</a><br>" + "<a href='" +
GITHUB_TROUBLESHOOTING_URL + "'>" + tr("Troubleshooting") + "</a><br>" + "<a href='" + GITHUB_FAQ_URL +
"'>" + tr("F.A.Q.") + "</a><br>"),
QMessageBox::Ok, this);
mb.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
mb.setTextInteractionFlags(Qt::TextBrowserInteraction);
@ -317,9 +318,9 @@ void MainWindow::actAbout()
void MainWindow::actTips()
{
if (tip != NULL) {
if (tip != nullptr) {
delete tip;
tip = NULL;
tip = nullptr;
}
tip = new DlgTipOfTheDay();
if (tip->successfulInit) {
@ -705,15 +706,33 @@ void MainWindow::createActions()
aExit->setMenuRole(QAction::QuitRole);
aAbout->setMenuRole(QAction::AboutRole);
char const *foo; // avoid "warning: expression result unused" under clang
foo = QT_TRANSLATE_NOOP("QMenuBar", "Services");
foo = QT_TRANSLATE_NOOP("QMenuBar", "Hide %1");
foo = QT_TRANSLATE_NOOP("QMenuBar", "Hide Others");
foo = QT_TRANSLATE_NOOP("QMenuBar", "Show All");
foo = QT_TRANSLATE_NOOP("QMenuBar", "Preferences...");
foo = QT_TRANSLATE_NOOP("QMenuBar", "Quit %1");
foo = QT_TRANSLATE_NOOP("QMenuBar", "About %1");
Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Services"));
Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Hide %1"));
Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Hide Others"));
Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Show All"));
Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Preferences..."));
Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "Quit %1"));
Q_UNUSED(QT_TRANSLATE_NOOP("QMenuBar", "About %1"));
#endif
// translate Qt's dialogs "default button text"; list taken from QPlatformTheme::defaultStandardButtonText()
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "OK"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Save"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Save All"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Open"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "&Yes"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Yes to &All"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "&No"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "N&o to All"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Abort"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Retry"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Ignore"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Close"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Cancel"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Discard"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Help"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Apply"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Reset"));
Q_UNUSED(QT_TRANSLATE_NOOP("QPlatformTheme", "Restore Defaults"));
}
void MainWindow::createMenus()
@ -811,7 +830,7 @@ MainWindow::MainWindow(QWidget *parent)
createTrayIcon();
}
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()), this, SLOT(refreshShortcuts()));
connect(&settingsCache->shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
refreshShortcuts();
connect(db, SIGNAL(cardDatabaseLoadingFailed()), this, SLOT(cardDatabaseLoadingFailed()));
@ -828,13 +847,30 @@ MainWindow::MainWindow(QWidget *parent)
if (tip->successfulInit && settingsCache->getShowTipsOnStartup() && tip->newTipsAvailable) {
tip->show();
}
// Only run the check updater if the user wants it (defaults to on)
if (settingsCache->getNotifyAboutNewVersion()) {
auto versionUpdater = new MainUpdateHelper();
connect(versionUpdater, SIGNAL(newVersionDetected(QString)), this, SLOT(alertForcedOracleRun(QString)));
QtConcurrent::run(versionUpdater, &MainUpdateHelper::testForNewVersion);
}
}
void MainWindow::alertForcedOracleRun(const QString &newVersion)
{
settingsCache->setClientVersion(newVersion);
QMessageBox::information(this, tr("New Version"),
tr("Congratulations on updating to Cockatrice %1!\n"
"Oracle will now launch to update your card database.")
.arg(newVersion));
actCheckCardUpdates();
}
MainWindow::~MainWindow()
{
if (tip != NULL) {
if (tip != nullptr) {
delete tip;
tip = NULL;
tip = nullptr;
}
if (trayIcon) {
trayIcon->hide();
@ -1271,3 +1307,10 @@ void MainWindow::promptForgotPasswordReset()
dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
}
}
void MainUpdateHelper::testForNewVersion()
{
if (settingsCache->getClientVersion() != VERSION_STRING) {
emit newVersionDetected(VERSION_STRING);
}
}

View File

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

View File

@ -127,21 +127,32 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
labNotes->setOpenExternalLinks(true);
labNotes->setText(
"<b>" + tr("Deck Editor") + ":</b> " +
tr("Only cards in enabled sets will appear in the deck editor card list") + "<br><b>" + tr("Card Art") +
":</b> " + tr("Image priority is decided in the following order") + "<ol><li>" + tr("The") +
"<a "
"href='https://github.com/Cockatrice/Cockatrice/wiki/"
tr("Only cards in enabled sets will appear in the deck editor card list") + "<br><br>" + "<b>" +
tr("Card Art") + ":</b> " + tr("Image priority is decided in the following order") + "<ol><li>" + tr("The") +
"<a 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'> " +
tr("CUSTOM Folder") + "</a></li><li>" + tr("Enabled Sets (Top to Bottom)") + "</li><li>" +
tr("Disabled Sets (Top to Bottom)") + "</li></ol>");
sortWarning = new QLabel;
sortWarning->setWordWrap(true);
sortWarning->setText(
"<b>" + tr("Warning: ") + "</b><br>" +
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->setStyleSheet("QLabel { background-color:red;}");
QGridLayout *hintsGrid = new QGridLayout;
hintsGrid->addWidget(labNotes, 0, 0);
hintsGroupBox = new QGroupBox(tr("Hints"));
hintsGroupBox->setLayout(hintsGrid);
sortWarning = new QGroupBox(tr("Note"));
QGridLayout *sortWarningLayout = new QGridLayout;
sortWarningText = new QLabel;
sortWarningText->setWordWrap(true);
sortWarningText->setText(tr("Sorting by column allows you to find a set while not changing set priority.") + " " +
tr("To enable ordering again, click the column header until this message disappears."));
sortWarningLayout->addWidget(sortWarningText, 0, 0, 1, 2);
sortWarningButton = new QPushButton;
sortWarningButton->setText(tr("Use the current sorting as the set priority instead"));
sortWarningButton->setToolTip(tr("Sorts the set priority using the same column"));
sortWarningButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
connect(sortWarningButton, SIGNAL(released()), this, SLOT(actIgnoreWarning()));
sortWarningLayout->addWidget(sortWarningButton, 1, 0);
sortWarning->setLayout(sortWarningLayout);
sortWarning->setVisible(false);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
@ -157,7 +168,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
mainLayout->addWidget(enableSomeButton, 2, 1);
mainLayout->addWidget(disableSomeButton, 2, 2);
mainLayout->addWidget(sortWarning, 3, 1, 1, 2);
mainLayout->addWidget(labNotes, 4, 1, 1, 2);
mainLayout->addWidget(hintsGroupBox, 4, 1, 1, 2);
mainLayout->addWidget(buttonBox, 5, 1, 1, 2);
mainLayout->setColumnStretch(1, 1);
mainLayout->setColumnStretch(2, 1);
@ -250,6 +261,17 @@ void WndSets::actSort(int index)
}
}
void WndSets::actIgnoreWarning()
{
if (sortIndex < 0) {
return;
}
model->sort(sortIndex, sortOrder);
view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder);
sortIndex = -1;
sortWarning->setVisible(false);
}
void WndSets::actDisableSortButtons(int index)
{
if (index != SORT_RESET) {

View File

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

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>
<location filename="../src/releasechannel.cpp" line="207"/>
<source>Beta Releases</source>
<translation type="unfinished"/>
<translation>version bêta</translation>
</message>
<message>
<location filename="../src/releasechannel.cpp" line="233"/>
<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>
<location filename="../src/releasechannel.cpp" line="242"/>
<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>
<location filename="../src/releasechannel.cpp" line="277"/>
<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>
</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="561"/>
<source>Update Spoilers</source>
<translation type="unfinished"/>
<translation>mettre à jour les spoilers</translation>
</message>
<message>
<location filename="../src/dlg_settings.cpp" line="550"/>
<source>Updating...</source>
<translation type="unfinished"/>
<translation>Mise à jours</translation>
</message>
<message>
<location filename="../src/dlg_settings.cpp" line="580"/>
<source>Choose path</source>
<translation type="unfinished"/>
<translation>Choisir le chemin</translation>
</message>
<message>
<location filename="../src/dlg_settings.cpp" line="606"/>
<source>Spoilers</source>
<translation type="unfinished"/>
<translation>Spoilers</translation>
</message>
<message>
<location filename="../src/dlg_settings.cpp" line="607"/>
<source>Download Spoilers Automatically</source>
<translation type="unfinished"/>
<translation>Télécharger automatiquement les spoilers</translation>
</message>
<message>
<location filename="../src/dlg_settings.cpp" line="608"/>
<source>Spoiler Location:</source>
<translation type="unfinished"/>
<translation>Emplacement des spoilers:</translation>
</message>
<message>
<location filename="../src/dlg_settings.cpp" line="609"/>
<source>Hey, something&apos;s here finally!</source>
<translation type="unfinished"/>
<translation>Hey, quelque chose est enfin !</translation>
</message>
<message>
<location filename="../src/dlg_settings.cpp" line="610"/>
<source>Last Updated</source>
<translation type="unfinished"/>
<translation>dernière mise à jour</translation>
</message>
<message>
<location filename="../src/dlg_settings.cpp" line="611"/>
<source>Spoilers download automatically on launch</source>
<translation type="unfinished"/>
<translation>spoilers téléchargé automatiquement au lancement</translation>
</message>
<message>
<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>
<location filename="../src/dlg_connect.cpp" line="28"/>
<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>
<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>
<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>
<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>
<location filename="../src/dlg_connect.cpp" line="80"/>
<location filename="../src/dlg_connect.cpp" line="262"/>
<source>Webpage</source>
<translation type="unfinished"/>
<translation>page internet</translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="90"/>
<source>Forgot Password</source>
<translation type="unfinished"/>
<translation>Mot de passe oublié</translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="94"/>
<source>&amp;Connect</source>
<translation type="unfinished"/>
<translation>&amp;Connexion </translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="136"/>
<source>Server Contact</source>
<translation type="unfinished"/>
<translation>Contact du serveur</translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="149"/>
<source>Connect to Server</source>
<translation type="unfinished"/>
<translation>Connecter au serveur</translation>
</message>
<message>
<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>
<location filename="../src/dlg_connect.h" line="76"/>
<source>Downloading...</source>
<translation type="unfinished"/>
<translation>Téléchargement</translation>
</message>
</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"/>
<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>
<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>
</context>
<context>
@ -1536,17 +1537,17 @@ Voulez vous changer les paramètres d&apos;emplacement de base de données ?</tr
<message>
<location filename="../src/dlg_tip_of_the_day.cpp" line="67"/>
<source>Next</source>
<translation type="unfinished"/>
<translation>suivant</translation>
</message>
<message>
<location filename="../src/dlg_tip_of_the_day.cpp" line="68"/>
<source>Previous</source>
<translation type="unfinished"/>
<translation>précédent</translation>
</message>
<message>
<location filename="../src/dlg_tip_of_the_day.cpp" line="90"/>
<source>Tip of the Day</source>
<translation type="unfinished"/>
<translation>Conseil du jour</translation>
</message>
</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="158"/>
<source>Released</source>
<translation type="unfinished"/>
<translation>Sortie</translation>
</message>
<message>
<location filename="../src/dlg_update.cpp" line="148"/>
@ -1715,7 +1716,7 @@ Vous devez compiler les sources vous-même.</translation>
<message>
<location filename="../src/dlg_viewlog.cpp" line="18"/>
<source>Clear log when closing</source>
<translation type="unfinished"/>
<translation>Effacer le journal lors de la fermeture</translation>
</message>
<message>
<location filename="../src/dlg_viewlog.cpp" line="25"/>
@ -1728,7 +1729,7 @@ Vous devez compiler les sources vous-même.</translation>
<message>
<location filename="../src/filterbuilder.cpp" line="28"/>
<source>Type your filter here</source>
<translation type="unfinished"/>
<translation>Tapez votre filtre ici</translation>
</message>
</context>
<context>
@ -2041,7 +2042,7 @@ Vous devez compiler les sources vous-même.</translation>
<message>
<location filename="../src/dlg_settings.cpp" line="317"/>
<source>Show tips on startup</source>
<translation type="unfinished"/>
<translation>Afficher les astuces au démarrage</translation>
</message>
</context>
<context>
@ -2589,7 +2590,7 @@ La version locale est %1, la nouvelle version est %2.</translation>
<message>
<location filename="../src/window_main.cpp" line="641"/>
<source>&amp;Tip of the Day</source>
<translation type="unfinished"/>
<translation>&amp;Conseil du jour</translation>
</message>
<message>
<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>
<location filename="../src/window_main.cpp" line="637"/>
<source>&amp;Manage sets...</source>
<translation type="unfinished"/>
<translation>&amp;Gérer les sets...</translation>
</message>
<message>
<location filename="../src/window_main.cpp" line="983"/>
<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.
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>
<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>
<location filename="../src/messagelogwidget.cpp" line="366"/>
<source>%1 turns %2 face-down.</source>
<translation type="unfinished"/>
<translation>%1 tourner %2 face cachée.</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="368"/>
<source>%1 turns %2 face-up.</source>
<translation type="unfinished"/>
<translation>%1 tourner %2 face visible.</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="402"/>
@ -3862,7 +3863,7 @@ Cockactrice va maintenant recharger la base de données de cartes.</translation>
<message>
<location filename="../src/player.cpp" line="2722"/>
<source>View related cards</source>
<translation type="unfinished"/>
<translation>Voir les cartes associées</translation>
</message>
<message>
<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"/>
<source>T&amp;urn Over</source>
<extracomment>Turn face up/face down</extracomment>
<translation type="unfinished"/>
<translation>Retourner</translation>
</message>
<message>
<location filename="../src/player.cpp" line="713"/>
@ -4148,7 +4149,7 @@ Cockactrice va maintenant recharger la base de données de cartes.</translation>
<message>
<location filename="../src/sequenceEdit/sequenceedit.cpp" line="158"/>
<source>Invalid key</source>
<translation type="unfinished"/>
<translation>Clé invalide</translation>
</message>
</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"/>
<source>Your configuration file contained invalid shortcuts.
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>
<location filename="../src/shortcutssettings.cpp" line="42"/>
<source>The following shortcuts have been set to default:
</source>
<translation type="unfinished"/>
<translation>Les raccourcis suivants ont é définis par défaut:
</translation>
</message>
</context>
<context>
@ -4268,48 +4270,48 @@ Please check your shortcut settings!</source>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="99"/>
<source>Spoilers season has ended</source>
<translation type="unfinished"/>
<translation>La saison des spoilers est terminée</translation>
</message>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="99"/>
<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>
<location filename="../src/spoilerbackgroundupdater.cpp" line="109"/>
<location filename="../src/spoilerbackgroundupdater.cpp" line="116"/>
<source>Spoilers download failed</source>
<translation type="unfinished"/>
<translation>Échec du téléchargement des spoilers</translation>
</message>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="109"/>
<source>No internet connection</source>
<translation type="unfinished"/>
<translation>Pas de connexion Internet</translation>
</message>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="116"/>
<source>Error</source>
<translation type="unfinished"/>
<translation>Erreur</translation>
</message>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="137"/>
<source>Spoilers already up to date</source>
<translation type="unfinished"/>
<translation>Spoilers déjà à jour</translation>
</message>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="137"/>
<source>No new spoilers added</source>
<translation type="unfinished"/>
<translation>Aucun nouveau spoilers ajouté</translation>
</message>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="178"/>
<source>Spoilers have been updated!</source>
<translation type="unfinished"/>
<translation>Les spoilers ont é mis à jour!</translation>
</message>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="178"/>
<source>Last change:</source>
<translation type="unfinished"/>
<translation>Dernier changement:</translation>
</message>
</context>
<context>
@ -4317,7 +4319,7 @@ Please check your shortcut settings!</source>
<message>
<location filename="../src/releasechannel.cpp" line="90"/>
<source>Stable Releases</source>
<translation type="unfinished"/>
<translation>Version stable</translation>
</message>
<message>
<location filename="../src/releasechannel.cpp" line="108"/>
@ -4438,57 +4440,57 @@ Please check your shortcut settings!</source>
<message>
<location filename="../src/tab_deck_editor.cpp" line="347"/>
<source>Search by card name</source>
<translation type="unfinished"/>
<translation>Rechercher par nom de carte</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="453"/>
<source>Add to Deck</source>
<translation type="unfinished"/>
<translation>Ajouter au Deck</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="454"/>
<source>Add to Sideboard</source>
<translation type="unfinished"/>
<translation>Ajouter à la réserve</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="459"/>
<source>Show Related cards</source>
<translation type="unfinished"/>
<translation>Afficher les cartes associées</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="623"/>
<source>Save deck to clipboard</source>
<translation type="unfinished"/>
<translation>Enregistrer le deck dans le presse-papier</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="624"/>
<source>Annotated</source>
<translation type="unfinished"/>
<translation>Annoté</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="625"/>
<source>Not Annotated</source>
<translation type="unfinished"/>
<translation>sans annotation</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="629"/>
<source>&amp;Send deck to online service</source>
<translation type="unfinished"/>
<translation>&amp; Envoyer le Deck au service en ligne</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="630"/>
<source>Create decklist (decklist.org)</source>
<translation type="unfinished"/>
<translation>Créer une liste de deck (decklist.org)</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="631"/>
<source>Analyze deck (deckstats.net)</source>
<translation type="unfinished"/>
<translation>Analyser le Deck (deckstats.net)</translation>
</message>
<message>
<location filename="../src/tab_deck_editor.cpp" line="632"/>
<source>Analyze deck (tappedout.net)</source>
<translation type="unfinished"/>
<translation>Analyser le deck (tappedout.net)</translation>
</message>
<message>
<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>
<location filename="../src/tab_deck_editor.cpp" line="877"/>
<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>
<location filename="../src/tab_deck_editor.cpp" line="889"/>
<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>
</context>
<context>
@ -5077,7 +5079,7 @@ Plus vous entrez d&apos;informations, meilleurs seront les résultats.</translat
<message>
<location filename="../src/tab_message.cpp" line="148"/>
<source>Private message from</source>
<translation type="unfinished"/>
<translation>Message privé de</translation>
</message>
<message>
<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"/>
<source>File does not exist.
</source>
<translation type="unfinished"/>
<translation>Le fichier n&apos;existe pas.
</translation>
</message>
<message>
<location filename="../src/tip_of_the_day.cpp" line="28"/>
<source>Failed to open file.
</source>
<translation type="unfinished"/>
<translation>Échec de l&apos;ouverture du fichier.
</translation>
</message>
</context>
<context>
@ -5810,17 +5814,17 @@ Merci de ne pas recommencer ou d&apos;autres mesures peuvent être prises contre
<message>
<location filename="../src/window_sets.cpp" line="61"/>
<source>Search by set name, code, or type</source>
<translation type="unfinished"/>
<translation>Rechercher par ensemble de nom, code ou type</translation>
</message>
<message>
<location filename="../src/window_sets.cpp" line="66"/>
<source>Default order</source>
<translation type="unfinished"/>
<translation>Ordre par défaut</translation>
</message>
<message>
<location filename="../src/window_sets.cpp" line="67"/>
<source>Restore original art priority order</source>
<translation type="unfinished"/>
<translation>Restaurer l&apos;ordre originale de priorité artistique</translation>
</message>
<message>
<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>
<location filename="../src/window_sets.cpp" line="139"/>
<source>Warning: </source>
<translation type="unfinished"/>
<translation>Attention:</translation>
</message>
<message>
<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>
<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>
<location filename="../src/window_sets.cpp" line="141"/>
<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>
<location filename="../src/window_sets.cpp" line="170"/>
<source>Manage sets</source>
<translation type="unfinished"/>
<translation>Gérer les sets</translation>
</message>
<message>
<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>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1764"/>
<source>Manage sets</source>
<translation type="unfinished"/>
<translation>Gérer les sets</translation>
</message>
<message>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1770"/>
<source>Export deck</source>
<translation type="unfinished"/>
<translation>Exporter le Deck</translation>
</message>
<message>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1774"/>
<source>Save deck (clip)</source>
<translation type="unfinished"/>
<translation>Sauvegarder le Deck (du presse-papier)</translation>
</message>
<message>
<location filename="../src/sequenceEdit/ui_shortcutstab.h" line="1776"/>
<source>Save deck (clip; no annotations)</source>
<translation type="unfinished"/>
<translation>Enregistrer le Deck (du presse papier; sans annotations)</translation>
</message>
<message>
<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>
<location filename="../src/cardinfotext.cpp" line="123"/>
<source>Unknown card:</source>
<translation type="unfinished"/>
<translation>:</translation>
</message>
<message>
<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>
<location filename="../src/dlg_connect.cpp" line="28"/>
<source>Refresh the server list with known public servers</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<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>
<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>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="80"/>
<location filename="../src/dlg_connect.cpp" line="262"/>
<source>Webpage</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="90"/>
<source>Forgot Password</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="94"/>
<source>&amp;Connect</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="136"/>
<source>Server Contact</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<location filename="../src/dlg_connect.cpp" line="149"/>
<source>Connect to Server</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<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>
<location filename="../src/dlg_edit_tokens.cpp" line="145"/>
<source>Please enter the name of the token:</source>
<translation>:</translation>
<translation>:</translation>
</message>
<message>
<location filename="../src/dlg_edit_tokens.cpp" line="149"/>
@ -1771,7 +1771,7 @@ You may have to build from source yourself.</source>
<message>
<location filename="../src/gameselector.cpp" line="169"/>
<source>The game does not exist any more.</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../src/gameselector.cpp" line="172"/>
@ -2818,12 +2818,12 @@ Cockatrice will now reload the card database.</source>
<message>
<location filename="../src/messagelogwidget.cpp" line="391"/>
<source>%1 is now watching the game.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="251"/>
<source>%1 has loaded a deck (%2).</source>
<translation>%1:%2</translation>
<translation>%1 :%2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="253"/>
@ -2889,57 +2889,57 @@ Cockatrice will now reload the card database.</source>
<message>
<location filename="../src/messagelogwidget.cpp" line="290"/>
<source>%1 gives %2 control over %3.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="302"/>
<source>%1 puts %2 into play tapped%3.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="304"/>
<source>%1 puts %2 into play%3.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="308"/>
<source>%1 exiles %2%3.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="321"/>
<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>
<location filename="../src/messagelogwidget.cpp" line="324"/>
<source>%1 moves %2%3 to sideboard.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="327"/>
<source>%1 plays %2%3.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="366"/>
<source>%1 turns %2 face-down.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="368"/>
<source>%1 turns %2 face-up.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="402"/>
<source>%1 has left the game (%2).</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="410"/>
<source>%1 is not watching the game any more (%2).</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="546"/>
@ -3004,7 +3004,7 @@ Cockatrice will now reload the card database.</source>
<message>
<location filename="../src/messagelogwidget.cpp" line="612"/>
<source>%1&apos;s turn.</source>
<translation>%1</translation>
<translation> %1 </translation>
</message>
<message numerus="yes">
<location filename="../src/messagelogwidget.cpp" line="636"/>
@ -3024,12 +3024,12 @@ Cockatrice will now reload the card database.</source>
<message>
<location filename="../src/messagelogwidget.cpp" line="151"/>
<source>%1 is now keeping the top card %2 revealed.</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="152"/>
<source>%1 is not revealing the top card %2 any longer.</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="446"/>
@ -3039,47 +3039,47 @@ Cockatrice will now reload the card database.</source>
<message>
<location filename="../src/messagelogwidget.cpp" line="385"/>
<source>%1 has joined the game.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="451"/>
<source>%1 is ready to start the game.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="415"/>
<source>%1 is not ready to start the game any more.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="691"/>
<source>%1 has locked their sideboard.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="693"/>
<source>%1 has unlocked their sideboard.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="169"/>
<source>%1 has conceded the game.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="176"/>
<source>%1 has restored connection to the game.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="180"/>
<source>%1 has lost connection to the game.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="721"/>
<source>%1 shuffles %2.</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="522"/>
@ -3094,27 +3094,27 @@ Cockatrice will now reload the card database.</source>
<message>
<location filename="../src/messagelogwidget.cpp" line="523"/>
<source>%1 flipped a coin. It landed as %2.</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="527"/>
<source>%1 rolls a %2 with a %3-sided die.</source>
<translation>%1%3%2</translation>
<translation>%1 %3%2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="344"/>
<source>%1 draws %2 card(s).</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="749"/>
<source>%1 undoes their last draw.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="752"/>
<source>%1 undoes their last draw (%2).</source>
<translation>%1 (%2) </translation>
<translation>%1 ( %2 ) </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="72"/>
@ -3154,202 +3154,202 @@ Cockatrice will now reload the card database.</source>
<message>
<location filename="../src/messagelogwidget.cpp" line="306"/>
<source>%1 puts %2%3 into their graveyard.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="310"/>
<source>%1 moves %2%3 to their hand.</source>
<translation>%1%2%3</translation>
<translation>% 1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="313"/>
<source>%1 puts %2%3 into their library.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="315"/>
<source>%1 puts %2%3 on bottom of their library.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="317"/>
<source>%1 puts %2%3 on top of their library.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="439"/>
<source>%1 takes a mulligan to %2.</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="441"/>
<source>%1 draws their initial hand.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="261"/>
<source>%1 destroys %2.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="743"/>
<source>%1 unattaches %2.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="242"/>
<source>%1 creates token: %2%3.</source>
<translation>%1:%2%3</translation>
<translation>%1 : %2 %3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="196"/>
<source>%1 points from their %2 to themselves.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="199"/>
<source>%1 points from their %2 to %3.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="203"/>
<source>%1 points from %2&apos;s %3 to themselves.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="207"/>
<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>
<location filename="../src/messagelogwidget.cpp" line="215"/>
<source>%1 points from their %2 to their %3.</source>
<translation>%1%2%3</translation>
<translation>%1 %2 %3 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="218"/>
<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>
<location filename="../src/messagelogwidget.cpp" line="224"/>
<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>
<location filename="../src/messagelogwidget.cpp" line="230"/>
<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>
<location filename="../src/messagelogwidget.cpp" line="629"/>
<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>
<location filename="../src/messagelogwidget.cpp" line="631"/>
<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>
<location filename="../src/messagelogwidget.cpp" line="708"/>
<source>%1 taps their permanents.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="708"/>
<source>%1 untaps their permanents.</source>
<translation>%1</translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="711"/>
<source>%1 taps %2.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="711"/>
<source>%1 untaps %2.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="659"/>
<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>
<location filename="../src/messagelogwidget.cpp" line="671"/>
<source>%1 sets %2 to not untap normally.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="673"/>
<source>%1 sets %2 to untap normally.</source>
<translation>%1%2</translation>
<translation>%1 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="682"/>
<source>%1 sets PT of %2 to %3.</source>
<translation>%1%2P/Tを%3</translation>
<translation>%1 %2 P/Tを%3</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="618"/>
<source>%1 sets annotation of %2 to %3.</source>
<translation>%1%2 (%3)</translation>
<translation>%1 %2 (%3)</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="353"/>
<source>%1 is looking at %2.</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="357"/>
<source>%1 is looking at the top %2 card(s) %3.</source>
<translation>%1%3%2</translation>
<translation>%1 %3%2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="736"/>
<source>%1 stops looking at %2.</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="479"/>
<source>%1 reveals %2 to %3.</source>
<translation>%1%3%2</translation>
<translation>%1 %3 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="484"/>
<source>%1 reveals %2.</source>
<translation>%1%2</translation>
<translation>%1 %2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="489"/>
<source>%1 randomly reveals %2%3 to %4.</source>
<translation>%1%3%2%4</translation>
<translation>%1 %3 %2 %4</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="496"/>
<source>%1 randomly reveals %2%3.</source>
<translation>%1%3%2</translation>
<translation>%1 %3 %2 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="501"/>
<source>%1 peeks at face down card #%2.</source>
<translation>%1#%2</translation>
<translation>%1 #%2</translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="503"/>
<source>%1 peeks at face down card #%2: %3.</source>
<translation>%1#%2 (%3) </translation>
<translation>%1 #%2 ( %3 ) </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="508"/>
<source>%1 reveals %2%3 to %4.</source>
<translation>%1%3%2%4</translation>
<translation>%1 %3 %2 %4 </translation>
</message>
<message>
<location filename="../src/messagelogwidget.cpp" line="515"/>
<source>%1 reveals %2%3.</source>
<translation>%1%3%2</translation>
<translation>%1 %3 %2 </translation>
</message>
</context>
<context>
@ -3515,7 +3515,7 @@ Cockatrice will now reload the card database.</source>
<message>
<location filename="../src/player.cpp" line="617"/>
<source>Player &quot;%1&quot;</source>
<translation> &quot;%1&quot;</translation>
<translation>: %1</translation>
</message>
<message>
<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="2877"/>
<source>C&amp;reate another %1 token</source>
<translation>%1</translation>
<translation> %1 </translation>
</message>
<message>
<location filename="../src/player.cpp" line="1228"/>
@ -4274,7 +4274,7 @@ Please check your shortcut settings!</source>
<message>
<location filename="../src/spoilerbackgroundupdater.cpp" line="99"/>
<source>Spoilers season has ended</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<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>
<location filename="../src/tab_message.cpp" line="66"/>
<source>Private &amp;chat</source>
<translation></translation>
<translation></translation>
</message>
<message>
<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>
<location filename="../src/tab_message.cpp" line="83"/>
<source>%1 - Private chat</source>
<translation>%1 - </translation>
<translation>%1 - </translation>
</message>
<message>
<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>
<location filename="../src/window_sets.cpp" line="139"/>
<source>Warning: </source>
<translation type="unfinished"/>
<translation>:</translation>
</message>
<message>
<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>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<location filename="../src/window_sets.cpp" line="141"/>
<source>To disable sorting click on the same column header again until this message disappears.</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<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
serverinfo_user_container.cpp
sfmt/SFMT.c
expression.cpp
)
set(ORACLE_LIBS)

View File

@ -2,8 +2,17 @@
#include <QCryptographicHash>
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
#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)
: name(_name), moveList(_moveList)
{
@ -477,161 +486,131 @@ bool DeckList::saveToFile_Native(QIODevice *device)
bool DeckList::loadFromStream_Plain(QTextStream &in)
{
const QRegularExpression reCardLine("^\\s*[\\w\\[\\(\\{].*$", QRegularExpression::UseUnicodePropertiesOption);
const QRegularExpression reEmpty("^\\s*$");
const QRegularExpression reComment("[\\w\\[\\(\\{].*$", QRegularExpression::UseUnicodePropertiesOption);
const QRegularExpression reSBMark("^\\s*sb:\\s*(.+)", QRegularExpression::CaseInsensitiveOption);
const QRegularExpression reSBComment("sideboard", QRegularExpression::CaseInsensitiveOption);
// simplified matches
const QRegularExpression reMultiplier("^[xX\\(\\[]*(\\d+)[xX\\*\\)\\]]* ?(.+)");
const QRegularExpression reBrace(" ?[\\[\\{][^\\]\\}]*[\\]\\}] ?"); // not nested
const QRegularExpression reRoundBrace("^\\([^\\)]*\\) ?"); // () are only matched at start of string
const QRegularExpression reDigitBrace(" ?\\(\\d*\\) ?"); // () are matched if containing digits
const QHash<QRegularExpression, QString> differences{{QRegularExpression(""), QString("'")},
{QRegularExpression("Æ"), QString("Ae")},
{QRegularExpression("æ"), QString("ae")},
{QRegularExpression(" ?[|/]+ ?"), QString(" // ")},
{QRegularExpression("(?<![A-Z]) ?& ?"), QString(" // ")}};
cleanList();
QVector<QString> inputs; // QTextStream -> QVector
bool priorEntryIsBlank = true, isAtBeginning = true;
int blankLines = 0;
while (!in.atEnd()) {
QString line = in.readLine().simplified().toLower();
QStringList inputs = in.readAll().trimmed().split('\n');
int max_line = inputs.size();
/*
* Removes all blank lines at start of inputs
* Ex: ("", "", "", "Card1", "Card2") => ("Card1", "Card2")
*
* This will also concise multiple blank lines in a row to just one blank
* Ex: ("Card1", "Card2", "", "", "", "Card3") => ("Card1", "Card2", "", "Card3")
*/
if (line.isEmpty()) {
if (priorEntryIsBlank || isAtBeginning) {
continue;
}
priorEntryIsBlank = true;
blankLines++;
} else {
isAtBeginning = false;
priorEntryIsBlank = false;
// start at the first empty line before the first cardline
int deckStart = inputs.indexOf(reCardLine);
if (deckStart == -1) { // there are no cards?
if (inputs.indexOf(reComment) == -1)
return false; // input is empty
deckStart = max_line;
} else {
deckStart = inputs.lastIndexOf(reEmpty, deckStart);
if (deckStart == -1) {
deckStart = 0;
}
inputs.push_back(line);
}
/*
* Removes blank line at end of inputs (if applicable)
* Ex: ("Card1", "Card2", "") => ("Card1", "Card2")
* NOTE: Any duplicates were taken care of above, so there can be
* at most one blank line at the very end
*/
if (!inputs.empty() && inputs.last().isEmpty()) {
blankLines--;
inputs.erase(inputs.end() - 1);
}
// If "Sideboard" line appears in inputs, then blank lines mean nothing
if (inputs.contains("sideboard")) {
blankLines = 2;
}
bool inSideboard = false, titleFound = false, isSideboard;
int okRows = 0;
foreach (QString line, inputs) {
// This is a comment line, ignore it
if (line.startsWith("//")) {
if (!titleFound) // Set the title to the first comment
{
name = line.mid(2).trimmed();
titleFound = true;
} else if (okRows == 0) // We haven't processed any cards yet
{
comments += line.mid(2).trimmed() + "\n";
// find sideboard position, if marks are used this won't be needed
int sBStart = -1;
if (inputs.indexOf(reSBMark, deckStart) == -1) {
sBStart = inputs.indexOf(reSBComment, deckStart);
if (sBStart == -1) {
sBStart = inputs.indexOf(reEmpty, deckStart + 1);
if (sBStart == -1) {
sBStart = max_line;
}
int nextCard = inputs.indexOf(reCardLine, sBStart + 1);
if (inputs.indexOf(reEmpty, nextCard + 1) != -1) {
sBStart = max_line; // if there is another empty line all cards are mainboard
}
}
}
int index = 0;
QRegularExpressionMatch match;
// parse name and comments
while (index < deckStart) {
const QString current = inputs.at(index++);
if (!current.contains(reEmpty)) {
match = reComment.match(current);
name = match.captured();
break;
}
}
while (index < deckStart) {
const QString current = inputs.at(index++);
if (!current.contains(reEmpty)) {
match = reComment.match(current);
comments += match.captured() + '\n';
}
}
comments.chop(1); // remove last newline
// parse decklist
for (; index < max_line; ++index) {
// check if line is a card
match = reCardLine.match(inputs.at(index));
if (!match.hasMatch())
continue;
}
QString cardName = match.captured().simplified();
// If we have a blank line and it's the _ONLY_ blank line in the paste
// and it follows at least one valid card
// Then we assume it means to start the sideboard section of the paste.
// If we have the word "Sideboard" appear on any line, then that will
// also indicate the start of the sideboard.
if ((line.isEmpty() && blankLines == 1 && okRows > 0) || line.startsWith("sideboard")) {
inSideboard = true;
continue; // The line isn't actually a card
}
isSideboard = inSideboard;
if (line.startsWith("sb:")) {
line = line.mid(3).trimmed();
isSideboard = true;
}
if (line.trimmed().isEmpty()) {
continue; // The line was " " instead of "\n"
}
// Filter out MWS edition symbols and basic land extras
QRegExp rx("\\[.*\\]\\s?");
line.remove(rx);
rx.setPattern("\\s?\\(.*\\)");
line.remove(rx);
// Filter out post card name editions
rx.setPattern("\\|.*$");
line.remove(rx);
// If the user inputs "Quicksilver Elemental" then it will cut it off
// 1x Squishy Treaker
int i = line.indexOf(' ');
int cardNameStart = i + 1;
if (i > 0) {
// If the count ends with an 'x', ignore it. For example,
// "4x Storm Crow" will count 4 correctly.
if (line.at(i - 1) == 'x') {
i--;
} else if (!line.at(i - 1).isDigit()) {
// If the user inputs "Quicksilver Elemental" then it will work as 1x of that card
cardNameStart = 0;
// check if card should be sideboard
bool sideboard = false;
if (sBStart < 0) {
match = reSBMark.match(cardName);
if (match.hasMatch()) {
sideboard = true;
cardName = match.captured(1);
}
} else {
if (index == sBStart) // skip sideboard line itself
continue;
sideboard = index > sBStart;
}
bool ok;
int number = line.left(i).toInt(&ok);
if (!ok) {
number = 1; // If input is "cardName" assume it's "1x cardName"
// check if a specific amount is mentioned
int amount = 1;
match = reMultiplier.match(cardName);
if (match.hasMatch()) {
amount = match.capturedRef(1).toInt();
cardName = match.captured(2);
}
QString cardName = line.mid(cardNameStart);
// remove stuff inbetween braces
cardName.remove(reBrace);
cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards
cardName.remove(reDigitBrace); // all cards from un-sets that have a word in between round braces at the end
// Common differences between Cockatrice's card names
// and what's commonly used in decklists
rx.setPattern("");
cardName.replace(rx, "'");
rx.setPattern("Æ");
cardName.replace(rx, "Ae");
rx.setPattern("\\s*[|/]{1,2}\\s*");
cardName.replace(rx, " // ");
// Replace only if the ampersand is preceded by a non-capital letter,
// as would happen with acronyms. So 'Fire & Ice' is replaced but not
// 'R&D' or 'R & D'.
// Qt regexes don't support lookbehind so we capture and replace instead.
rx.setPattern("([^A-Z])\\s*&\\s*");
if (rx.indexIn(cardName) != -1) {
cardName.replace(rx, QString("%1 // ").arg(rx.cap(1)));
// replace common differences in cardnames
for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) {
cardName.replace(diff.key(), diff.value());
}
// We need to get the name of the card from the database,
// but we can't do that until we get the "real" name
// (name stored in database for the card)
// and establish a card info that is of the card, then it's
// a simple getting the _real_ name of the card
// (i.e. "STOrm, CrOW" => "Storm Crow")
// get cardname, this function does nothing if the name is not found
cardName = getCompleteCardName(cardName);
// Look for the correct card zone of where to place the new card
QString zoneName = getCardZoneFromName(cardName, isSideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
// get zone name based on if it's in sideboard
QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
okRows++;
new DecklistCardNode(cardName, number, getZoneObjFromName(zoneName));
// make new entry in decklist
new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName));
}
updateDeckHash();
return (okRows > 0);
return true;
}
InnerDecklistNode *DeckList::getZoneObjFromName(const QString zoneName)

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)
endif (UNIX)
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;
}
}
message Command_Unconcede {
extend GameCommand {
optional Command_Unconcede ext = 1032;
}
}

View File

@ -6,3 +6,9 @@ message Context_Concede {
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